mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-16 04:28:39 +00:00
Merge pull request #958 from iflytek/chore/trellis-engineering-specs
docs(workflow): 将工程 Skill 迁移为 Trellis 分层规范
This commit is contained in:
commit
1cff1bbe0a
50 changed files with 1962 additions and 1660 deletions
|
|
@ -1,140 +0,0 @@
|
|||
---
|
||||
name: api-and-namespace-design
|
||||
description: API design conventions, namespace coordinate system, RBAC roles, ClawHub compatibility layer, OpenAPI contract sync rules, and CSRF/session handling.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# API and Namespace Design Skill
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when:
|
||||
- Adding or modifying REST API endpoints
|
||||
- Changing namespace, skill, or user coordinate logic
|
||||
- Working on ClawHub CLI compatibility layer
|
||||
- Modifying OpenAPI specifications or generated types
|
||||
- Adding new admin or governance endpoints
|
||||
|
||||
## Namespace Coordinate System
|
||||
|
||||
SkillHub uses a two-axis coordinate model:
|
||||
|
||||
```
|
||||
@{namespace_slug}/{skill_slug}
|
||||
```
|
||||
|
||||
- `@global/my-skill` — Global namespace skill
|
||||
- `@my-team/my-skill` — Team namespace skill (namespace slug is any valid slug)
|
||||
- `@department-ops/my-skill` — Department namespace skill
|
||||
|
||||
### Namespace Model
|
||||
|
||||
Namespaces (`domain/namespace/`):
|
||||
- **Slug**: unique identifier, validated by `SlugValidator`
|
||||
- **Status**: `ACTIVE`, `FROZEN`, `ARCHIVED`
|
||||
- **Roles**: `OWNER`, `ADMIN`, `MEMBER`
|
||||
- Frozen or archived namespaces cannot publish skills
|
||||
|
||||
### RBAC Roles
|
||||
|
||||
**Namespace-level** (`domain/namespace/NamespaceRole`):
|
||||
- `OWNER` — Full control over namespace and all skills
|
||||
- `ADMIN` — Can manage members, archive skills, publish
|
||||
- `MEMBER` — Can publish skills to the namespace
|
||||
|
||||
**Platform-level**:
|
||||
- `SUPER_ADMIN` — Bypasses all permission checks, can publish directly without review
|
||||
|
||||
## ClawHub Compatibility Layer
|
||||
|
||||
ClawHub CLI uses a single-slug model (no `/` allowed in slugs). Mapping:
|
||||
|
||||
| SkillHub Coordinate | Canonical Slug | Notes |
|
||||
|---------------------|----------------|-------|
|
||||
| `@global/my-skill` | `my-skill` | Global namespace omits prefix |
|
||||
| `@team-name/my-skill` | `team-name--my-skill` | Double-dash separator |
|
||||
|
||||
**Conflict resolution**: `--` split takes priority. `@global/team-name--my-skill` would conflict
|
||||
with `@team-name/my-skill`, resolved to the team namespace skill. Global skill slugs must NOT
|
||||
contain `--`.
|
||||
|
||||
## API Design
|
||||
|
||||
### Controllers
|
||||
|
||||
- Controllers in `skillhub-app` (`com.iflytek.skillhub.controller/`) are **transport only**
|
||||
- Responsibilities: extract auth context, bind request params, wrap responses
|
||||
- Complex business logic belongs in domain services (`skillhub-domain`) or app services
|
||||
- Use Springdoc OpenAPI annotations (`@Operation`, `@ApiResponse`) for API documentation
|
||||
- User identity is always **String** in API inputs and outputs
|
||||
|
||||
### Request/Response Patterns
|
||||
|
||||
- DTOs in `com.iflytek.skillhub.dto/`
|
||||
- `ReviewTaskRequest` / `ReviewTaskResponse` for review workflow
|
||||
- Response wrapping handled at controller layer
|
||||
- Validation errors use `DomainBadRequestException` with i18n message keys
|
||||
|
||||
### Session and CSRF
|
||||
|
||||
- Session-based auth with cookie storage
|
||||
- CSRF protection via `XSRF-TOKEN` cookie and `X-XSRF-TOKEN` header
|
||||
- Smoke tests validate the full register → login → CSRF → action → logout flow
|
||||
- Mock auth uses `X-Mock-User-Id` header in local dev
|
||||
|
||||
### Well-known Discovery
|
||||
|
||||
`/.well-known/clawhub.json` returns `{ "apiBase": "/api/v1" }` for ClawHub CLI auto-discovery.
|
||||
|
||||
## OpenAPI Contract Sync
|
||||
|
||||
When backend API contracts change:
|
||||
|
||||
```bash
|
||||
make generate-api
|
||||
```
|
||||
|
||||
This runs `openapi-typescript http://localhost:8080/v3/api-docs -o src/api/generated/schema.d.ts`.
|
||||
|
||||
Commit the updated `web/src/api/generated/schema.d.ts` with the PR.
|
||||
|
||||
To verify no drift:
|
||||
|
||||
```bash
|
||||
./scripts/check-openapi-generated.sh
|
||||
```
|
||||
|
||||
This starts local dependencies, boots the backend, regenerates the schema, and fails if the
|
||||
checked-in SDK is stale.
|
||||
|
||||
## Versioning and Tags
|
||||
|
||||
- Semantic versioning for skill versions (`major.minor.patch`)
|
||||
- `latest` tag is system-reserved, read-only, auto-follows `Skill.latestVersionId`
|
||||
- Custom tags (`stable`, `beta`) are manually maintained
|
||||
- `latest` cannot be moved manually
|
||||
- Auto-generated versions use `yyyyMMdd.HHmmss` format when no version is specified in SKILL.md
|
||||
|
||||
## Key API Endpoints
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `GET` | `/api/v1/auth/me` | Current user info (401 if unauthenticated) |
|
||||
| `POST` | `/api/v1/auth/local/login` | Local account login |
|
||||
| `POST` | `/api/v1/auth/local/register` | Local account registration |
|
||||
| `POST` | `/api/v1/auth/logout` | Logout (302/200/204) |
|
||||
| `POST` | `/api/v1/auth/local/change-password` | Password change |
|
||||
| `GET` | `/api/v1/namespaces` | List namespaces |
|
||||
| `GET` | `/api/v1/labels` | List visible labels (public) |
|
||||
| `POST` | `/api/v1/admin/labels` | Create label definition (admin) |
|
||||
| `DELETE` | `/api/v1/admin/labels/{slug}` | Delete label definition (admin) |
|
||||
| `GET` | `/actuator/health` | Health check |
|
||||
| `GET` | `/actuator/prometheus` | Prometheus metrics |
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- Forgetting CSRF token on POST/PUT/DELETE requests (needs `X-XSRF-TOKEN` header)
|
||||
- Using numeric user IDs in API — all user identities are **String**
|
||||
- Not regenerating OpenAPI types after adding/changing endpoints
|
||||
- Putting business logic in controllers instead of domain/app services
|
||||
- Assuming namespace slugs follow a specific prefix pattern — they are arbitrary valid slugs
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
---
|
||||
name: backend-module-structure
|
||||
description: Rules for the SkillHub backend Maven multi-module clean architecture. Ensures agents place new code in the correct module and respect dependency direction.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Backend Module Structure Skill
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when:
|
||||
- Adding or modifying Java backend code
|
||||
- Creating new services, controllers, repositories, or entities
|
||||
- Refactoring backend code across files
|
||||
- Reviewing backend code placement
|
||||
|
||||
## Rules
|
||||
|
||||
### Dependency Direction
|
||||
|
||||
The design-doc dependency direction:
|
||||
|
||||
```
|
||||
app → domain, auth, search, storage, infra, notification
|
||||
infra → domain # implements domain repository interfaces
|
||||
auth → domain
|
||||
search → domain
|
||||
notification → domain
|
||||
storage → (independent) # pure SPI
|
||||
```
|
||||
|
||||
**Design intent**: `skillhub-domain` should be the innermost layer, defining entities,
|
||||
repository interfaces, and domain services without depending on infra, auth, search, or storage.
|
||||
|
||||
**Code reality**: `skillhub-domain` declares a Maven dependency on `skillhub-storage` (via
|
||||
`pom.xml`), and several domain services (`SkillHardDeleteService`, `SkillDownloadService`,
|
||||
`SkillPublishService`, `SkillGovernanceService`, `SkillQueryService`,
|
||||
`SkillStorageDeletionCompensationService`) import `com.iflytek.skillhub.storage.ObjectStorageService`.
|
||||
This is an existing deviation from the ideal clean architecture. New code should avoid adding
|
||||
further cross-module dependencies from domain.
|
||||
|
||||
### Where to Place Code
|
||||
|
||||
| Code Type | Module | Java Package |
|
||||
|-----------|--------|-------------|
|
||||
| Entity / Value Object | skillhub-domain | `com.iflytek.skillhub.domain.{submodule}/` |
|
||||
| Repository Interface | skillhub-domain | `com.iflytek.skillhub.domain.{submodule}/` |
|
||||
| Domain Service | skillhub-domain | `com.iflytek.skillhub.domain.{submodule}/service/` |
|
||||
| Domain Event | skillhub-domain | `com.iflytek.skillhub.domain/event/` |
|
||||
| Domain Exception | skillhub-domain | `com.iflytek.skillhub.domain/shared/exception/` |
|
||||
| JPA Repository Impl | skillhub-infra | `com.iflytek.skillhub.infra.repository/` |
|
||||
| Controller | skillhub-app | `com.iflytek.skillhub.controller/` |
|
||||
| App Service | skillhub-app | `com.iflytek.skillhub.service/` |
|
||||
| Query Repository | skillhub-app | `com.iflytek.skillhub.repository/` |
|
||||
| DTO / Response | skillhub-app | `com.iflytek.skillhub.dto/` |
|
||||
| OAuth2 / Auth Config | skillhub-auth | `com.iflytek.skillhub.auth/` |
|
||||
| Search SPI / Impl | skillhub-search | `com.iflytek.skillhub.search/` |
|
||||
| Storage SPI / Impl | skillhub-storage | `com.iflytek.skillhub.storage/` |
|
||||
| Notification Service | skillhub-notification | `com.iflytek.skillhub.notification/` |
|
||||
|
||||
### Maven Modules
|
||||
|
||||
The parent POM (`server/pom.xml`) defines 7 modules with `spring-boot-starter-parent:3.2.3`:
|
||||
|
||||
```
|
||||
skillhub-app | skillhub-domain | skillhub-auth | skillhub-search
|
||||
skillhub-storage | skillhub-infra | skillhub-notification
|
||||
```
|
||||
|
||||
### Repository vs Query Repository
|
||||
|
||||
- **Domain Repository** (`skillhub-domain`): Aggregate reads, state transitions, rule evaluation.
|
||||
Returns domain objects. Defined as interfaces, implemented in `skillhub-infra` via Spring Data JPA.
|
||||
- **Query Repository** (`com.iflytek.skillhub.repository`): Read-model assembly, joins multiple
|
||||
sources, presentation projection. Returns DTOs. Implemented directly in `skillhub-app`.
|
||||
|
||||
Current query repositories:
|
||||
- `GovernanceQueryRepository` / `JpaGovernanceQueryRepository`
|
||||
- `MySkillQueryRepository` / `JpaMySkillQueryRepository`
|
||||
- `ProfileReviewQueryRepository` / `JpaProfileReviewQueryRepository`
|
||||
- `AdminSkillReportQueryRepository` / `JpaAdminSkillReportQueryRepository`
|
||||
|
||||
When a new read use case arrives:
|
||||
1. If it's for state transition or domain rule → domain repository port
|
||||
2. If it's for page/list/detail response assembly with joins → app query repository
|
||||
3. If it's a thin single-aggregate read → direct domain repository call from app service
|
||||
4. If direct SQL/EntityManager is needed → add class-level comment explaining why
|
||||
|
||||
### Building Backend Tests
|
||||
|
||||
Never run `./mvnw -pl skillhub-app clean test` directly under `server/`. Use:
|
||||
```bash
|
||||
make test-backend-app # skillhub-app + dependencies (includes -am)
|
||||
make test-backend # all backend modules
|
||||
```
|
||||
|
||||
Running clean test on skillhub-app alone can fall back to stale artifacts from the local Maven
|
||||
repository, surfacing misleading `cannot find symbol` and signature-mismatch errors.
|
||||
|
||||
### User Identity Type
|
||||
|
||||
User identity is **always String** throughout the codebase. This covers:
|
||||
- Authentication, API params, permissions, audit
|
||||
- Resource owner, creator, reviewer, actor, submittedBy
|
||||
- All user-associated fields
|
||||
|
||||
The `UserAccount` entity uses `@Column(length = 128)` for its ID. The platform needs to support
|
||||
external SSO/OIDC/SCIM identity sources whose UIDs are typically stable strings.
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
---
|
||||
name: code-conventions
|
||||
description: Code style, logging, and testing conventions for SkillHub backend (Java) and frontend (TypeScript). Use when writing or reviewing code.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Code Conventions Skill
|
||||
|
||||
## Java / Backend Conventions
|
||||
|
||||
### User Identity Type
|
||||
|
||||
User identity is **always `String`** throughout the codebase. This covers:
|
||||
- Authentication and authorization
|
||||
- API parameters and responses
|
||||
- Permission checks
|
||||
- Audit logs
|
||||
- Resource owner, creator, reviewer, actor, submittedBy fields
|
||||
|
||||
Never introduce `int`, `long`, or `bigint` as user identifiers. The platform needs to support
|
||||
external SSO/OIDC/SCIM identity sources whose UIDs are typically stable strings.
|
||||
|
||||
### Exception Handling
|
||||
|
||||
- Use `LocalizedDomainException` for user-facing error messages (supports i18n)
|
||||
- Use `DomainBadRequestException` for invalid client input
|
||||
- Use `DomainNotFoundException` for missing resources
|
||||
- Use `DomainForbiddenException` for authorization failures
|
||||
- Exception classes live in `skillhub-domain/shared/exception/`
|
||||
|
||||
### Domain Services
|
||||
|
||||
- Return domain objects, not DTOs
|
||||
- Contain business rules and state transitions
|
||||
- Use domain events for cross-cutting side effects (publishing, notifications)
|
||||
- Located in `domain/{submodule}/service/`
|
||||
|
||||
### Controllers
|
||||
|
||||
- Transport only: extract auth context, bind request params, wrap responses
|
||||
- No business logic in controllers
|
||||
- Located in `com.iflytek.skillhub.controller/`
|
||||
|
||||
### Query Repositories
|
||||
|
||||
- Handle read-model joins and presentation projection
|
||||
- Return DTOs or presentation models
|
||||
- Located in `com.iflytek.skillhub.repository/`
|
||||
- Named like `*QueryRepository` (e.g., `GovernanceQueryRepository`, `MySkillQueryRepository`)
|
||||
|
||||
### App Services
|
||||
|
||||
- Workflow orchestration: coordinate domain services and query repositories
|
||||
- Should express "what this endpoint does", not "how it assembles DTOs"
|
||||
- Located in `com.iflytek.skillhub.service/`
|
||||
|
||||
### Logging
|
||||
|
||||
- Use SLF4J with structured logging
|
||||
- Use MDC for request tracing
|
||||
- Log at appropriate levels: INFO for business events, DEBUG for troubleshooting, ERROR for failures
|
||||
|
||||
## TypeScript / Frontend Conventions
|
||||
|
||||
### Type Safety
|
||||
|
||||
- Strict TypeScript mode. No `any` types.
|
||||
- Use generated OpenAPI types from `web/src/api/generated/schema.d.ts` for all API interactions.
|
||||
- Additional types in `web/src/types/`
|
||||
|
||||
### Data Fetching
|
||||
|
||||
- **Always use TanStack Query** (`@tanstack/react-query`) for server state
|
||||
- **Never use `useEffect`** for data fetching
|
||||
- Use `openapi-fetch` client for type-safe API calls
|
||||
|
||||
### Component Composition
|
||||
|
||||
- **Radix UI** primitives: `@radix-ui/react-dropdown-menu`, `@radix-ui/react-select`
|
||||
- **class-variance-authority** (cva) for component variants
|
||||
- **clsx** + **tailwind-merge** for class merging
|
||||
- **`cn()` utility**: `web/src/shared/lib/utils.ts`
|
||||
- shadcn/ui is NOT used as a library
|
||||
|
||||
### State Management
|
||||
|
||||
- **TanStack Query** for server state (API data, caching, invalidation)
|
||||
- **Zustand** for local/UI state (theme, sidebar, modals, form state)
|
||||
|
||||
### Feature-Sliced Design
|
||||
|
||||
| Layer | Path | Purpose |
|
||||
|-------|------|---------|
|
||||
| Pages | `web/src/pages/` | Route-level page components |
|
||||
| Features | `web/src/features/` | Self-contained business features |
|
||||
| Entities | `web/src/entities/` | Domain entity display logic |
|
||||
| Shared | `web/src/shared/` | Reusable UI components, hooks, utilities |
|
||||
|
||||
Place code at the lowest appropriate layer. Do not put page-level logic in shared.
|
||||
|
||||
### Styling
|
||||
|
||||
- Tailwind CSS for all styling
|
||||
- Follow existing component patterns
|
||||
- Use `cn()` for conditional class merging
|
||||
|
||||
### Internationalization
|
||||
|
||||
- Use i18next + react-i18next
|
||||
- All user-facing text must be translatable
|
||||
- Translation keys in `web/src/i18n/`
|
||||
|
||||
## Testing Philosophy
|
||||
|
||||
### Backend
|
||||
|
||||
- JUnit 5 + Mockito + AssertJ
|
||||
- Use Spring Boot test slices where possible (`@WebMvcTest`, `@DataJpaTest`)
|
||||
- Test behaviors, not implementations
|
||||
- Use `make test-backend-app` (includes `-am` for dependent modules)
|
||||
- Never run `./mvnw -pl skillhub-app clean test` directly — stale Maven cache causes misleading errors
|
||||
|
||||
### Frontend
|
||||
|
||||
- Vitest for unit tests
|
||||
- Playwright for E2E tests
|
||||
- Test component behavior and user interactions
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Maven multi-module**: Always use `-am` flag or Makefile targets to include dependent modules
|
||||
- **OpenAPI types**: Must regenerate and commit after API contract changes
|
||||
- **String identity**: Never use numeric types for user identifiers
|
||||
- **Controller business logic**: Move to domain service or app service
|
||||
- **Complex read-models in app service**: Extract to query repository
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
---
|
||||
name: dev-workflow
|
||||
description: The complete development workflow for SkillHub contributors including local dev, staging validation, testing, and PR creation. Ensures agents follow the correct sequence of steps.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Development Workflow Skill
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when:
|
||||
- Starting local development
|
||||
- Running tests or validation
|
||||
- Preparing a pull request
|
||||
- Setting up the development environment
|
||||
- Working with parallel agent worktrees
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Java 21+ (`java -version`)
|
||||
- Maven wrapper (`./mvnw` in `server/`)
|
||||
- Node.js + pnpm
|
||||
- Docker + docker compose
|
||||
- `gh` CLI (for PR creation)
|
||||
- `curl` (for smoke tests and health checks)
|
||||
|
||||
## Workflow Stages
|
||||
|
||||
### Stage 1: Local Development (fast iteration)
|
||||
|
||||
**One-command start:**
|
||||
|
||||
```bash
|
||||
make dev-all # Start full stack: Postgres, Redis, MinIO, scanner, backend, frontend
|
||||
make dev-all-down # Stop everything
|
||||
make dev-all-reset # Full reset (clears data volumes)
|
||||
make dev-status # Check service status
|
||||
```
|
||||
|
||||
**Access points:**
|
||||
- Web UI: `http://localhost:3000`
|
||||
- Backend API: `http://localhost:8080`
|
||||
- Scanner: `http://localhost:8000`
|
||||
|
||||
**Individual components:**
|
||||
|
||||
```bash
|
||||
make dev # Start dependency services only (Postgres, Redis, MinIO, scanner)
|
||||
make dev-server # Start backend in foreground (blocking)
|
||||
make dev-web # Start Vite dev server (HMR enabled)
|
||||
make dev-server-restart # Restart backend process
|
||||
make dev-down # Stop dependency services
|
||||
make dev-logs # View backend logs (use SERVICE=frontend for frontend logs)
|
||||
```
|
||||
|
||||
**Backend development**: After editing Java code, run `make dev-server-restart`.
|
||||
|
||||
**Frontend development**: Vite HMR enabled — save a file for instant browser updates.
|
||||
|
||||
**Scanner**: The security scanner is enabled by default in dev. Health checked at `http://localhost:8000/health`.
|
||||
|
||||
### Stage 2: Testing
|
||||
|
||||
| Command | Scope | Notes |
|
||||
|---------|-------|-------|
|
||||
| `make test-backend-app` | Backend unit tests | skillhub-app + dependencies (`-am`) |
|
||||
| `make test-backend` | All backend modules | All modules via `./mvnw test` |
|
||||
| `make test-frontend` | Frontend unit tests | Vitest (pnpm run test) |
|
||||
| `make test-e2e-frontend` | Frontend E2E tests | Playwright |
|
||||
| `make test-e2e-smoke-frontend` | Frontend E2E smoke | Playwright subset |
|
||||
| `make typecheck-web` | TypeScript type check | `tsc --noEmit` |
|
||||
| `make lint-web` | ESLint check | Frontend linting |
|
||||
|
||||
**Important**: Never run `./mvnw -pl skillhub-app clean test` directly under `server/`.
|
||||
Use `-am` or Makefile targets to include dependent modules.
|
||||
|
||||
### Stage 3: Staging Regression (pre-PR validation)
|
||||
|
||||
```bash
|
||||
make staging # Build backend Docker image + frontend static + smoke test
|
||||
make staging-down # Tear down
|
||||
make staging-logs # View backend logs
|
||||
SERVICE=web make staging-logs # View Nginx logs
|
||||
```
|
||||
|
||||
Staging validates the containerized deployment path:
|
||||
- Backend: built as Docker image from local source (`Dockerfile.dev`)
|
||||
- Frontend: built as static files (`pnpm build`), served by Nginx
|
||||
- Dependencies: same Postgres/Redis/MinIO as local dev
|
||||
- Smoke test runs against staging via `scripts/smoke-test.sh`
|
||||
|
||||
**Staging URLs:**
|
||||
- Web UI: `http://localhost`
|
||||
- Backend API: `http://localhost:8080`
|
||||
|
||||
**Staging credentials** (for bootstrap admin):
|
||||
- Username: `admin`
|
||||
- Password: `Admin@staging2026`
|
||||
|
||||
### Stage 4: Pull Request
|
||||
|
||||
```bash
|
||||
make pr # Push branch + create PR (requires gh CLI)
|
||||
```
|
||||
|
||||
Requirements:
|
||||
- `gh` CLI installed and authenticated
|
||||
- Not on main/master branch
|
||||
- All changes committed (will prompt if uncommitted changes exist)
|
||||
|
||||
### Useful Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `make generate-api` | Regenerate OpenAPI types from running backend |
|
||||
| `make namespace-smoke` | Namespace workflow smoke test |
|
||||
| `make db-reset` | Reset database only (Flyway migrate) |
|
||||
| `make validate-release-config` | Validate release env vars (.env.release) |
|
||||
| `./scripts/smoke-test.sh` | Basic API smoke test (health, auth, labels) |
|
||||
| `./scripts/namespace-smoke-test.sh` | Namespace CRUD + membership smoke test |
|
||||
| `./scripts/check-openapi-generated.sh` | Verify OpenAPI types are not stale |
|
||||
| `make parallel-init TASK=name` | Create parallel worktree for agent |
|
||||
|
||||
### Mock Auth Users
|
||||
|
||||
| User ID | Role | Header |
|
||||
|---------|------|--------|
|
||||
| `local-user` | Regular user | `X-Mock-User-Id: local-user` |
|
||||
| `local-admin` | Super admin | `X-Mock-User-Id: local-admin` |
|
||||
|
||||
Bootstrap admin (local profile):
|
||||
- Username: `admin`
|
||||
- Password: `ChangeMe!2026`
|
||||
|
||||
### Smoke Test Coverage
|
||||
|
||||
`scripts/smoke-test.sh` validates:
|
||||
1. Health endpoint (`/actuator/health` → 200)
|
||||
2. Prometheus metrics (`/actuator/prometheus` → 200)
|
||||
3. Namespaces API (`/api/v1/namespaces` → 200)
|
||||
4. Auth required (`/api/v1/auth/me` → 401 without session)
|
||||
5. User registration flow (with CSRF)
|
||||
6. Auth me with session
|
||||
7. Password change
|
||||
8. Logout + verify 401 after
|
||||
9. Admin login
|
||||
10. Label CRUD (admin only)
|
||||
|
||||
Additional smoke tests:
|
||||
- `scripts/namespace-smoke-test.sh` — Namespace creation, membership, publishing
|
||||
- `scripts/governance-smoke-test.sh` — Governance and moderation
|
||||
- `scripts/promotion-smoke-test.sh` — Skill promotion between scopes
|
||||
|
||||
### Parallel Agent Workflow
|
||||
|
||||
For parallel agent development with isolated worktrees:
|
||||
|
||||
```bash
|
||||
make parallel-init TASK=feature-name # Create worktree
|
||||
make parallel-sync SOURCES="feat1 feat2" # Merge feature branches
|
||||
make parallel-up SOURCES="feat1 feat2" # Merge + start dev environment
|
||||
make parallel-down # Stop parallel environment
|
||||
```
|
||||
|
||||
See `docs/13-parallel-workflow.md` for full details.
|
||||
|
||||
### Commit Style
|
||||
|
||||
Use conventional commit format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
```
|
||||
|
||||
Examples:
|
||||
```
|
||||
feat(auth): add local account login
|
||||
fix(publish): resolve null pointer when skill metadata is missing name
|
||||
docs(deploy): clarify runtime image usage
|
||||
test(namespace): add membership service edge case tests
|
||||
refactor(review): extract query repository for governance list
|
||||
chore(ci): add parallel workflow scripts
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Backend won't start | Check Java version (`java -version`), must be 21+ |
|
||||
| Port 8080 in use | `lsof -i :8080` to find and kill the process |
|
||||
| Maven download timeout | Configure mirror in `~/.m2/settings.xml` |
|
||||
| Frontend won't start | Run `make web-deps` to ensure node_modules exist |
|
||||
| Staging build fails | Check `Dockerfile.dev` and ensure Maven build succeeds first |
|
||||
| CSRF errors in tests | Ensure cookie jar is shared and CSRF token refreshed after login |
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
---
|
||||
name: frontend-conventions
|
||||
description: Coding conventions, architecture patterns, and testing rules for the SkillHub React frontend. Ensures agents follow Feature-Sliced Design and use the generated OpenAPI types.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Frontend Conventions Skill
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when:
|
||||
- Adding or modifying React/TypeScript frontend code
|
||||
- Creating new pages, features, entities, or shared components
|
||||
- Changing API client calls or data fetching patterns
|
||||
|
||||
## Rules
|
||||
|
||||
### Feature-Sliced Design
|
||||
|
||||
Place code at the lowest appropriate layer:
|
||||
|
||||
| Layer | Path | Purpose |
|
||||
|-------|------|---------|
|
||||
| Pages | `web/src/pages/` | Route-level page components |
|
||||
| Features | `web/src/features/` | Business features (search, upload, review, etc.) |
|
||||
| Entities | `web/src/entities/` | Domain entity display logic (skill, user, namespace) |
|
||||
| Shared | `web/src/shared/` | Reusable UI components, hooks, utilities |
|
||||
|
||||
Current features:
|
||||
- `admin` — Admin panel (user management, labels, search)
|
||||
- `auth` — Login, OAuth flows, device auth
|
||||
- `governance` — Skill governance actions (hide, yank, archive)
|
||||
- `namespace` — Namespace management (members, settings)
|
||||
- `notification` — User notifications and inbox
|
||||
- `promotion` — Skill promotion between scopes
|
||||
- `publish` — Skill upload/publish UI
|
||||
- `report` — Skill reporting
|
||||
- `review` — Review workflow UI
|
||||
- `search` — Skill search and filtering
|
||||
- `security-audit` — Security audit viewer
|
||||
- `skill` — Skill detail, listing, cards
|
||||
- `social` — Stars, ratings, subscriptions
|
||||
- `token` — API token management
|
||||
|
||||
### Data Fetching
|
||||
|
||||
- **Always use TanStack Query** (`@tanstack/react-query`) for server state.
|
||||
- **Never use `useEffect`** for data fetching.
|
||||
- Use `openapi-fetch` client with generated types from `web/src/api/generated/schema.d.ts`.
|
||||
- Never use `any` types.
|
||||
|
||||
### State Management
|
||||
|
||||
- **TanStack Query** for server state (API data, caching, invalidation, optimistic updates)
|
||||
- **Zustand** for local/UI state (theme, sidebar, modals, form state)
|
||||
|
||||
### Component Composition
|
||||
|
||||
- **Radix UI** primitives: `@radix-ui/react-dropdown-menu`, `@radix-ui/react-select`
|
||||
- **class-variance-authority** (cva) for component variants
|
||||
- **clsx** + **tailwind-merge** for class merging
|
||||
- **`cn()` utility**: `web/src/shared/lib/utils.ts`
|
||||
- **shadcn/ui is NOT used** as a library
|
||||
|
||||
### API Type Generation
|
||||
|
||||
When backend OpenAPI contracts change:
|
||||
|
||||
```bash
|
||||
make generate-api
|
||||
```
|
||||
|
||||
This runs `openapi-typescript http://localhost:8080/v3/api-docs -o src/api/generated/schema.d.ts`.
|
||||
|
||||
Commit the updated `web/src/api/generated/schema.d.ts` with the PR.
|
||||
|
||||
To verify the generated file is not stale:
|
||||
|
||||
```bash
|
||||
./scripts/check-openapi-generated.sh
|
||||
```
|
||||
|
||||
### Styling
|
||||
|
||||
- **Tailwind CSS** for all styling
|
||||
- **`cn()` utility** for conditional class merging
|
||||
- Follow existing component patterns in `web/src/shared/components/`
|
||||
|
||||
### Internationalization
|
||||
|
||||
- **i18next** + **react-i18next** for translations
|
||||
- All user-facing text must be translatable
|
||||
- Translation keys in `web/src/i18n/`
|
||||
|
||||
### Build & Development
|
||||
|
||||
```bash
|
||||
make dev-web # Start Vite dev server (HMR enabled)
|
||||
make build-frontend # Production build
|
||||
make typecheck-web # TypeScript type check (tsc --noEmit)
|
||||
make lint-web # ESLint check
|
||||
make test-frontend # Vitest unit tests
|
||||
make test-e2e-frontend # Playwright E2E tests
|
||||
make test-e2e-smoke-frontend # Playwright smoke tests
|
||||
```
|
||||
|
||||
Vite HMR is enabled by default — save a file and the browser updates instantly.
|
||||
|
||||
### Frontend Dependencies
|
||||
|
||||
Key dependencies (from `web/package.json`):
|
||||
- `react` 19, `react-dom` 19
|
||||
- `@tanstack/react-query` 5
|
||||
- `@tanstack/react-router` 1
|
||||
- `@radix-ui/react-dropdown-menu`, `@radix-ui/react-select`
|
||||
- `class-variance-authority`, `clsx`, `tailwind-merge`
|
||||
- `openapi-fetch` 0.13
|
||||
- `i18next`, `react-i18next`
|
||||
- `zustand` 5
|
||||
- `react-markdown`, `rehype-highlight`, `rehype-sanitize`
|
||||
- `lucide-react` (icons)
|
||||
- `sonner` (toasts)
|
||||
|
||||
Build tools: Vite 6, TypeScript 5.7, Vitest 3.2, Playwright 1.58
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
---
|
||||
name: pr-submission
|
||||
description: PR title format, commit conventions, and pre-PR checklist for SkillHub. Use when preparing or reviewing pull requests.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# PR Submission Skill
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Identify the scope of your change (feature, bug fix, docs, test, refactor, chore)
|
||||
2. Format PR title and commits using the conventions below
|
||||
3. Run the pre-PR checklist commands
|
||||
4. Open the PR with a descriptive body
|
||||
|
||||
## PR Title Format
|
||||
|
||||
Use conventional commit style:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
```
|
||||
|
||||
**Types:**
|
||||
|
||||
| Type | When to Use |
|
||||
|------|-------------|
|
||||
| `feat` | New feature or capability |
|
||||
| `fix` | Bug fix |
|
||||
| `docs` | Documentation changes only |
|
||||
| `test` | Adding or updating tests |
|
||||
| `refactor` | Code restructuring with no behavior change |
|
||||
| `chore` | Build, CI, tooling, or maintenance tasks |
|
||||
|
||||
**Scopes:** Use module or domain names: `auth`, `search`, `publish`, `review`, `namespace`, `governance`, `deploy`, `ci`, `frontend`, `scanner`
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
feat(auth): add local account login with password reset
|
||||
fix(publish): resolve null pointer when skill metadata is missing name
|
||||
docs(deploy): clarify runtime image usage
|
||||
test(namespace): add membership service edge case tests
|
||||
refactor(review): extract query repository for governance list
|
||||
chore(ci): add parallel workflow scripts for multi-agent development
|
||||
```
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Same convention as PR titles. One logical change per commit.
|
||||
|
||||
**Types:**
|
||||
|
||||
- **feat**: A new feature for the user
|
||||
- **fix**: A bug fix for the user
|
||||
- **docs**: Documentation changes only
|
||||
- **test**: Adding or updating tests
|
||||
- **refactor**: Code change that neither fixes a bug nor adds a feature
|
||||
- **chore**: Changes to build process, CI, or maintenance tasks
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
fix(auth): resolve session cookie conflict in device flow
|
||||
feat(publish): support security scan before review submission
|
||||
docs(skill-protocol): add nested SKILL.md discovery rules
|
||||
test(search): verify jieba analysis with Chinese skill descriptions
|
||||
refactor(storage): simplify LocalFile path normalization
|
||||
```
|
||||
|
||||
## Pre-PR Checklist
|
||||
|
||||
- [ ] Backend tests pass: `make test-backend-app`
|
||||
- [ ] Frontend typecheck passes: `make typecheck-web`
|
||||
- [ ] If API changed: `make generate-api` was run and `web/src/api/generated/schema.d.ts` is committed
|
||||
- [ ] Smoke test passes: `make staging`
|
||||
- [ ] Follow existing module boundaries and dependency direction
|
||||
- [ ] Add/update tests for new behavior
|
||||
- [ ] Update design docs when APIs, auth flows, deployment, or operator workflows change
|
||||
|
||||
## PR Body Structure
|
||||
|
||||
When creating a PR, include:
|
||||
|
||||
1. **What** — Summary of the change
|
||||
2. **Why** — Motivation (link to issue if applicable)
|
||||
3. **How** — Key implementation details (especially for non-obvious decisions)
|
||||
4. **Testing** — How to verify the change works
|
||||
5. **Impact** — Breaking changes, migration notes, or rollout considerations
|
||||
|
||||
## Review Conventions
|
||||
|
||||
- When reviewing, cite the specific AGENTS.md rule that applies if suggesting a convention change
|
||||
- For backend code, check dependency direction does not violate clean architecture rules
|
||||
- For frontend code, check OpenAPI types are regenerated if API changed
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
---
|
||||
name: skill-lifecycle
|
||||
description: The authoritative skill lifecycle state model including container states, version states, review workflow states, visibility overlay, and governance actions. Ensures agents don't introduce invalid states or transitions.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Skill Lifecycle Skill
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when:
|
||||
- Modifying skill publish, review, or unpublish flows
|
||||
- Adding or changing skill/version status fields
|
||||
- Working on search, detail pages, or listing pages that show skill state
|
||||
- Implementing governance actions (hide, yank, archive)
|
||||
- Adding new state transitions or permission checks
|
||||
|
||||
## State Model
|
||||
|
||||
### Skill Container States
|
||||
|
||||
Enum `SkillStatus` (`domain/skill/SkillStatus.java`):
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `ACTIVE` | Skill is operational and can have versions published |
|
||||
| `HIDDEN` | Skill hidden by platform governance (design doc says prefer boolean `hidden` flag instead) |
|
||||
| `ARCHIVED` | Skill archived by owner/namespace admin, cannot publish new versions |
|
||||
|
||||
**Design-vs-code note**: `docs/14-skill-lifecycle.md` specifies `hidden` should be a governance
|
||||
overlay (boolean flag) rather than a lifecycle enum state. The current code still defines
|
||||
`SkillStatus.HIDDEN`. New code should use the `skill.hidden` boolean field, not the enum value.
|
||||
|
||||
### SkillVersion States
|
||||
|
||||
Enum `SkillVersionStatus` (`domain/skill/SkillVersionStatus.java`):
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `DRAFT` | Non-public draft, can resubmit or delete |
|
||||
| `SCANNING` | Undergoing security scan |
|
||||
| `SCAN_FAILED` | Security scan failed |
|
||||
| `UPLOADED` | Uploaded but not yet submitted for review (or withdrawn from review) |
|
||||
| `PENDING_REVIEW` | Frozen pending reviewer action |
|
||||
| `PUBLISHED` | Currently distributable |
|
||||
| `REJECTED` | Review denied, retained |
|
||||
| `YANKED` | Was published, withdrawn from distribution |
|
||||
|
||||
### ReviewTask States
|
||||
|
||||
Enum `ReviewTaskStatus` (`domain/review/ReviewTaskStatus.java`):
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `PENDING` | Awaiting reviewer |
|
||||
| `APPROVED` | Reviewer approved |
|
||||
| `REJECTED` | Reviewer rejected |
|
||||
|
||||
### Visibility Model
|
||||
|
||||
Enum `SkillVisibility` (used in `SkillPublishService`):
|
||||
|
||||
| Value | Publish Path |
|
||||
|-------|-------------|
|
||||
| `PUBLIC` | Creates `PENDING_REVIEW` version, review task, security scan |
|
||||
| `NAMESPACE_ONLY` | Same as PUBLIC but limited visibility scope |
|
||||
| `PRIVATE` | Goes directly to `UPLOADED` status, no review task |
|
||||
|
||||
`SUPER_ADMIN` role bypasses review — versions go directly to `PUBLISHED`.
|
||||
|
||||
### Latest Version Pointer
|
||||
|
||||
`Skill.latestVersionId` is **only** the latest published pointer:
|
||||
- Can only point to a `PUBLISHED` version
|
||||
- May be `null` if no published version exists
|
||||
- `latest` tag auto-follows this pointer (read-only)
|
||||
- When yanking: recalculates to newest remaining `PUBLISHED` version, or `null`
|
||||
|
||||
### Key Transitions
|
||||
|
||||
| Action | From | To | Notes | Source |
|
||||
|--------|------|-----|-------|--------|
|
||||
| First upload (PUBLIC/NAMESPACE_ONLY) | — | `PENDING_REVIEW` | Review task created | `SkillPublishService` |
|
||||
| First upload (SUPER_ADMIN) | — | `PUBLISHED` | Direct publish, `SkillPublishedEvent` emitted | `SkillPublishService` |
|
||||
| First upload (PRIVATE) | — | `UPLOADED` | No review task, `latestVersionId` updated | `SkillPublishService` |
|
||||
| Review approve | `PENDING_REVIEW` | `PUBLISHED` | Updates `latestVersionId` | Review workflow |
|
||||
| Review reject | `PENDING_REVIEW` | `REJECTED` | Version retained | Review workflow |
|
||||
| Withdraw review | `PENDING_REVIEW` | `UPLOADED` | Deletes pending `ReviewTask` | `SkillGovernanceService.withdrawPendingVersion` |
|
||||
| Yank | `PUBLISHED` | `YANKED` | Recalculates `latestVersionId` | `SkillGovernanceService.yankVersion` |
|
||||
| Hide | — | `hidden=true` | Independent overlay | `SkillGovernanceService.hideSkill` |
|
||||
| Restore | — | `hidden=false` | Independent overlay | `SkillGovernanceService.unhideSkill` |
|
||||
| Archive | `ACTIVE` | `ARCHIVED` | `SkillStatusChangedEvent` emitted | `SkillGovernanceService.archiveSkill` |
|
||||
| Unarchive | `ARCHIVED` | `ACTIVE` | `SkillStatusChangedEvent` emitted | `SkillGovernanceService.unarchiveSkill` |
|
||||
| New publish (existing pending) | `PENDING_REVIEW` | `UPLOADED` | Auto-withdraw + delete review task | `SkillPublishService` |
|
||||
| Delete version | `DRAFT`/`REJECTED`/`SCAN_FAILED`/`UPLOADED` | — | Last version protected | `SkillGovernanceService.deleteVersion` |
|
||||
|
||||
### Yank Pointer Recalculation
|
||||
|
||||
When yanking the current `latestVersionId` (`SkillGovernanceService`):
|
||||
1. Query all remaining `PUBLISHED` versions for the skill
|
||||
2. Sort by `publishedAt` DESC, then `createdAt` DESC, then `id` DESC
|
||||
3. Point `latestVersionId` to the top result, or `null` if none remain
|
||||
|
||||
### Lifecycle Projection
|
||||
|
||||
Read models (detail, my-skills, favorites, search) use `*QueryRepository` patterns:
|
||||
- `headlineVersion` — Main display version for the page
|
||||
- `publishedVersion` — Latest published version
|
||||
- `ownerPreviewVersion` — Pending review version (visible to owner/namespace admin)
|
||||
- `resolutionMode` — `PUBLISHED`, `OWNER_PREVIEW`, or `NONE`
|
||||
|
||||
**Public browsing, install, download, search only use `publishedVersion`.**
|
||||
|
||||
### Permission Boundaries
|
||||
|
||||
| Action | Who |
|
||||
|--------|-----|
|
||||
| Withdraw review | Submitter only |
|
||||
| Delete version | Owner or namespace admin, only `DRAFT`/`REJECTED`/`SCAN_FAILED`/`UPLOADED` |
|
||||
| Archive/unarchive | Owner or namespace admin (`ADMIN` or `OWNER` role) |
|
||||
| Hide/restore | Platform governance (no permission check in code) |
|
||||
| Yank | Platform governance (no permission check in code) |
|
||||
| Publish PUBLIC skill | Namespace member (or `SUPER_ADMIN`) |
|
||||
| Publish PRIVATE skill | Namespace member (or `SUPER_ADMIN`) |
|
||||
|
||||
### Delete Version Constraints
|
||||
|
||||
`SkillGovernanceService.deleteVersion` enforces:
|
||||
- Only `DRAFT`, `REJECTED`, `SCAN_FAILED`, or `UPLOADED` versions can be deleted
|
||||
- Cannot delete the last remaining version of a skill
|
||||
- Deletes associated storage keys (individual files + `bundle.zip`)
|
||||
- Deletes associated security scan records
|
||||
- Updates `latestVersionId` if the deleted version was the pointer
|
||||
- Storage deletion happens after transaction commit with compensation recording
|
||||
|
||||
### Domain Events
|
||||
|
||||
| Event | When Emitted |
|
||||
|-------|-------------|
|
||||
| `SkillStatusChangedEvent` | Archive or unarchive |
|
||||
| `SkillPublishedEvent` | SUPER_ADMIN direct publish |
|
||||
| `SkillVersionYankedEvent` | Yank action |
|
||||
| `ReviewSubmittedEvent` | Create review task for PUBLIC/NAMESPACE_ONLY |
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
- Setting `SkillStatus.HIDDEN` directly — use `skill.setHidden(true)` via `SkillGovernanceService` instead
|
||||
- Forgetting to recalculate `latestVersionId` after yank or version deletion
|
||||
- Not auto-withdrawing pending versions when publishing a new version
|
||||
- Missing the `confirmWarnings` two-step publish flow (warnings require explicit confirmation)
|
||||
- Assuming all publish flows create review tasks — `PRIVATE` visibility skips review
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
---
|
||||
name: testing-and-ci
|
||||
description: Testing conventions, CI pipeline rules, and smoke test coverage for SkillHub. Ensures agents write tests correctly and understand the CI gate requirements.
|
||||
license: Apache-2.0
|
||||
---
|
||||
|
||||
# Testing and CI Skill
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when:
|
||||
- Adding or modifying backend tests
|
||||
- Adding or modifying frontend tests
|
||||
- Changing CI/CD workflows
|
||||
- Adding smoke tests or E2E tests
|
||||
|
||||
## Rules
|
||||
|
||||
### Backend Testing
|
||||
|
||||
Tests live alongside source in each module's `src/test/java/`:
|
||||
- `server/skillhub-app/src/test/java/` — Controller integration tests, service tests
|
||||
- `server/skillhub-domain/src/test/java/` — Domain service unit tests
|
||||
- `server/skillhub-auth/src/test/java/` — Auth flow tests
|
||||
|
||||
**Tools**: JUnit 5 + Mockito + AssertJ + Spring Boot test slices (`@WebMvcTest`, `@DataJpaTest`)
|
||||
|
||||
**Build commands:**
|
||||
```bash
|
||||
make test-backend-app # skillhub-app + dependencies (includes -am)
|
||||
make test-backend # all backend modules
|
||||
```
|
||||
|
||||
**Never** run `./mvnw -pl skillhub-app clean test` directly under `server/`.
|
||||
`skillhub-app` depends on sibling modules, and a standalone clean build can fall back to stale
|
||||
artifacts from the local Maven repository, surfacing misleading `cannot find symbol` and
|
||||
signature-mismatch errors. Use `-am`, or the Makefile targets above.
|
||||
|
||||
**Test naming conventions:**
|
||||
- Controller tests: `{ControllerName}Test.java` (e.g., `SkillControllerTest.java`)
|
||||
- Service tests: `{ServiceName}Test.java`
|
||||
- Integration tests: `{FlowName}IntegrationTest.java`
|
||||
- Security tests: `{ControllerName}SecurityTest.java`
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
**Tools**: Vitest (unit), Playwright (E2E)
|
||||
|
||||
```bash
|
||||
make test-frontend # Vitest unit tests (pnpm run test)
|
||||
make test-e2e-frontend # Playwright E2E tests
|
||||
make test-e2e-smoke-frontend # Playwright smoke tests (subset)
|
||||
```
|
||||
|
||||
E2E tests live in `web/e2e/`.
|
||||
|
||||
### Smoke Tests
|
||||
|
||||
Smoke tests validate end-to-end operator workflows against a running backend:
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `scripts/smoke-test.sh` | Basic API health, auth, label CRUD |
|
||||
| `scripts/namespace-smoke-test.sh` | Namespace creation, membership, publishing |
|
||||
| `scripts/governance-smoke-test.sh` | Governance and moderation flows |
|
||||
| `scripts/promotion-smoke-test.sh` | Skill promotion between scopes |
|
||||
|
||||
When operator-facing workflows change, update the corresponding smoke test.
|
||||
|
||||
### CI Pipeline
|
||||
|
||||
GitHub Actions workflows in `.github/workflows/`:
|
||||
|
||||
| Workflow | Trigger | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `pr-tests.yml` | PR | Backend + frontend unit tests |
|
||||
| `pr-e2e.yml` | PR | E2E smoke tests against staging |
|
||||
| `pr-batch-test-deploy.yml` | workflow_dispatch | Batch test and deploy |
|
||||
| `publish-images.yml` | release published / workflow_dispatch | Build and publish Docker images to GHCR |
|
||||
| `deploy-docs.yml` | push to docs | Deploy documentation site |
|
||||
| `issue-triage.yml` | issues | Auto-triage incoming issues |
|
||||
| `issue-backlog-rescore.yml` | cron (every 6h) | Rescore backlog issues |
|
||||
| `release-notes.yml` | workflow_dispatch | Generate release notes |
|
||||
| `deepwiki.yml` | release published | Update DeepWiki documentation |
|
||||
| `claim-issue-reward.yml` | issue_comment | Auto-claim issue rewards |
|
||||
| `statistic-member-reward.yml` | cron/schedule | Calculate member rewards |
|
||||
|
||||
All workflows live in `.github/workflows/`. Deno scripts for triage, release notes, and rewards
|
||||
live in `.github/scripts/`.
|
||||
|
||||
### Staging
|
||||
|
||||
Before opening a PR, validate with staging:
|
||||
|
||||
```bash
|
||||
make staging # Build backend Docker image + frontend static + smoke test
|
||||
make staging-down # Tear down
|
||||
SERVICE=web make staging-logs # View Nginx logs
|
||||
```
|
||||
|
||||
Staging validates the containerized deployment path:
|
||||
- Backend: built as Docker image from local source (`Dockerfile.dev`)
|
||||
- Frontend: built as static files (`pnpm build`), served by Nginx
|
||||
- Dependencies: same Postgres/Redis/MinIO as local dev
|
||||
|
||||
If staging passes, the environment stays running at:
|
||||
- Web UI: `http://localhost`
|
||||
- Backend API: `http://localhost:8080`
|
||||
|
||||
### Pre-PR Testing Checklist
|
||||
|
||||
- [ ] `make test-backend-app` passes
|
||||
- [ ] `make typecheck-web` passes
|
||||
- [ ] `make lint-web` passes (if frontend changed)
|
||||
- [ ] `make staging` passes (full regression)
|
||||
- [ ] If API changed: `make generate-api` run and generated file committed
|
||||
- [ ] New behavior has corresponding tests
|
||||
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
## Validation
|
||||
|
||||
- [ ] Backend tests passed
|
||||
- [ ] Frontend typecheck/build passed
|
||||
- [ ] Applicable checks selected from `.trellis/spec/testing/index.md`; exclusions explained
|
||||
- [ ] Backend tests passed when backend behavior changed
|
||||
- [ ] Frontend typecheck/build passed when frontend files changed
|
||||
- [ ] OpenAPI SDK regenerated or checked when API contracts changed
|
||||
- [ ] Smoke test run when relevant
|
||||
|
||||
|
|
|
|||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -85,6 +85,14 @@ docs/superpowers/
|
|||
# Local workspace metadata
|
||||
CLAUDE.md
|
||||
|
||||
# Share stable engineering specs; keep Trellis execution state local.
|
||||
!/.trellis/
|
||||
/.trellis/*
|
||||
!/.trellis/spec/
|
||||
!/.trellis/spec/**
|
||||
/.agents/skills/trellis-*/
|
||||
/AGENTS.override.md
|
||||
|
||||
# Local report-generation skill
|
||||
.agents/skills/generate-skillhub-weekly-report/
|
||||
|
||||
|
|
|
|||
43
.trellis/spec/backend/api-contracts.md
Normal file
43
.trellis/spec/backend/api-contracts.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# API 与行为契约维护
|
||||
|
||||
适用后端、前端和 CLI 的公共接口变更;消费者可直接读本页,无需加载整个后端目录。
|
||||
具体端点、坐标、RBAC、版本标签和兼容语义以 [API设计](../../../docs/06-api-design.md)、
|
||||
[领域模型](../../../docs/02-domain-model.md)、[生命周期](../../../docs/14-skill-lifecycle.md)及相关 OpenSpec 为正式来源。
|
||||
不在 Trellis 复制端点/角色/状态表。
|
||||
|
||||
身份、会话、设备授权或 API token 变化追加[认证设计](../../../docs/03-authentication-design.md)及对应实现测试。
|
||||
token 持久化保留现有安全哈希机制,不退化为明文存储,不把展示前缀当作凭证本体。
|
||||
发布、上传、解压或包校验变化追加[包协议](../../../docs/07-skill-protocol.md),核对实际校验策略;
|
||||
不在此复制易过期的扩展名与大小限制表,拒绝路径和边界值应有测试。
|
||||
|
||||
## 工程约定
|
||||
|
||||
- DTO 放 app 的 dto 包,Controller 负责参数绑定、认证上下文与响应包装;业务规则由领域/应用服务承担。
|
||||
- 用户身份使用 String。客户端传入 owner/user 字段不等于已认证操作者,授权需使用可信身份上下文。
|
||||
- 普通 JSON 接口沿用 ApiResponse/ApiResponseFactory,文案通过现有本地化机制生成;流式文件和兼容接口按已有协议处理,不能强行套壳。
|
||||
- 分页字段、页码基数、排序、空值、缺省值与可选字段逐项核对端点和消费者;不凭框架默认值推导公共协议。
|
||||
- 参数格式、权限与领域前置状态分别校验;HTTP 错误处理见 [错误专题](./error-handling.md)。
|
||||
- 同一修改说明成功后的状态、失败后的状态、可重试条件和副作用;取消 HTTP 等待不自动意味着服务端事务取消。
|
||||
|
||||
## 身份与兼容边界
|
||||
|
||||
Session/CSRF 行为核对 [SecurityConfig](../../../server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java) 与相应认证链。
|
||||
浏览器会话使用的 XSRF-TOKEN/X-XSRF-TOKEN 与 token/兼容接口的例外不能混为一条“所有写请求都带 CSRF”。
|
||||
Mock 身份仅按现有开发 profile 使用,不替代真实认证验收。
|
||||
命名空间 slug、二轴坐标到 ClawHub 单 slug 的解析、冲突优先级和 well-known discovery 变更时,同时检查 compat 与 CLI 消费方。
|
||||
不要根据 namespace 名称前缀猜测类型或权限。
|
||||
|
||||
## 契约变更验收
|
||||
|
||||
1. 明确受影响消费者、字段可选性、错误结构与兼容策略;不能因类型仍编译就认定行为兼容。
|
||||
2. 保持 Controller/OpenAPI 注解和 DTO 一致;从正确运行中的后端执行 `make generate-api`,检查生成 diff 并随契约提交,勿手改生成类型。
|
||||
3. 选择 HTTP/消费方测试验证实际响应和失败路径;至少覆盖此次变化触及的身份/权限边界。
|
||||
4. [生成漂移脚本](../../../scripts/check-openapi-generated.sh) 会启动依赖并在退出时执行 Compose down;仅在隔离、可清理的本地环境运行,不能作为无副作用的文档检查。
|
||||
|
||||
## 已知文档漂移
|
||||
|
||||
2026-10-15 静态核查:docs/14 的撤回描述仍为 DRAFT,而
|
||||
[withdrawPendingVersion 实现](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java) 设置 UPLOADED。
|
||||
旧工程 Skill 的状态表也不是新的权威。修改相关功能时对照
|
||||
[治理测试](../../../server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceServiceTest.java)和正式需求解决差异;本次迁移不裁定新业务状态。
|
||||
发布审核绕过、可见性、latest 指针、警告确认、删除约束均须核对当前服务与测试,不从历史快照复制规则。
|
||||
29
.trellis/spec/backend/database-guidelines.md
Normal file
29
.trellis/spec/backend/database-guidelines.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# 数据库、查询与迁移
|
||||
|
||||
适用 PostgreSQL/JPA/Flyway 代码。数据模型见 [领域模型](../../../docs/02-domain-model.md),
|
||||
数据库配置及版本核对实际构建和部署配置。
|
||||
|
||||
## 仓储与读模型
|
||||
|
||||
- 聚合读取、状态迁移和规则判断用 domain 仓储接口,由 infra 实现。
|
||||
- 页面列表、join、DTO 投影用 app 查询仓储;薄的单聚合读取不必新增查询抽象。
|
||||
- 必要的直接 SQL/EntityManager 在类注释解释原因,参数绑定避免拼接用户输入。
|
||||
- 修改分页、排序、过滤或计数时同时检查数据查询与 total 查询,以及权限过滤;关注稳定排序、空结果、同值排序与跨页重复。
|
||||
- 数据库特性、索引和锁行为用真实 PostgreSQL 验证,mock 返回成功不证明 SQL 可执行。
|
||||
|
||||
## 迁移约定
|
||||
|
||||
结构变更使用 [Flyway迁移](../../../server/skillhub-app/src/main/resources/db/migration/)。
|
||||
已发布迁移不靠改旧文件纠正;新增迁移需考虑现有数据、默认值、回填、约束和新旧应用兼容。
|
||||
新建库成功不能证明存量升级成功;影响已有数据时验证代表性旧数据升级。
|
||||
用户身份列保持字符串语义。结构完整性与多变的业务政策分开:新增 CHECK 等约束需说明稳定性、迁移代价及与应用规则的关系,不能把全部工作流写死在 SQL 中。
|
||||
|
||||
变更查询前定位实际慢点与调用频率,检查 N+1、查询范围和必要索引;性能结论提供代表性数据与执行计划,不能从“加了索引”直接推导收益。
|
||||
涉及缓存时说明权威数据来源、失效触发和身份/命名空间隔离,不以缓存替代授权。
|
||||
|
||||
## 验证与边界
|
||||
|
||||
状态竞争、事务回滚见 [事务与并发](./transactions-and-concurrency.md)。
|
||||
迁移发布顺序和回退限制读 [部署规范](../deployment/index.md);测试方法读 [后端测试](../testing/backend-testing.md)。
|
||||
例证:[查询实现](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/repository/JpaMySkillQueryRepository.java)、
|
||||
[查询测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/repository/JpaMySkillQueryRepositoryTest.java)。
|
||||
35
.trellis/spec/backend/directory-structure.md
Normal file
35
.trellis/spec/backend/directory-structure.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 模块与目录约定
|
||||
|
||||
适用类归属、模块依赖和读写模型划分。架构目的见 [系统架构](../../../docs/01-system-architecture.md)。
|
||||
|
||||
| 职责 | 模块及包 |
|
||||
|---|---|
|
||||
| 实体、值对象、领域仓储接口 | `server/skillhub-domain`,`domain/{submodule}` |
|
||||
| 领域服务、事件、本地化异常 | `domain/{submodule}/service`、`domain/event`、`domain/shared/exception` |
|
||||
| 持久化适配器 | `server/skillhub-infra`,`infra/jpa`、`infra/repository`(跟随现有实现) |
|
||||
| Controller、DTO、应用服务 | `server/skillhub-app`,`controller`、`dto`、`service` |
|
||||
| 展示投影、跨来源查询 | `skillhub-app`,`repository/*QueryRepository` |
|
||||
| 认证授权、搜索、存储、通知 | 对应 `skillhub-auth/search/storage/notification` 模块 |
|
||||
|
||||
包前缀为 `com.iflytek.skillhub`。沿现有业务子域命名,不按表格批量移动存量代码。
|
||||
包应通过 `package-info.java` 说明职责;新增包补齐,存量缺失不借本次改动批量补写或搬迁。
|
||||
|
||||
## 依赖与职责
|
||||
|
||||
设计方向为 app 装配各模块,infra 实现 domain 仓储接口,auth/search/notification 使用领域模型。
|
||||
Controller 绑定参数、提取可信身份、包装响应,不直接做领域状态迁移。
|
||||
应用服务表达用例编排;领域服务返回领域对象,不返回 app DTO。
|
||||
复杂 join、快照与展示投影放查询仓储;简单单聚合读取可以直接调用领域仓储,避免空壳转发层。
|
||||
领域事件用于已有跨模块副作用协作;是否提交后处理、是否需要持久化投递,读 [事务专题](./transactions-and-concurrency.md),不能仅靠事件名称判断可靠性。
|
||||
|
||||
## 已知偏差与审查
|
||||
|
||||
[domain POM](../../../server/skillhub-domain/pom.xml) 仍依赖 storage;这是与理想内层隔离的存量偏差。
|
||||
本地修改不顺带修复整个架构,也不据此继续增加 domain 到 infra/auth/search/storage 的依赖。
|
||||
新增跨模块访问检查 POM 和 import;业务确需改变边界时走设计评审并记录原因。
|
||||
|
||||
用户身份在认证、API、权限、审计与 owner/reviewer/actor 字段保持 String,依据 [领域模型](../../../docs/02-domain-model.md)。
|
||||
不要推广为所有资源 ID 都是 String。
|
||||
|
||||
例证:[查询仓储](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/repository/JpaMySkillQueryRepository.java)、
|
||||
[领域仓储适配器](../../../server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/JpaSkillStorageDeletionCompensationRepositoryAdapter.java)。
|
||||
36
.trellis/spec/backend/error-handling.md
Normal file
36
.trellis/spec/backend/error-handling.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# 错误处理
|
||||
|
||||
适用领域失败、HTTP 映射和面向用户的错误信息。
|
||||
|
||||
- 领域失败沿用 `LocalizedDomainException` 及现有 bad-request/not-found/forbidden 子类,携带消息键与安全参数;不要在领域层返回 HTTP DTO。
|
||||
- 认证链、Controller advice 和兼容层各按已有协议处理,不能假设所有错误必然经过同一个 advice。
|
||||
- 复用 ApiResponseFactory 和现有消息资源。面向用户的业务提示可本地化,内部异常详情不直接拼进响应。
|
||||
- 不用异常文案匹配来新增业务分类。发现存量特殊兼容处理时保持其适用范围,避免扩展成通用规则。
|
||||
- 错误响应验证 HTTP 状态、响应结构、消息与失败后数据状态;只断言“抛了异常”不足以证明事务回滚。
|
||||
- 不吞掉基础设施失败并返回业务成功;是否重试取决于错误类别及重复执行安全性。
|
||||
|
||||
当前统一 handler 将乐观锁冲突映射为 409、对象存储访问故障映射为 503,并隐藏内部错误细节。
|
||||
这些是实现锚点,不代表所有失败都应转换成同一状态码。
|
||||
|
||||
来源:[异常实现](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/exception/GlobalExceptionHandler.java)、
|
||||
[响应与脱敏断言](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/exception/GlobalExceptionHandlerTest.java)、
|
||||
[领域异常](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/shared/exception/LocalizedDomainException.java)。
|
||||
|
||||
## 场景:新增失败分类或修改统一异常处理
|
||||
|
||||
先定位失败属于领域、MVC 参数绑定、认证过滤器还是对象存储边界,再选现有异常与 handler。
|
||||
例如对象存储读取失败应检查 `handleStorageAccess`;不能在下载服务捕获后返回空 ZIP 并声称成功。
|
||||
|
||||
| 输入/场景 | 当前可观察契约 | 回归锚点(GlobalExceptionHandlerTest) |
|
||||
|---|---|---|
|
||||
| `ObjectOptimisticLockingFailureException` | HTTP 409、响应 `code=409`、本地化冲突提示 | `persistenceConflictsReturn409WithoutLeakingDatabaseDetails` |
|
||||
| `StorageAccessException` | HTTP 503;日志带 requestId 和认证状态,不输出稳定 userId | `handleStorageAccess_shouldLogAuthenticationWithoutStableUserId` |
|
||||
| MVC 缺参、不可读请求体、类型不匹配 | HTTP 400、ApiResponse `code=400`;不记作未处理内部故障 | `handleMvcBadRequest_shouldReturn400WithoutUnhandledErrorLog` |
|
||||
| 不支持的 HTTP 方法 | HTTP 405、ApiResponse `code=405`,保留 Allow 响应头 | `handleMethodNotAllowed_shouldReturn405AndAllowHeader` |
|
||||
|
||||
反例:用 `exception.getMessage().contains(...)` 为新业务规则分配状态码;只断言状态而遗漏协议头;
|
||||
把原始存储异常文本直接发给客户端。正确做法是沿用有类型的异常、现有消息键与响应工厂,
|
||||
在对应测试中同时断言状态、响应字段及敏感信息边界。
|
||||
|
||||
验收时执行 [后端测试入口](../testing/backend-testing.md) 对应测试;修改过滤器链或真实 HTTP 协议时追加 Controller/安全测试。
|
||||
上表是当前单测覆盖范围,不证明事务回滚、真实存储可用性或所有入口均经过该 handler。
|
||||
40
.trellis/spec/backend/index.md
Normal file
40
.trellis/spec/backend/index.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# 后端规范入口
|
||||
|
||||
适用 Java/Maven 后端及 Python Scanner 本体。先读本页,再按改动选择专题;工程规范正文在本目录,不依赖工程 Skill 自动触发。
|
||||
具体业务需求、权限和状态机归正式 docs/OpenSpec;规范整理不授权改变业务行为。
|
||||
|
||||
## 日常约定
|
||||
|
||||
- Controller 负责传输边界,应用服务协调流程,领域服务维护业务不变量;复杂展示查询交给 app 查询仓储。
|
||||
- 用户身份全链路使用 `String`;不要把技能或命名空间的数值主键类型套到用户身份。
|
||||
- 复用现有仓储、响应工厂、本地化异常与请求追踪,不另造同义基础设施。
|
||||
- 参数合法性、授权、领域状态、数据库完整性是不同边界,各自维护所负责的检查。
|
||||
- 失败、并发、外部副作用与成功路径一起设计;不以加了事务注解证明跨服务原子性。
|
||||
|
||||
## 按需读取
|
||||
|
||||
| 触发条件 | 专题 |
|
||||
|---|---|
|
||||
| 新类、模块、依赖或读模型 | [模块边界](./directory-structure.md) |
|
||||
| 仓储、查询、迁移与性能 | [数据库](./database-guidelines.md) |
|
||||
| HTTP、鉴权上下文、API消费方、生成类型 | [接口契约](./api-contracts.md) |
|
||||
| 状态变更、重复请求、竞争或外部副作用 | [事务与并发](./transactions-and-concurrency.md) |
|
||||
| 异常与响应错误 | [错误处理](./error-handling.md) |
|
||||
| 发布、下载、删除、授权行为变化,或日志、审计、追踪 | [日志](./logging-guidelines.md) |
|
||||
| 扫描、队列、超时、重试或 Python Scanner 本体 | [扫描集成](./scanning-integration.md) |
|
||||
| 实现与审查收尾 | [后端质量](./quality-guidelines.md)、[测试入口](../testing/index.md) |
|
||||
|
||||
生命周期修改读 [正式生命周期文档](../../../docs/14-skill-lifecycle.md) 及对应源码、测试;
|
||||
API、坐标、权限修改读 [API设计](../../../docs/06-api-design.md)、[领域模型](../../../docs/02-domain-model.md)、[认证授权](../../../docs/03-authentication-design.md)。
|
||||
发现文档与实现漂移时列出差异,不能按旧表机械改写代码;已知漂移见接口专题。
|
||||
版本和模块清单以 [父 POM](../../../server/pom.xml) 为准,不将某次版本快照写成升级授权。
|
||||
|
||||
## 业务与基础设施路由
|
||||
|
||||
| 改动场景 | 修改前读取与核对 |
|
||||
|---|---|
|
||||
| 搜索条件、索引更新、搜索引擎替换 | [搜索设计](../../../docs/04-search-architecture.md)、[SearchQueryService](../../../server/skillhub-search/src/main/java/com/iflytek/skillhub/search/SearchQueryService.java)、[SearchIndexService](../../../server/skillhub-search/src/main/java/com/iflytek/skillhub/search/SearchIndexService.java);分别定位查询与索引端口,不将某个引擎的 SDK 泄漏到业务调用方 |
|
||||
| 对象存储提供方、上传下载行为 | [架构设计](../../../docs/01-system-architecture.md)、[ObjectStorageService](../../../server/skillhub-storage/src/main/java/com/iflytek/skillhub/storage/ObjectStorageService.java)、[错误处理](./error-handling.md);先核对端口方法、现有实现和调用方,再判断是配置变化还是接口变化 |
|
||||
| 发布、审核、下载等跨服务流程 | [业务流程](../../../docs/05-business-flows.md)、[事务与并发](./transactions-and-concurrency.md)、[日志与审计](./logging-guidelines.md);列出实际调用链的数据库写入、外部副作用与失败返回 |
|
||||
|
||||
上述是读取触发条件,不表示现有提供方已完成互换验证。涉及替换时按受影响行为验证,不凭“兼容协议”跳过验证。
|
||||
35
.trellis/spec/backend/logging-guidelines.md
Normal file
35
.trellis/spec/backend/logging-guidelines.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 日志、请求追踪与审计
|
||||
|
||||
适用新增日志、异常日志、异步任务与审计字段。
|
||||
|
||||
- 使用 SLF4J 参数化日志和既有上下文,记录能定位操作的必要字段。业务事件通常 INFO、调试 DEBUG;预期拒绝不必 ERROR,真正故障保留必要原因。
|
||||
- 沿用 RequestIdAccessor/RequestIdFilter 的校验、作用域与清理机制;不要直接信任任意请求头,也不要让线程复用残留上一请求上下文。
|
||||
- token、密码、Cookie、OAuth secret、私钥和原始敏感请求体不进入日志;查询串、异常文本、对象 toString 同样可能泄露。
|
||||
- 复用适用的脱敏工具并测试新增敏感字段;现有过滤器只覆盖已知字段,存在过滤器不证明所有输入已安全。
|
||||
- 区分故障排查日志与业务审计。审计沿既有 AuditLogService 传递可信操作者和必要目标,不能靠普通 INFO 日志替代正式审计。
|
||||
- 循环、批处理和重试避免逐条噪声;保留任务标识、失败阶段与重试状态,不记录完整包内容或凭证。
|
||||
|
||||
修改追踪验证无效 ID、正常响应和异常后的作用域释放;修改敏感字段日志用包含假凭证的测试输入断言其未输出。
|
||||
|
||||
来源:[请求ID实现](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/filter/RequestIdFilter.java)、
|
||||
[请求ID测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/filter/RequestIdFilterTest.java)、
|
||||
[请求日志实现](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/filter/RequestLoggingFilter.java)、
|
||||
[请求日志测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/filter/RequestLoggingFilterTest.java)。
|
||||
|
||||
## 场景:关键业务操作的审计随重构丢失
|
||||
|
||||
修改发布、下载、删除、授权及审核流程时,即使没有改日志代码,也必须检查审计链路。
|
||||
先在旧调用链定位事件生产位置,记录 action、目标类型/ID、可信操作者、请求上下文以及记录发生在成功还是失败分支;
|
||||
新调用链必须保留所需语义。匿名下载等场景应遵循原契约,不能自行伪造用户身份。
|
||||
找不到原记录或业务文档与实现不一致时报告缺口,不能宣称已有覆盖,也不借规范整理擅自新增业务政策。
|
||||
|
||||
具体参考:[PromotionPortalAppServiceTest](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/service/PromotionPortalAppServiceTest.java)
|
||||
的 `approvePromotion_recordsSelfReviewAuditDetailForSuperAdminSelfApproval`:除了调用成功,
|
||||
还验证 `PROMOTION_APPROVE`、`PROMOTION_REQUEST`、目标 ID、操作者与 `selfReview` 明细。
|
||||
[AuditLogService](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/audit/AuditLogService.java)
|
||||
及其 [测试](../../../server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/audit/AuditLogServiceTest.java)
|
||||
是公共记录入口,不要在各服务自行拼接另一套审计持久化方式。
|
||||
|
||||
反例:重构后 HTTP 仍返回 200,因而删除旧审计调用;或用一条 `log.info("deleted")` 替代业务审计。
|
||||
验收:对受影响操作补/保留 action、目标、操作者与必要明细断言,检查拒绝/失败分支不会记录虚假的成功事件。
|
||||
mock 的 `record` 断言只能证明调用参数;改动审计持久化或事务时还需验证实际提交/回滚结果。
|
||||
23
.trellis/spec/backend/quality-guidelines.md
Normal file
23
.trellis/spec/backend/quality-guidelines.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# 后端质量与行为验收
|
||||
|
||||
本页定义检查什么;测试层次、fixture、真实依赖与运行方式见 [后端测试](../testing/backend-testing.md),
|
||||
交付门槛见 [测试入口](../testing/index.md)。
|
||||
|
||||
| 改动 | 应观察的结果 |
|
||||
|---|---|
|
||||
| 分层与模块 | 新依赖方向符合模块约定,领域不返回 app DTO,查询投影没有挤入编排服务 |
|
||||
| API/DTO/兼容协议 | 成功与错误结构、空值/分页及消费者预期一致,生成类型没有未解释漂移 |
|
||||
| 授权/命名空间 | 允许场景成功;匿名、无权限、跨命名空间或伪造身份被正确拒绝且不产生写副作用 |
|
||||
| 状态机/事务 | 前后状态、版本指针和读模型符合正式契约;失败不留下不允许的中间状态 |
|
||||
| 并发/重复请求 | 竞争结果与契约一致,无重复副作用或旧状态覆盖;锁定后重新判断有效状态 |
|
||||
| 仓储/迁移 | 查询、排序、total与权限一致;实际数据库执行及存量升级得到验证 |
|
||||
| 外部服务/队列 | 失败和超时可观察;重试、终态、确认与清理顺序不会丢失必要恢复信息 |
|
||||
| 日志/异常 | 请求可关联,敏感信息不输出,错误映射与消息资源匹配 |
|
||||
|
||||
## 项目经验:Maven 多模块旧缓存
|
||||
|
||||
仅在 app 下执行 `./mvnw -pl skillhub-app clean test` 可能使用本地仓库中陈旧的依赖产物,出现误导性的符号或签名不匹配。
|
||||
使用仓库 `make test-backend-app`(含依赖模块)或更广的 `make test-backend`;故障排查先确认实际构建模块,不能通过改业务代码迎合旧产物。
|
||||
命令实现以 [Makefile](../../../Makefile) 为准。
|
||||
|
||||
单测通过、真实依赖集成通过、环境健康与生产验收分别报告;静态阅读测试文件不算运行测试。
|
||||
59
.trellis/spec/backend/scanning-integration.md
Normal file
59
.trellis/spec/backend/scanning-integration.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# 扫描与队列集成
|
||||
|
||||
适用 Java 后端与扫描服务、Redis Stream、对象存储之间的协议与失败处理;
|
||||
部署步骤和配置详情引用 [Scanner文档](../../../scanner/README.md),不复制运行手册或固定重试次数。
|
||||
|
||||
## 工程边界
|
||||
|
||||
- 领域扫描服务协调扫描状态;outbox dispatcher 发布任务;consumer 处理投递并调用 scanner 适配器。
|
||||
- 任务持久化、投递、业务完成和消息确认是不同步骤。发送成功不能证明扫描完成,超时也不能证明远端没有执行。
|
||||
- 保持任务/版本身份、原始时间和重试信息在重投递中可追踪;不能每次重新计时导致恢复窗口无限延长。
|
||||
- 区分暂时不可用、可重试失败、终态失败与重复消息;策略依据现有代码和配置,不统一“失败立即确认”或“永远重试”。
|
||||
- 包下载、临时文件和扫描目录使用现有路径安全边界;清理只针对本次拥有的文件,不能误删并行扫描仍在使用的目录。
|
||||
|
||||
## 应保留的行为
|
||||
|
||||
[consumer](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/stream/ScanTaskConsumer.java)
|
||||
存在任务锁与 pending 恢复逻辑;锁被占用时保留原投递,避免重复消费抹去唯一持久任务。
|
||||
扫描暂不可用和超过恢复窗口采用不同路径。失败已记录但确认失败后的重投递不能重复做业务失败处理。
|
||||
[outbox dispatcher](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/task/ScanTaskOutboxDispatcher.java)
|
||||
使用 claim、发送标记和退避;修改需要检查租约、发送与标记间失败以及多实例竞争。
|
||||
|
||||
## 验证锚点
|
||||
|
||||
[消费测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/stream/ScanTaskConsumerTest.java)
|
||||
覆盖扫描不可用、锁占用、恢复窗口与确认失败后的重投递;
|
||||
[路径安全测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/stream/ScanTaskConsumerPathSafetyTest.java)
|
||||
覆盖临时路径边界;
|
||||
[outbox测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/task/ScanTaskOutboxDispatcherTest.java)
|
||||
用于检查调度行为。
|
||||
这些是回归锚点,不意味着本次已运行或真实 Redis/Scanner 链路已验收。
|
||||
|
||||
## Python Scanner 本体
|
||||
|
||||
修改 [skillhub_scanner_app.py](../../../scanner/skillhub_scanner_app.py) 的并发限制、取消、超时、响应脱敏或临时目录时,
|
||||
先读 [本体测试](../../../scanner/tests/test_skillhub_scanner_app.py),不只运行 Java consumer 测试。
|
||||
|
||||
| 场景 | 当前行为与可观察结果 | 测试名 |
|
||||
|---|---|---|
|
||||
| 扫描容量已满 | 新请求返回 503,`Retry-After: 30`,不启动额外扫描 | `test_excess_scan_is_rejected` |
|
||||
| 客户端取消,但底层扫描尚未结束 | 容量仍被占用,新请求仍拒绝;原扫描结束后释放容量 | `test_client_disconnect_keeps_slot_until_scan_finishes` |
|
||||
| 扫描超过硬超时 | 调用重启处理;真实实现记录 critical、关闭日志并退出进程 | `test_hard_timeout_requests_process_restart`(替换重启函数,未真正退出进程) |
|
||||
| 响应 findings 含敏感文本 | 脱敏但保留原响应契约与正常文本 | `test_scan_findings_redact_credentials_without_changing_response_contract`、`test_safe_scan_findings_are_unchanged` |
|
||||
| 初始化临时目录 | 仅重建私有运行目录,不清理父目录 | `test_import_recreates_private_runtime_temp_root_without_touching_parent` |
|
||||
|
||||
反例:HTTP 请求一取消就释放并发槽位,但底层线程仍扫描;再次重试会绕过容量限制。
|
||||
沿用现有 shield/等待与 finally 释放路径;不要将客户端断开等同于计算已停止。
|
||||
|
||||
日志使用现有 `logging.getLogger(__name__)` 与参数化消息,新增日志不得包含 API key、上传包全文或未脱敏 findings。
|
||||
当前包装层源码未配置 JSON formatter;不能把“结构化 JSON 日志”写成已具备的保证。
|
||||
日志格式或容器退出恢复变化还需检查 [监控手册](../../../scanner/docs/monitoring-guide.md) 和运行配置。
|
||||
|
||||
仓库根目录运行现有 [CI](../../../.github/workflows/pr-scripts.yml) 使用的命令:
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s scanner/tests -p 'test_*.py'
|
||||
```
|
||||
|
||||
期望所选用例全部通过。此套测试用替身隔离上游服务,不能证明 Cisco 扫描引擎、真实 HTTP 断连、容器重启或 Redis 链路已验收;
|
||||
这些边界发生变化时按 [部署规范](../deployment/index.md) 补环境验证。
|
||||
34
.trellis/spec/backend/transactions-and-concurrency.md
Normal file
34
.trellis/spec/backend/transactions-and-concurrency.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# 事务、并发与外部副作用
|
||||
|
||||
适用状态迁移、批量写入、重复请求、异步处理及对象存储/消息副作用。
|
||||
本页要求显式分析契约,不给所有接口增加幂等键、分布式锁或统一重试。
|
||||
|
||||
## 修改前确定边界
|
||||
|
||||
- 列出必须共同提交的数据以及可延迟的副作用,确定事务真正由哪个 Spring 代理调用进入。
|
||||
- 明确前置校验到写入之间是否存在竞争;预检查不代替锁定后或条件更新时的状态校验。
|
||||
- 对重复提交说明复用结果、拒绝冲突还是允许重复;根据现有业务契约选择机制,不能自行改变产品语义。
|
||||
- 定义失败后的可观察状态:回滚哪些数据、已经发出的消息或上传文件由谁补偿、如何发现和恢复。
|
||||
- 取消请求/超时只表示一段等待结束,不能推断数据库或外部系统已经撤销操作。
|
||||
|
||||
## 现有实现模式及适用范围
|
||||
|
||||
[扫描重试服务](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SecurityScanRetryAppService.java)
|
||||
先做标量状态预检查,再锁定版本并重判;有活动扫描时复用响应。这个模式只证明该用例的处理,不意味着全部 API 幂等。
|
||||
[并发回归测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SecurityScanRetryLockingTest.java)
|
||||
用两个事务验证等待锁后看见已提交新状态,避免持久化上下文中的预加载旧实体误导决策。
|
||||
复用经验时检查 ORM 缓存、锁顺序和超时,不只看有无 `@Transactional`。
|
||||
|
||||
[版本治理](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java)
|
||||
将对象删除放在提交后,失败交给补偿;数据库回滚不能自动恢复外部删除。
|
||||
[补偿服务](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillStorageDeletionCompensationService.java)
|
||||
区分普通清理失败与发布回滚后的独立事务记录。修改时检查实际代理调用与提交时序,不把“记录补偿”视为已可靠持久化的证明。
|
||||
|
||||
## 验证要求
|
||||
|
||||
- 竞争测试控制交错顺序并断言最终持久状态;单线程 mock 不证明真实锁语义。
|
||||
- 在事务提交、外部调用失败、重投递等关键边界注入失败,验证无丢失/重复副作用及补偿可达。
|
||||
- 幂等范围若涉及任务ID、版本或请求键,验证同键与不同键,不以请求次数推定逻辑执行次数。
|
||||
- 检查拒绝/回滚时事件、审核记录、指针与对象文件的一致性;正式业务预期引用生命周期文档并核对其漂移。
|
||||
|
||||
测试设计见 [后端测试](../testing/backend-testing.md),队列细节见 [扫描集成](./scanning-integration.md)。
|
||||
64
.trellis/spec/cli/index.md
Normal file
64
.trellis/spec/cli/index.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# CLI 工程规范
|
||||
|
||||
修改 `cli/` 的命令、安装、配置或凭证时读取。CLI 使用自己的 Bun 工具链和 Node 运行产物,
|
||||
不套用 Web 的 pnpm、React 规则。命令说明见 [CLI README](../../../cli/README.md),
|
||||
工具和构建入口见 [package.json](../../../cli/package.json)与[Makefile](../../../Makefile)。
|
||||
|
||||
## 分层与输出契约
|
||||
|
||||
- 命令负责参数和展示,业务编排复用 services,持久化使用 stores,系统能力使用 platform;
|
||||
新行为先查对应模块和测试,不在命令中复制下载、凭证或安装逻辑。
|
||||
- 输出走现有 [output.ts](../../../cli/src/shared/output.ts) 与 [CliError](../../../cli/src/shared/errors.ts)。
|
||||
人类格式与 `--json` 是不同消费契约;JSON 模式不能混入提示/进度导致无法解析。
|
||||
- 保持各命令 stdout/stderr 和退出码语义。参数解析失败的既有契约为 stderr、退出码 5;
|
||||
其他失败从现有错误映射核对,不擅自把所有失败归为 1。
|
||||
- 错误区分参数、认证、服务响应、网络和本地文件失败;只输出安全的上下文和可执行下一步,
|
||||
不泄露 token、完整凭证或内部堆栈。服务 requestId 存在时沿现有字段传递。
|
||||
- 交互判断复用 [tty.ts](../../../cli/src/shared/tty.ts):stdin/stdout 都是 TTY 且非 JSON。
|
||||
无人值守时按命令契约要求显式参数或安全失败,不能偷偷选择破坏性默认值。
|
||||
|
||||
## 配置、身份与本地状态
|
||||
|
||||
[registry-service.ts](../../../cli/src/services/registry-service.ts) 定义 registry 优先级:
|
||||
显式参数 → `SKILLHUB_REGISTRY` → config → 内置默认;token 为显式参数 → `SKILLHUB_TOKEN` → 对应 registry 的存储凭证。
|
||||
修改时同步子进程集成测试,不只测 resolver。不同 registry 凭证不能串用;登出只移除目标凭证,
|
||||
不删除其他 registry、配置或安装记录。配置写入保留不属于本次更新的字段。
|
||||
|
||||
安装、升级、删除、同步修改需明确源身份、目标目录、已有用户修改和失败后状态。
|
||||
复用现有目标锁、inventory 和 archive 能力;并发操作不得丢失另一安装的记录,
|
||||
中途失败不能伪报完成或遗留与实际文件不符的成功状态。具体 overwrite/force 行为以命令正式契约为准,
|
||||
不在规范迁移时改变用户数据策略。
|
||||
|
||||
## 测试与验收
|
||||
|
||||
CLI 行为或工具链变化运行 `make lint-cli`、`make typecheck-cli`、`make test-cli`、`make build-cli`;
|
||||
仅帮助显示文案按[测试矩阵](../testing/index.md)执行相关输出测试,文档修改检查来源与链接。
|
||||
CI 另做多平台和 Node 产物运行,其实际必需检查不因本地按影响选测而豁免。
|
||||
单元测试覆盖规则,子进程测试同时断言退出码、stdout/stderr、目标文件和状态记录。
|
||||
复用 [临时环境](../../../cli/test/helpers/temp-env.ts)、[进程 helper](../../../cli/test/helpers/run-cli.ts)
|
||||
及 fake registry,隔离 home/cwd/凭证/宿主 `SKILLHUB_*` 环境;清理本次资源,测试不调用真实桌面应用或发布服务。
|
||||
|
||||
回归锚点:[错误输出](../../../cli/test/integration/error-output.test.ts)、
|
||||
[身份优先级](../../../cli/test/integration/auth-resolution.test.ts)、
|
||||
[多进程并发](../../../cli/test/integration/concurrency.test.ts)。mock registry 的通过仅证明 CLI 消费契约,
|
||||
不能标为真实服务端验收。公共 API 变化追加[后端接口契约](../backend/api-contracts.md)和[测试入口](../testing/index.md)。
|
||||
|
||||
### 例:修改参数错误或身份配置解析
|
||||
|
||||
| 改动 | 现有代表用例与结果 | 容易漏掉的验证 |
|
||||
|---|---|---|
|
||||
| 未知命令的 JSON 输出 | `runCli(['foo', '--json'])`:退出码 `5`,stderr 可解析为 `{ok: false, message: 'unknown command "foo" for "skillhub"', exitCode: 5}` | 只匹配错误文字会漏掉 JSON 前混入提示或退出码被改成 `0` |
|
||||
| token 优先级 | fake registry 仅接受显式参数的假 token,环境变量放另一假值;`whoami` 应成功返回该 registry 的用户 | 只测 resolver,不能证明命令将参数传入了 resolver |
|
||||
| registry 优先级 | 两个 fake registry 使用不同用户和 token;显式 registry 胜过环境变量,环境变量胜过 config | 不同 registry 返回相同 fixture 会掩盖请求发往错误服务的问题 |
|
||||
|
||||
上表来自链接的集成测试;新增 stdout 纯净性等断言时另行实现,不把表格当成所有检查都已有覆盖。
|
||||
在已按 CLI 工具链安装依赖的环境,从仓库根目录执行局部反馈:
|
||||
|
||||
```bash
|
||||
cd cli
|
||||
bun run test test/integration/error-output.test.ts test/integration/auth-resolution.test.ts
|
||||
```
|
||||
|
||||
`bun run test` 保留 package.json 的 pretest 元数据生成步骤;测试会派生本地 CLI 进程、
|
||||
启动本地 fake HTTP registry 并使用临时配置,不应访问真实 registry。
|
||||
此命令验证这两种行为,不能代替 CLI 行为改动所需的完整 lint/typecheck/test/build。
|
||||
117
.trellis/spec/deployment/index.md
Normal file
117
.trellis/spec/deployment/index.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# 部署与运行规范
|
||||
|
||||
Bootstrap 管理员用于首次启动引导,不作为共享交付凭证;交付检查初始化开关、凭证来源与首次初始化后的访问控制,
|
||||
配置字段及行为核对[认证设计](../../../docs/03-authentication-design.md)和当前启动实现,不复制默认口令。
|
||||
|
||||
## 适用范围
|
||||
|
||||
修改容器、Compose、Kubernetes、镜像发布、运行配置、健康检查或运维接入时读取。
|
||||
这是工程规则入口,不是执行部署的授权;只读分析不启动服务、不发布镜像。
|
||||
|
||||
## 通用原则
|
||||
|
||||
- 区分开发、回归、交付环境,先识别目标、现有服务和数据卷,避免影响无关环境。
|
||||
- 配置由环境或现有配置机制注入;示例与真实凭证分离,日志和报告不输出密钥。
|
||||
- 发布产物应可追溯到版本或提交;配置、数据迁移和回退能力需要分别检查。
|
||||
- 构建成功、配置校验、服务健康、完整业务验收是不同证据,不能相互替代。
|
||||
- 运行前读实际脚本与变量来源;不凭文档示例猜测命令参数、默认地址或当前环境状态。
|
||||
|
||||
## SkillHub 约定
|
||||
|
||||
- 开发入口是 `make dev-all`;正式单机交付参考 `compose.release.yml` 和部署手册。
|
||||
开发能力与交付能力分开,交付环境不得误开启本地 mock 认证旁路。
|
||||
- 发布配置检查入口为 `make validate-release-config`,读取 `.env.release`;缺少配置时报告缺项,
|
||||
不为了让检查通过写入真实凭证或输出配置文件内容。校验脚本部分错误可能带出配置值,
|
||||
保存和分享输出前先脱敏,不能因命令名含 validate 就认为输出安全。
|
||||
- 镜像与 Helm 发布行为查 `.github/workflows/publish-images.yml`、`publish-chart.yml`;
|
||||
标签、架构与触发条件以当前工作流为准。发布和部署仍需要任务已有的相应授权。
|
||||
- Kubernetes 配置、存储和 Secret 的完整要求读对应手册,不在此复制参数表。
|
||||
- 配置变更检查应用绑定、默认值、环境透传和实际生效路径;文件中存在某个键不代表环境已经采用。
|
||||
- 数据迁移需说明备份、兼容性和回退限制;回退应用镜像不等于回退数据库。
|
||||
|
||||
## 条件性深读
|
||||
|
||||
| 条件 | 来源 |
|
||||
|---|---|
|
||||
| Compose、Profile、镜像标签、配置、上线与迁移 | [部署架构与运维](../../../docs/09-deployment.md) |
|
||||
| 开发或 Staging 回归环境 | [开发工作流](../../../docs/dev-workflow.md)、[Makefile](../../../Makefile) |
|
||||
| Kubernetes | [用户部署指南](../../../docs/skillhub/guide/kubernetes.md)、[清单说明](../../../deploy/k8s/README.md) |
|
||||
| 请求关联、日志、Trace 接入 | [可观测性开发者指南](../../../docs/observability-developer-guide.md) |
|
||||
| 扫描服务运行与失败语义 | [扫描运行指南](../../../docs/security-scanning.md) |
|
||||
| 真实部署验证与报告 | [测试与质量](../testing/index.md) |
|
||||
|
||||
## 需要核对的边界
|
||||
|
||||
部署文档对支持的运行方式有收敛描述,而 Makefile 与开发文档仍保留 `make staging`。
|
||||
先区分正式交付与回归验证,不据此自行删除 Staging,也不把它宣称为生产部署标准。
|
||||
|
||||
验证配置改动时检查配置校验、构建和相关 Smoke;操作实际环境前确认授权与影响范围。
|
||||
验证报告写明环境、版本、命令及实际观察结果;不能继承其他 worktree 或历史发布的通过结论。
|
||||
|
||||
## 配置与发布的验收契约
|
||||
|
||||
配置修改应追踪“声明 → 应用绑定/默认值 → Compose 或 Helm 透传 → 进程实际使用”。
|
||||
区分构建时与运行时变量;仅修改部署清单或模板不能证明浏览器和服务已经读取新值。
|
||||
缺省、空值、非法值、旧配置兼容按本次变化验证,敏感值只证明是否存在/生效,不打印内容。
|
||||
|
||||
发布前为受影响范围回答以下问题,并将操作步骤留在对应运行手册:
|
||||
|
||||
| 变化 | 需要明确的兼容与失败行为 | 验收证据 |
|
||||
|---|---|---|
|
||||
| 应用或镜像 | 新旧 API/CLI/前端能否共存;镜像与提交对应关系 | 实际运行版本与关键请求结果 |
|
||||
| 配置或 Secret | 默认值是否改变、是否需重启、旧配置是否仍可用 | 绑定和透传检查,启动后有效行为 |
|
||||
| 数据迁移 | 旧数据如何升级、失败阻止启动还是可恢复、旧应用是否读得懂新 schema | 隔离库升级测试、迁移记录与升级后业务断言 |
|
||||
| 异步任务/外部依赖 | 切换期间任务如何继续、重复执行如何处理、超时失败如何观察 | 受影响队列/任务状态和副作用,不只健康接口 |
|
||||
|
||||
Flyway 是 schema 变更入口,应用容器启动会执行迁移;具体规则见
|
||||
[数据库规范](../backend/database-guidelines.md)和[部署手册](../../../docs/09-deployment.md)。
|
||||
因此首次启动新应用本身可能修改数据库,不能把“先启动看看”当作无副作用预检。
|
||||
涉及不兼容字段或数据转换时,先明确应用/迁移顺序和兼容窗口;不能只写“回退镜像即可”。
|
||||
备份应有对应恢复方式和可验证性;恢复数据、补偿副作用、回退应用是不同操作,执行需在已有授权范围内。
|
||||
|
||||
## 分层验收与收尾
|
||||
|
||||
### 例:修改 Web 子路径配置
|
||||
|
||||
本例说明如何落实“配置声明到实际使用”的检查;不是新的部署默认值。
|
||||
涉及 `SKILLHUB_WEB_BASE_PATH`、同源 `SKILLHUB_WEB_API_BASE_URL` 或公开地址时,联读
|
||||
[校验脚本](../../../scripts/validate-release-config.sh)、[Compose 透传](../../../compose.release.yml)、
|
||||
[容器入口](../../../web/docker-entrypoint.d/20-base-path.sh)与[Nginx 模板](../../../web/nginx.conf.template)。
|
||||
|
||||
| 代表输入 | 应检查的结果 |
|
||||
|---|---|
|
||||
| Web base `/skillhub/`,同源 API base `/skillhub`,公开地址 `https://skillhub.example.com/skillhub` | 路径一致;生成带前缀的静态资源与 Nginx 路由 |
|
||||
| Web base `/skillhub/`,同源 API base `/other` | 配置校验及运行入口拒绝,不静默使用另一 API 路径 |
|
||||
| Web base `/api/` 或 `/foo/../bar/` | 拒绝保留前缀或点路径,避免遮蔽服务器路由或路径归一化后失配 |
|
||||
| 根路径 `/` | 不生成重复子路径 location;静态资源保持根路径 |
|
||||
|
||||
不要只改 Compose 加一个变量就判定完成;也不能仅校验字符串末尾相同就接受公开地址的路径。
|
||||
例如 `/other/skillhub` 与 `/skillhub` 不等价。独立 API 主机的绝对 URL 是允许的例外,不能套同源路径相等规则。
|
||||
|
||||
从仓库根目录运行已有代表检查:
|
||||
|
||||
```bash
|
||||
sh scripts/tests/web-base-path-routing-test.sh
|
||||
bash scripts/tests/validate-release-config-test.sh
|
||||
```
|
||||
|
||||
前者使用临时 HTML 和配置文件检查容器入口生成结果,不启动 Nginx;后者使用假配置,
|
||||
依赖 Docker Compose 的 `config` 渲染但不启动服务。它们均不是读取真实 `.env.release` 的交付校验,
|
||||
也不能证明浏览器、OAuth 回调或反向代理实际可用。
|
||||
需要容器 HTTP 验证时再读取[子路径 Smoke](../../../scripts/tests/web-base-path-nginx-smoke-test.sh),
|
||||
核对它的镜像、容器创建与清理范围后执行。
|
||||
|
||||
### 证据层次
|
||||
|
||||
1. 静态配置和构建:证明配置结构、产物可构建,未证明部署可用。
|
||||
2. 启动与健康:证明服务就绪、依赖连通,未证明权限和业务闭环。
|
||||
3. 真实业务 Smoke:检查受影响入口、读写、权限拒绝与结果状态;结合
|
||||
[E2E/Smoke 规范](../testing/e2e.md)隔离账号及数据。
|
||||
4. 记录环境、版本、执行命令、观察结果、未验内容;清理只针对本次创建的资源。
|
||||
|
||||
已有运行环境与端口必须先识别。不要按旧工作流建议直接 kill 占用端口的进程;先确认所有者和影响。
|
||||
`make clean`、`make dev-all-reset`、`make db-reset` 含卷或本地数据清理,不能用作常规失败重试。
|
||||
`make staging-down` 只清理对应栈且不代表获得删除共享资源的授权。
|
||||
|
||||
来源:[发布配置校验](../../../scripts/validate-release-config.sh)、
|
||||
[Compose 发布清单](../../../compose.release.yml)、[Makefile](../../../Makefile)。
|
||||
49
.trellis/spec/frontend/component-catalog.md
Normal file
49
.trellis/spec/frontend/component-catalog.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# 组件目录与选型
|
||||
|
||||
这份共享目录维护盘点方法、维护决策和候选分类;它不声明某个分支已安装或已通过验证。
|
||||
当前检出的具体组件表、官方差异与执行结果记录到当前任务,避免内部或旧分支快照随模板传播。
|
||||
开始组件工作前重新核对目录、组件 API、调用方和测试。
|
||||
|
||||
## 当前检出盘点格式
|
||||
|
||||
每个组件至少记录:
|
||||
|
||||
| 字段 | 要求 |
|
||||
|---|---|
|
||||
| 检出身份 | origin、工作树、分支、基线、核对日期;只保留在任务记录 |
|
||||
| 本地路径与用途 | 实际文件、导入方式、基础/业务组合归属 |
|
||||
| 公开 API | 源码中实际导出、原生属性、ref、变体、受控契约;不得编造官方属性 |
|
||||
| 底层与调用方 | 原生 HTML/Radix/自有实现、代表调用方与相关测试 |
|
||||
| 官方参考 | 当前适配来源及版本;区别参考和来源证明 |
|
||||
| 差异与结论 | 保留/调整/增加变体/按需补缺,写理由与必须保留的行为 |
|
||||
| 实现与验证 | 已存在、待适配、已检查、未验证;区分源码、工程检查与浏览器证据 |
|
||||
|
||||
常见盘点对象包括 Button、Input、Textarea、Label、Card、Table、Select、Dropdown Menu、
|
||||
Dialog、Tabs;数量以当前文件为准,不能从这份示例清单推断实际文件存在。
|
||||
Button 变体与 loading/asChild、Dialog 的底层、Tabs 的交互尤须读取源码。
|
||||
|
||||
业务无关组合按实际存在情况调查页头、空态、分页、确认、骨架、复制与通知;
|
||||
已有能力满足需求时保留,不因官方同名组件存在就另建一套。
|
||||
|
||||
## 官方参考与兼容性
|
||||
|
||||
官方组件目录:<https://ui.shadcn.com/docs/components>。
|
||||
配置:<https://ui.shadcn.com/docs/components-json>。
|
||||
Tailwind 迁移说明:<https://ui.shadcn.com/docs/tailwind-v4>。
|
||||
具体组件文档与注册表在使用时核对;示例链接不是当次 URL 存活或兼容性验证结果。
|
||||
保留当前合理定制,逐项检查生成目录、导入、依赖和动画插件;不直接复制不兼容的新栈示例。
|
||||
|
||||
## 按需候选
|
||||
|
||||
| 场景 | 可调查项 | 边界 |
|
||||
|---|---|---|
|
||||
| 表单组合 | Field、Input Group、Native Select | 先检查现有 Label/Input 组合和当前栈兼容性 |
|
||||
| 输入选择 | Combobox、Checkbox、Radio Group、Switch | 先查原生控件或已有业务能力,再判断是否需要新原语 |
|
||||
| 展示 | Badge、Avatar、Separator、Accordion、Collapsible | 不把候选写成可直接 import 的已安装组件 |
|
||||
| 数据表 | Data Table | 组合方案;优先现有 Table/分页,排序筛选按业务需求定义 |
|
||||
| 导航 | Breadcrumb、Navigation Menu、Sidebar、Command | 沿用 router 与已有导航,缺口成立才增加 |
|
||||
| 弹层 | Alert Dialog、Popover、Tooltip、Sheet、Drawer | 需要实际验证焦点、Portal 与底层兼容性 |
|
||||
| 反馈 | Alert、Spinner、Progress | 现有文字/图标/通知能满足时不另建体系 |
|
||||
| 专门需求 | Calendar、Date Picker、Chart、Resizable、Scroll Area | 真实需求成立再查依赖,不默认安装大型工具链 |
|
||||
|
||||
清单是调查入口,不代表对应版本、文档、注册表或依赖已经在当前任务验证。
|
||||
48
.trellis/spec/frontend/component-guidelines.md
Normal file
48
.trellis/spec/frontend/component-guidelines.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# 组件复用与定制
|
||||
|
||||
先读[视觉基础](./visual-foundations.md),再按需读[组件目录](./component-catalog.md)。
|
||||
读取当前检出适用的 AGENTS/CLAUDE、[前端入口](./index.md)、架构文档、源码与测试;不存在的文件
|
||||
不假称已读。不同检出的工程依赖或 API 有差异时先核对,不继承其他工作树的实现状态。
|
||||
|
||||
采用 shadcn/ui 源码体系,保留 React 19、Tailwind 3 与既有 Radix。
|
||||
“不使用 shadcn 运行时库”不等于禁止参考官方源码;不安装另一套整包 UI 体系。
|
||||
官方 shadcn Skill 提供 API/CLI 用法,项目规范提供设计与兼容边界。
|
||||
|
||||
## 选择与放置
|
||||
|
||||
选择顺序:已有 shared/ui、shared/components、业务组件 → 兼容官方源码 →
|
||||
布局组合 → 必要的共享变体或实现调整。图标复用 Lucide;合并用既有 cn;文案用 i18n。
|
||||
|
||||
| 需求 | 放置位置 |
|
||||
|---|---|
|
||||
| 单处宽度、排列、响应式 | 调用处 className |
|
||||
| 多处复用的语义外观或尺寸 | 检查既有变体,再扩展基础组件变体 |
|
||||
| 所有控件的默认样式、焦点、键盘 | shared/ui 实现,核对直接/间接调用方 |
|
||||
| 全站字体、主题、密度 | CSS 变量与 Tailwind 映射 |
|
||||
| 安装面板、技能卡片、业务权限 | 原业务目录,不塞进基础组件 |
|
||||
|
||||
例:单页面 `className="w-full sm:w-auto"` 合理。
|
||||
例:每页重写整套按钮颜色和焦点方案会破坏一致性,应使用已有变体。
|
||||
不要为文档树把已有效运行的业务组件搬到另一个 FSD 层。
|
||||
|
||||
## 官方组件适配
|
||||
|
||||
在当前 web 工程核对 CLI 实际版本和帮助,再执行 info 与代表组件 dry-run。
|
||||
安装版本、命令、路径、依赖预告和执行结果写入任务记录,不固定成模板的永久版本。
|
||||
核对 components/ui/utils/lib/hooks 与 TypeScript、Vite 别名,不能产生第二套 ui 目录。
|
||||
兼容 Tailwind 3 的来源逐项核对;修改 components.json style 不等于迁移已有组件。
|
||||
禁止无差别 init、批量 add 或覆盖定制源码。候选组件不意味着必须加依赖。
|
||||
|
||||
## 组件契约
|
||||
|
||||
保留原生属性、事件、ref、受控/非受控状态、className/style、标签、aria、表单语义、
|
||||
Portal、滚动与焦点。先检查实际 API,不能把官方或其他工作树的属性当作本地已提供。
|
||||
自有 Dialog/Tabs 即使外观相似,也不能宣称完整等价于 Radix。
|
||||
|
||||
Tabs 的 Tab 入口、方向键、Home/End、禁用项处理属于组件基础可用性。
|
||||
存在 enabled 项而无有效选中项时,应保留 enabled 键盘入口;入口恢复不能自动选择或
|
||||
触发业务回调。全部禁用时没有可操作入口。受控值、动态移除/禁用都需行为验证。
|
||||
这不要求增加全站快捷键功能。
|
||||
|
||||
弹层验证 Escape、Tab 范围、焦点返回和嵌套 Portal。表单验证标签、错误关联与提交语义。
|
||||
详细检查与证据规则见[验收规范](./quality-guidelines.md)。
|
||||
28
.trellis/spec/frontend/directory-structure.md
Normal file
28
.trellis/spec/frontend/directory-structure.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# 前端目录与依赖归属
|
||||
|
||||
适用于新增文件、拆分组件、移动逻辑和导入调整。具体架构背景见[前端架构](../../../docs/08-frontend-architecture.md)。
|
||||
|
||||
| 位置 | 职责 |
|
||||
|---|---|
|
||||
| `web/src/app/` | 应用入口、路由、Provider 与整体布局 |
|
||||
| `web/src/pages/` | 路由级页面及页面组合 |
|
||||
| `web/src/features/` | 业务功能与该功能的交互、Hook |
|
||||
| `web/src/entities/` | 领域对象展示 |
|
||||
| `web/src/shared/` | 跨功能基础组件、工具与共享能力 |
|
||||
| `web/src/api/` | 请求边界、API 类型适配;generated 为生成结果 |
|
||||
| `web/src/i18n/` | 国际化配置与翻译资源 |
|
||||
|
||||
选择能承担语义的最低合适层,而不是把所有逻辑下沉到 shared。页面专属权限、导航、发布流程
|
||||
留在业务目录;组件共享取决于职责与变化原因,不取决于出现次数。已有共享业务 Hook 的分布
|
||||
不代表新增功能都应继续集中在一个文件;不为了符合示意树搬动有效代码。
|
||||
|
||||
移动时核对直接导入、别名、barrel/re-export、字符串路径、测试和调用方。先复用
|
||||
[组件约定](./component-guidelines.md)中的基础能力;业务规则不得为迁就通用组件 API 被改写。
|
||||
技术栈和具体版本读取[依赖声明](../../../web/package.json),不在规范里维护易过期功能或依赖清单。
|
||||
|
||||
## 验证与锚点
|
||||
|
||||
检查路由能定位页面、原导入仍解析、共享模块没有新增不必要的页面依赖;检查方法见
|
||||
[前端测试](../testing/frontend-testing.md)。示例:[应用装配](../../../web/src/app/providers.tsx)、
|
||||
[业务 Hook 兼容导出](../../../web/src/features/skill/use-search-skills.ts)及其
|
||||
[导出测试](../../../web/src/features/skill/use-search-skills.test.ts)。导出测试只证明路径有效,不证明请求行为。
|
||||
68
.trellis/spec/frontend/hook-guidelines.md
Normal file
68
.trellis/spec/frontend/hook-guidelines.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# 请求 Hook、缓存与并发行为
|
||||
|
||||
适用于请求、Mutation、缓存键、失效、取消和重试修改。服务器状态使用 TanStack Query,
|
||||
不在 `useEffect` 中另建取数与缓存链。Effect 可用于确需的外部订阅等副作用,但要清理资源;
|
||||
命令式上传、下载等操作复用现有 API 边界,不为形式一致改写其业务语义。
|
||||
|
||||
## 工程约定
|
||||
|
||||
- 复用 `web/src/api/client.ts` 的类型化请求边界;不要绕过既有 CSRF、基础路径、语言头与错误处理。
|
||||
生成类型和现存 wrapper 的边界见[类型安全](./type-safety.md)。
|
||||
- 业务 Hook 放所属功能;跨功能稳定能力才放 shared。返回值明确区分加载、失败、数据和操作状态。
|
||||
- 缓存键纳入影响响应的筛选、排序、分页、资源和必要的身份/语言维度。复用已有键工厂,
|
||||
让读取、写入和失效针对同一身份;不无条件清空全站缓存。
|
||||
- Mutation 成功后按影响更新详情、列表、计数或权限缓存;检查当前筛选/分页是否仍成立。
|
||||
错误处理遵守当前局部与全局处理边界,避免重复弹提示。
|
||||
|
||||
## 具体例子:列表排序与联动刷新
|
||||
|
||||
[usePromotionList](../../../web/src/features/promotion/use-promotion-list.ts)先把默认分页、状态与排序
|
||||
归一化为 `normalizedParams`,同一个对象用于 `['promotions', normalizedParams]` 和 API 调用。
|
||||
因此 `sortDirection: 'ASC'` 与 `'DESC'` 的结果不会共用一个缓存身份。
|
||||
|
||||
```ts
|
||||
// 正例:请求条件与缓存身份使用同一份归一化参数
|
||||
queryKey: ['promotions', normalizedParams],
|
||||
queryFn: () => promotionApi.list(normalizedParams),
|
||||
|
||||
// 反例:请求已改变排序,键却不区分,可能复用另一排序的结果
|
||||
queryKey: ['promotions'],
|
||||
queryFn: () => promotionApi.list(normalizedParams),
|
||||
```
|
||||
|
||||
同文件的审批/拒绝成功回调同时失效 `['promotions']` 和 `['governance']`,因为治理面板也消费
|
||||
该结果。添加新的消费者时先定位其 query key;只刷新按钮所在列表可能让另一个页面保留旧状态。
|
||||
这不是要求所有 mutation 都刷新这两个前缀,而是要求写出“受影响消费者 → 缓存键”的实际对应关系。
|
||||
|
||||
验证排序变更时分别断言 API 参数含 ASC/DESC、两个 query key 不相同;验证 mutation 变更时
|
||||
断言实际受影响的缓存失效,不能只断言 mutation 请求成功。现有
|
||||
[列表测试](../../../web/src/features/promotion/use-promotion-list.test.ts)覆盖前三者中的参数/键,
|
||||
不覆盖 mutation 刷新或真实 QueryClient 竞态,按此次修改补足对应边界。
|
||||
|
||||
## 接口与行为契约
|
||||
|
||||
1. 快速切换 A→B 后,A 的迟到结果不能作为 B 显示或写入 B 的缓存。正确 query key 之外,
|
||||
核对手写回调、额外本地状态和同资源并发 mutation;最新可接受结果由业务语义决定。
|
||||
2. 支持取消的请求应将查询 signal 传到请求边界并清理监听/计时器。区分主动取消、超时和业务失败。
|
||||
隐藏组件或停止等待不等于服务器写入撤销;需要撤销业务时使用正式接口契约。
|
||||
3. 保留旧结果必须是明确的过渡体验,不能误标成新条件的最终结果;跨身份或敏感资源边界不沿用旧数据。
|
||||
4. 乐观更新仅在失败可恢复且冲突可处理时使用。记录受影响快照,失败恢复,并与后续成功结果协调,
|
||||
避免旧请求失败回滚掉新请求成功。复杂写入可以等待成功后刷新,无须默认乐观更新。
|
||||
5. 不盲目重试写操作;先确认重复执行的业务语义。读取重试也需区分授权失败、取消和暂时性失败,
|
||||
不能把所有错误统一包装成可重试异常。
|
||||
|
||||
## 现状锚点与待核实差异
|
||||
|
||||
- [查询实现](../../../web/src/shared/hooks/use-skill-queries.ts)中搜索键带参数,使用 `keepPreviousData`。
|
||||
源码注释记录减少搜索时卸载抖动的目的;不是所有页面都应保留旧结果的依据。
|
||||
- [缓存键测试](../../../web/src/shared/hooks/query-keys.test.ts)与
|
||||
[查询测试](../../../web/src/shared/hooks/use-skill-queries.test.ts)是可参考锚点,不能据此声称竞态已全面覆盖。
|
||||
- 当前上述查询函数未普遍透传 Query signal;[请求客户端](../../../web/src/api/client.ts)虽支持 signal/timeout,
|
||||
仍将 `AbortError` 转成超时错误。新增取消能力需同时核查这一映射及调用方,本文不声称取消闭环已实现。
|
||||
- [Provider](../../../web/src/app/providers.tsx)集中默认重试和全局错误处理;端点例外应说明原因,不能复制另一套默认策略。
|
||||
|
||||
## 验证
|
||||
|
||||
针对变更覆盖逆序响应、切换后旧请求完成、Mutation 失败恢复、身份隔离、受影响缓存刷新;
|
||||
涉及取消则断言主动取消与超时的表现。用可控 Promise/网络 fixture 触发,不能用 re-export
|
||||
存在性或查询配置字符串代替行为证明。测试方法见[前端测试](../testing/frontend-testing.md)。
|
||||
51
.trellis/spec/frontend/index.md
Normal file
51
.trellis/spec/frontend/index.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# 前端规范入口
|
||||
|
||||
适用于 SkillHub 的页面、组件、样式、状态、Hook 和 API 消费代码。先读本页,再按任务
|
||||
读取下表中的专题;不要求纯类型或数据逻辑修改加载全部视觉规范,也不要求纯前端任务
|
||||
读取全部后端规范。仓库 AGENTS.md 仍适用;CLAUDE.md 仅在当前检出存在时读取。
|
||||
|
||||
## 通用原则
|
||||
|
||||
- 先检查已有组件、调用方和测试,再决定复用或扩展;业务逻辑不因“复用”下沉到通用组件。
|
||||
- 服务端状态与临时交互状态分开,避免重复缓存和相互同步;状态作用域尽量贴近使用处。
|
||||
- 保留键盘、焦点、表单、受控状态和错误反馈契约;加载、空态、错误与无权限分别表达。
|
||||
- 类型来自真实接口契约;设计基线、当前实现和验证证据分别记录,不把文档当作能力证明。
|
||||
|
||||
## SkillHub 约定
|
||||
|
||||
- 使用现有 React/TypeScript、Vite、Tailwind 工程;版本以当前依赖声明为准。
|
||||
- 按 FSD 放置代码:`web/src/pages/` 为页面,`features/` 为业务功能,`entities/` 为领域展示,
|
||||
`shared/` 为可复用基础能力;不为对齐目录示例搬动已有效工作的业务代码。
|
||||
- TanStack Query 管理服务端请求、缓存和失效,不用 `useEffect` 取数;Zustand 管理需要共享
|
||||
的本地/UI 状态,组件内部短暂交互可用组件状态,不把服务端缓存复制进 UI store。
|
||||
- API 复用既有客户端(含 `openapi-fetch` 与类型化 wrapper)及 `web/src/api/generated/schema.d.ts`;不使用 `any`。
|
||||
后端契约变化后通过 `make generate-api` 更新生成类型,不手改生成结果。
|
||||
- 复用现有 Radix 与 shadcn 源码体系、`cn()`、组件变体和 Tailwind;“不使用 shadcn
|
||||
运行时库”不等于禁止参考其源码,不另建整套 UI 体系。
|
||||
- 用户可见文案使用 i18next/react-i18next,翻译资源在 `web/src/i18n/`。
|
||||
- 视觉规则见专题;字号、密度、组件 API 必须核对当前实现,个人预览例外不自动升级为全站默认。
|
||||
|
||||
## 按需深读
|
||||
|
||||
| 修改触发条件 | 读取内容 |
|
||||
|---|---|
|
||||
| 新增文件或调整层次 | [目录结构](./directory-structure.md) |
|
||||
| 修改视觉、布局或可见 UI | [视觉基础](./visual-foundations.md)、[组件规则](./component-guidelines.md) |
|
||||
| 动效设计、实现或评审 | [交互动效规范入口](./motion-guidelines.md)及其链接的正文;按入口说明为实现/检查 Agent 准备上下文 |
|
||||
| 选型、新增或盘点组件 | [组件目录](./component-catalog.md);候选不代表已安装 |
|
||||
| 新增页面或改变页面组合 | [页面模式](./page-patterns.md)及其引用的视觉与组件专题 |
|
||||
| 修改请求 Hook 或缓存失效 | [Hook 规则](./hook-guidelines.md) |
|
||||
| 修改状态作用域或 store | [状态管理](./state-management.md) |
|
||||
| 修改 API 消费、类型或生成 schema | [类型安全](./type-safety.md) |
|
||||
| 修改架构、路由或共享模式 | [前端架构](../../../docs/08-frontend-architecture.md) |
|
||||
| 涉及版本状态、权限或公开分发 | [生命周期契约](../../../docs/14-skill-lifecycle.md),并核对相关源码与测试 |
|
||||
| 新增日志、错误诊断或排查凭证泄漏 | [前端验收:日志与诊断](./quality-guidelines.md#日志与诊断) |
|
||||
| 组件交互、视觉与页面验收 | [前端验收](./quality-guidelines.md) |
|
||||
|
||||
## 验证与补充参考
|
||||
|
||||
按改动范围读取[测试与验证入口](../testing/index.md),选择最小相关检查;涉及共享组件、
|
||||
路由或用户流程时按影响扩大。可见变化在环境允许时用浏览器验证;未运行或环境失败明确记录。
|
||||
|
||||
本目录持有前端工程规范正文,不依赖旧工程 Skill 自动发现。架构背景与业务契约仍按上表引用。
|
||||
遇到文档与源码或其他规则冲突,记录差异,按适用指令优先级处理,不借整理规范默改业务规则。
|
||||
21
.trellis/spec/frontend/motion-guidelines.md
Normal file
21
.trellis/spec/frontend/motion-guidelines.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# 交互动效规范入口
|
||||
|
||||
涉及 Web 交互动效设计、实现或评审时,继续读取[Web 交互动效基础规范](./web-interaction-motion.md)。
|
||||
该正文随项目分发,是本项目通用动效原则的权威版本,不依赖个人目录或全局 Skill。
|
||||
静态页面且不涉及动效判断时无需加载全文。外部来源、固定版本与引用边界见正文第 8 节。
|
||||
|
||||
## 读取与任务上下文
|
||||
|
||||
- 主 Agent 从本页进入正文,不以读到链接代替读取规范。
|
||||
- 有任务上下文清单时,将本页与 `.trellis/spec/frontend/web-interaction-motion.md` 同时列入
|
||||
实现和检查的适用上下文;不要假定注入脚本会自动展开 Markdown 链接。
|
||||
- 没有任务记录时,直接读取本页和正文;无需为加载规范新建任务。
|
||||
- 正文缺失或无法读取时报告实际读取范围,不声称已遵循。
|
||||
|
||||
## 与项目规范的关系
|
||||
|
||||
- 通用原则、选择依据、模式、中断、无障碍与验证方法以正文为准。
|
||||
- 项目默认值与适配边界见[视觉基础](./visual-foundations.md);控件反馈 150ms 是项目基线,
|
||||
不推广为全部动效的通用要求。
|
||||
- 先核对现有组件与行为,优先复用;官方 recipe 参数不自动覆盖已有实现。
|
||||
- 实现与检查均使用正文第 7 节评价方法,分别说明已验证内容与未验证内容。
|
||||
22
.trellis/spec/frontend/page-patterns.md
Normal file
22
.trellis/spec/frontend/page-patterns.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# 页面组合
|
||||
|
||||
先读[视觉基础](./visual-foundations.md)和[组件目录](./component-catalog.md)。
|
||||
这些模式指导组合,不授权全站或特定页面重设计。示例组件在当前检出存在时复用,
|
||||
不存在时先核对实际能力;不为对齐文档名字迁移业务代码。
|
||||
|
||||
- 搜索:标题/输入 → 筛选/排序/结果数 → 结果列表 → 分页。输入有可访问标签,
|
||||
窄屏工具栏换行。技能卡片、分页、空态和骨架优先复用。元数据按信息优先级展示,
|
||||
不全部变成彩色徽标;空结果给清除筛选动作,失败给重试。
|
||||
- 详情:标题/来源/版本 → 安装主要操作 → 文档、版本、文件分区。
|
||||
复用安装命令、复制与 Tabs;长名称/命令可换行或内部滚动。
|
||||
安装与草稿权限遵循既有业务规则,UI 基础不能改写权限。
|
||||
- 表单:关联的 Label → Input/Select/Textarea → 帮助或错误 → 主次操作。
|
||||
错误用 aria-invalid 与 aria-describedby;提交中防重复并附文字,失败保留输入。
|
||||
权限错误单独说明,不能冒充空结果。
|
||||
- 管理列表:页头/主要操作 → 工具栏 → Table → Pagination。
|
||||
局部 compact 适合高信息密度列表,表格内部横滚。行操作不抢占主要任务;
|
||||
危险确认显示明确对象名称,保留取消路径并复用已有确认能力。
|
||||
|
||||
常规页面区块使用有限 gap-6/8、字段 gap-4、操作 gap-2;场景需要可调整。
|
||||
空态与加载态分别处理,骨架调用方提供可访问加载说明。
|
||||
复用已有标题、分页、复制、确认和骨架,不引入重复组合或包裹一切的空抽象。
|
||||
69
.trellis/spec/frontend/quality-guidelines.md
Normal file
69
.trellis/spec/frontend/quality-guidelines.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# 前端验收
|
||||
|
||||
本页规定验收行为;测试设计、隔离与执行方法见[前端测试](../testing/frontend-testing.md),
|
||||
检查选择与交付门槛见[测试入口](../testing/index.md)。先检查相关组件/调用方,再扩大到共享影响范围。
|
||||
|
||||
## 工程与组件
|
||||
|
||||
1. 按改动运行 typecheck、lint、相关单测和构建;共享行为扩大检查范围。
|
||||
2. 修改 shadcn 配置时实际执行 CLI info 与代表组件 dry-run,检查目标路径、导入和依赖。
|
||||
不能产生 shared/ui/ui 等重复目录,也不能以 CLI 识别成功证明组件来源或交互正确。
|
||||
3. 验证 focus、hover、disabled、busy、invalid、selected、open,及空/加载/错/无权限状态。
|
||||
状态有语义和文字,不只靠颜色;表单标签及错误关联可观察。
|
||||
4. Tabs 检查方向键、Home/End、禁用项跳过、受控值与动态变更;无有效选中项仍可键盘进入,
|
||||
不自动触发选择回调。Select 检查选中与滚动;Dialog 检查 Escape、Tab 范围、焦点返回与嵌套 Portal。
|
||||
5. 密度变化检查控件、表头/单元格横向对齐、卡片间距与字号;验证显式样式覆盖实际生效。
|
||||
|
||||
## 数据与交互行为
|
||||
|
||||
- 请求/筛选变化:旧响应不覆盖当前条件;加载、刷新、空结果、失败与无权限分别可辨。
|
||||
- 会话/资源变化:私有数据和未完成写入不串到另一身份或资源,表单草稿按既定契约保留或重置。
|
||||
- Mutation:成功状态、受影响缓存与列表一致;失败不留下伪成功;重复操作与并发失败恢复不覆盖较新结果。
|
||||
- 取消/重试:只在此次改动涉及这些能力时验证其语义,不能用 signal 参数存在作为完成证据。
|
||||
- 国际化:新增用户可见文字进入翻译资源,检查插值、空/长内容以及中英文布局;错误消息同样可翻译。
|
||||
|
||||
## 日志与诊断
|
||||
|
||||
- 需要记录的错误用 `console.error`,弃用提示用 `console.warn`;避免在生产代码遗留
|
||||
`console.log` 调试输出。日志不能替代用户可见的失败提示,也不要在客户端和调用方重复记录同一错误。
|
||||
- 禁止输出 token、Cookie、Authorization、密码及包含这些字段的完整请求/响应对象。
|
||||
`console.error(error)` 也不是天然安全:先检查异常 message、cause 和附带上下文是否包含敏感数据。
|
||||
- 需要定位请求失败时只选择已确认安全的字段,如操作名、HTTP 状态码;不要直接打印登录请求体、
|
||||
token 创建结果或完整 URL 查询参数。示例:`console.error('Token creation failed', { status: 500 })`;
|
||||
反例:`console.error('Token creation failed', response)`,后者可能带新生成的 token。
|
||||
- 日志修改的验收:检查所有成功/失败分支的实际参数;涉及脱敏逻辑时用明显的假凭证标记驱动错误分支,
|
||||
spy 日志输出并断言该标记未出现。不能用真实凭证做 fixture。
|
||||
|
||||
当前 [ESLint 配置](../../../web/.eslintrc.cjs)没有 `no-console` 规则;lint 通过不代表完成上述检查。
|
||||
日志级别与字段限制需在变更 diff 中审查,本页不宣称已有自动脱敏或前端日志平台。
|
||||
|
||||
## 动效验收
|
||||
|
||||
涉及动效变化时,读取[交互动效规范入口](./motion-guidelines.md)及其正文,
|
||||
按正文第 7 节区分设计评审、功能验证、性能测量和真实用户研究。
|
||||
验收范围与例外以正文为准,不以参数一致、截图或媒体查询存在代替验证。
|
||||
|
||||
## 视觉与页面
|
||||
|
||||
选择并记录代表性桌面/移动视口,验证明/暗 × regular/compact。
|
||||
检查页面无横向溢出、长中英文换行、字体加载、字重、对齐、控件层次和减少动态偏好。
|
||||
共享字体/主题/组件变化检查代表登录、搜索、技能详情和管理页,实际页面按检出选择。
|
||||
固定 API 响应可作 UI 回归,不代表真实后端或生产验收。
|
||||
|
||||
个人预览保持紧凑首屏;额外字体层级、间距、圆角、阴影、状态及组合样例可折叠,
|
||||
验收时展开检查,不因首屏精简丢失覆盖。预览用当前工程组件,局部适配与共享默认有明确区分。
|
||||
预览路径、端口、版本和启动命令属于当前任务的运行记录,不是所有工作树的固定能力。
|
||||
|
||||
## 证据与隔离
|
||||
|
||||
- 每次报告记录仓库/基线/diff、命令、实际运行环境和结果,不继承其他检出的通过记录。
|
||||
- 浏览器启动失败、业务断言失败、未运行分别记录;源码字符串断言或截图存在不能替代交互/视觉检查。
|
||||
- 截图检查后才作参考;基准标注预览版本、浏览器、视口、主题、密度、固定输入与字体/动画条件。
|
||||
像素差异先定位字体/动画等来源,不靠更新图片掩盖失败。
|
||||
- 个人预览/截图/任务日志先验证未跟踪且被忽略;工程 fixture 与个人入口分离。
|
||||
生产构建不可暴露个人入口,截图和本机路径不能混入工程 diff。
|
||||
- 官方 Skill 安装、当前会话读取、新会话发现、AGENTS 加载、hook 信任与实际触发分别验证。
|
||||
未信任 hook 不通过修改信任记录或绕过入口伪造通过。
|
||||
- 任务的延期项有明确原因与后续命令;延期不是通过,也不自动变成永久豁免。
|
||||
|
||||
规范正文不包含个人预览、截图或任务运行状态;工程测试与 fixture 按仓库约定维护。
|
||||
34
.trellis/spec/frontend/state-management.md
Normal file
34
.trellis/spec/frontend/state-management.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# 状态归属与会话边界
|
||||
|
||||
适用于筛选、表单、缓存、跨组件状态、登录登出及命名空间切换。
|
||||
|
||||
| 状态 | 默认归属 | 边界 |
|
||||
|---|---|---|
|
||||
| 远端数据、缓存、失效 | TanStack Query | 不另复制一份到 Zustand 并靠 Effect 同步 |
|
||||
| 可分享、刷新后应恢复的筛选/分页 | 路由 URL | 解析、默认值和查询参数一致;检查前进/后退 |
|
||||
| 编辑中的表单、弹层开关 | 组件或所属功能状态 | 草稿有独立生命周期;不要被后台刷新覆盖 |
|
||||
| 多组件共享的本地/UI状态 | 现有 Zustand 或所在功能共享状态 | 明确所有者、重置时机;不为单组件临时值建全局 store |
|
||||
| 可由上述数据推导的值 | 计算结果 | 避免维护第二份可漂移状态 |
|
||||
|
||||
已有页面未使用 URL 的状态,不在纯文档迁移时改变产品行为;新增可分享状态先确定路由契约。
|
||||
服务端数据被复制为用户可编辑草稿时,明确初始化、保存、放弃及远端变更冲突的处理。
|
||||
|
||||
## 身份、权限与资源切换契约
|
||||
|
||||
- 用户私有缓存应按身份分区,或在会话变化时清理;选择方案时涵盖在途请求和切换后重新订阅。
|
||||
不允许用户 B 看见用户 A 的私有结果。客户端角色判断用于展示,不能代替服务端授权。
|
||||
- 命名空间、资源、语言影响响应时,纳入缓存身份或有等价失效机制。资源切换需清理旧选择、草稿、
|
||||
乐观状态;是否保留草稿按产品契约判断,不静默丢弃用户输入。
|
||||
- 禁用查询只控制取数,不自动证明缓存已清除;旧数据保留只适用于允许沿用的上下文,不能跨越身份边界。
|
||||
- Mutation 成功后更新或失效相关数据,失败后不能显示未成功的服务端状态。具体见[Hook 契约](./hook-guidelines.md)。
|
||||
|
||||
## 已有实践与证明范围
|
||||
|
||||
[通知会话工具](../../../web/src/features/notification/notification-session.ts)按用户分区通知键,并清理一组
|
||||
会话相关查询;[测试](../../../web/src/features/notification/notification-session.test.ts)验证分区及保留公共搜索缓存。
|
||||
[会话建立 Hook](../../../web/src/features/auth/use-session-bootstrap.ts)先调用清理再写入当前用户。
|
||||
这些锚点不证明所有接口均已覆盖身份切换:新增私有查询要核对它是否进入这条机制。
|
||||
|
||||
[语言缓存键](../../../web/src/shared/hooks/query-keys.ts)展示语言维度;不要把一个键工厂推广为所有响应都依赖语言。
|
||||
验证应覆盖相关的账号切换、资源快速切换、浏览器前进/后退、未保存表单与后台刷新,方法见
|
||||
[前端测试](../testing/frontend-testing.md)。
|
||||
54
.trellis/spec/frontend/type-safety.md
Normal file
54
.trellis/spec/frontend/type-safety.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# 类型安全与 API 边界
|
||||
|
||||
适用于 API 消费、模型、生成 schema 和类型断言修改。
|
||||
|
||||
- 保持 TypeScript strict;不新增 `any` 逃避契约。未知外部输入用 `unknown` 并在边界收窄,
|
||||
TypeScript 泛型或断言不等于运行时校验。
|
||||
- API 形状优先来自 `web/src/api/generated/schema.d.ts`,通过既有 API 模块引用;
|
||||
展示模型、表单草稿和必要适配可以独立定义,但不能再维护一份会漂移的服务端协议。
|
||||
- 用户标识使用字符串;不要因为样本是数字文本便转成 number。资源 ID 的类型依据各自协议,不混为一谈。
|
||||
- 不以 `as`、非空断言或手写 Required 掩盖服务端可选字段。适配层强化约束时说明实际保证,
|
||||
覆盖缺失/null/未知枚举的适用行为;不能把假设伪装为 schema 事实。
|
||||
- 后端 OpenAPI 契约变化后通过 `make generate-api` 生成类型,生成文件随契约修改交付;不手改生成结果。
|
||||
命令实现以 [Makefile](../../../Makefile) 为准,检查前确认所用服务来自本次代码。
|
||||
|
||||
## 改接口字段时怎么落地
|
||||
|
||||
例如调整当前用户字段,先查看 [API 类型入口](../../../web/src/api/types.ts) 的 `User`,
|
||||
再追到生成 schema 的 `AuthMeResponse`。当前 `User` 用 `Omit` 后重声明部分字段;这属于已有
|
||||
适配边界,不是服务端自动保证所有字段必填的证据。新增必填假设应核对响应及缺失值处理。
|
||||
|
||||
```ts
|
||||
// 消费既有类型,不在组件内复制一份 User 协议
|
||||
import type { User } from '@/api/types'
|
||||
type UserLabelProps = Pick<User, 'userId' | 'displayName'>
|
||||
|
||||
// 反例:用户身份可能包含非数字字符,转换会破坏身份
|
||||
// const userId = Number(user.userId)
|
||||
// 反例:断言只改变编译器看法,不能验证接口返回
|
||||
// const user = response as User
|
||||
```
|
||||
|
||||
新增测试数据可参考
|
||||
[晋升列表 fixture](../../../web/src/features/promotion/use-promotion-list.test.ts) 的
|
||||
`{ ... } satisfies PromotionTask`:缺字段时修复 fixture 或核实契约,不用 `as PromotionTask` 绕过检查。
|
||||
`Partial<T>` 可表达真正的局部编辑状态,但不能用来掩盖最终提交协议所需字段。
|
||||
|
||||
字段变更后的检查顺序:核对后端响应/schema → 更新已有类型或适配 → 检查消费方和 fixture →
|
||||
运行 `pnpm -C web typecheck` 与相关测试。仅类型变化不需要引入新的运行时校验库;
|
||||
若此次需求涉及不可信输入,则在对应边界明确验证及失败行为。
|
||||
|
||||
## 现有边界
|
||||
|
||||
[客户端](../../../web/src/api/client.ts)同时包含 openapi-fetch 与手写 convenience wrapper,
|
||||
[API 类型](../../../web/src/api/types.ts)同时包含生成类型映射和手写接口。
|
||||
新增消费复用这些入口并优先生成类型;不能宣称现有全部请求已经生成类型覆盖,
|
||||
也不在规范整理中无范围限制地重写所有 wrapper。
|
||||
|
||||
[编译配置](../../../web/tsconfig.json)启用 strict;
|
||||
[客户端测试](../../../web/src/api/client.test.ts)提供请求边界用例。审查时检查响应字段、请求参数、
|
||||
可选性、身份类型与消费方一致,必要时补充边界测试。
|
||||
|
||||
生成一致性检查入口为 `scripts/check-openapi-generated.sh`,该脚本会启动本地依赖且清理时执行
|
||||
Compose down,不是纯静态检查;在隔离环境按[测试入口](../testing/index.md)执行。
|
||||
不能把类型检查通过等同于真实接口、授权或运行时响应验证通过。
|
||||
40
.trellis/spec/frontend/visual-foundations.md
Normal file
40
.trellis/spec/frontend/visual-foundations.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# SkillHub 视觉基础
|
||||
|
||||
适用于采用 React 19、Tailwind 3、现有 Radix 与 shadcn 源码体系的 UI 工作。
|
||||
这是设计基线,不是所有分支的实现声明。开始修改前核对当前源码、调用方和测试;
|
||||
已具备能力、差异和验证结果记录在当前任务中。
|
||||
|
||||
视觉变量的工程来源是 `web/src/index.css`,映射是 `web/tailwind.config.ts`,
|
||||
组件 API 和实际默认值以当前检出源码为准。规则不替代源码,也不授权全站重设计。
|
||||
|
||||
| 维度 | 默认与允许变化 | 使用边界 |
|
||||
|---|---|---|
|
||||
| 页面、表面、弹层 | background/card/popover 与各自 foreground 成对 | 黑白灰主调;避免页面硬编码白色使暗色失效 |
|
||||
| 主要操作 | primary 保持近黑/近白;次操作 outline/ghost | 蓝色仅用于有限交互强调,不默认把按钮和卡片全改蓝色 |
|
||||
| 状态 | info/success/warning/destructive 与 foreground 成对 | 同时提供文字或图标,不能只靠颜色表达状态;先核对变量是否存在 |
|
||||
| 正文与标题 | 自托管 Inter 400/500/600/700;中文系统 sans 回退 | CSS 与 Tailwind 字体同源;核对实际加载资源,不假设未提供字重可用 |
|
||||
| 代码与品牌 | 自托管 JetBrains Mono;既有品牌字体限品牌场景 | 品牌字体不作为正文;不增加运行时远程字体请求 |
|
||||
| 字号 | 页面标题30/36px、区域20/28px、卡片18/28px;正文16/24或14/20px;辅助12/16px | 使用有限尺度;长标题允许换行,场景需要可调整,不以缩字解决拥挤 |
|
||||
| 字重 | 正文400、表单/次标题500、主要标题600、少量700 | 符合已加载字体字重;中文平台差异需在视觉检查中记录 |
|
||||
| 间距 | 内部8/12px、字段16px、分组24px、页面区块32px | 使用 gap-2/3/4/6/8;例外按布局场景解释,不增加无人使用的变量 |
|
||||
| 密度 | regular 控件36px/单元格12px/卡片20px;compact 32px/8px/16px | 设计基线;容器 data-density=regular/compact,使用前确认变量与组件已实现 |
|
||||
| 尺寸例外 | sm、lg、icon 沿用当前 API;Textarea 不跟随单行控件压缩 | compact 保持默认文字可读;触屏按需求使用常规或更大操作区域 |
|
||||
| 圆角 | sm=8px、md/lg=10px、xl=14px、2xl=18px;标签可full | 复用 --radius 与既有映射;文档不能声称同值的 md/lg 不同 |
|
||||
| 边框与阴影 | 边框分组、轻阴影表面、较高层级弹层 | 不给每层嵌套容器加重阴影;检查 className/style 覆盖是否实际生效 |
|
||||
| 动效 | 控件反馈默认150ms是项目基线;有依据的场景可调整,非必要装饰不新增 | 涉及动效时读取[交互动效规范入口](./motion-guidelines.md)及其正文;按通用规范验证减少动态与交互行为 |
|
||||
| 响应式 | 页面边距移动约16px/桌面约32px,工具栏换行,表格内部横滚 | 长无空格内容可换行;弹层不超视口;页面不能横向溢出 |
|
||||
| 主题、焦点与错误 | 明暗状态成对;可见 focus;aria-invalid 关联错误文字 | 不移除焦点提示;错误不能只改变边框颜色 |
|
||||
|
||||
正确:在密度实现已具备的管理表格容器上设置 `data-density="compact"`。
|
||||
错误:每个按钮分别写任意高度,并把默认正文缩成10px。
|
||||
正确:成功颜色附“已发布”文字。错误:仅用一个绿色圆点表达审核结果。
|
||||
|
||||
## 密度与局部定制
|
||||
|
||||
表头与数据单元格横向 padding 应使用同一密度来源。Portal 不自动继承触发器局部
|
||||
容器的密度,Select/菜单选项须保留可达尺寸;全局密度与局部紧凑分别验证。
|
||||
|
||||
单页面宽度、排列、文本域高度、按钮留白和标题字号可以在调用处调整。
|
||||
个人预览的局部适配不升级为全站默认;只有多处产品需求成立才按
|
||||
[组件规则](./component-guidelines.md) 增加共享变体。
|
||||
兼容类与营销页面现有特例不自动成为新页面模板,也不因此批量清理存量。
|
||||
299
.trellis/spec/frontend/web-interaction-motion.md
Normal file
299
.trellis/spec/frontend/web-interaction-motion.md
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
# Web 交互动效基础规范
|
||||
|
||||
版本:1.0 · 资料核对日期:2026-10-15 · 适用范围:跨项目、跨框架的 Web 交互设计与质量评价。
|
||||
|
||||
本规范回答四个问题:为什么动、怎样选择、不同交互怎样保持一致、如何证明它有帮助。Transitions.dev 是主要设计参考,不是本规范的发布者或认证方。
|
||||
|
||||
## 1. 阅读约定与参考边界
|
||||
|
||||
全文采用以下来源标记;标记作用于所在段落或明确标注的表格:
|
||||
|
||||
- **【官方】**:在固定版本的 Transitions.dev 资料中可以直接核对的描述、参数或方法。
|
||||
- **【归纳】**:从参考效果中提炼的通用设计原则,不代表官方原文。
|
||||
- **【建议】**:本规范为完整性、可用性或验证而补充的建议,可依据实际约束调整。
|
||||
- **【外部标准】**:来自 W3C 等资料的要求或指导,保留其适用条件和等级。APG 模式指导与浏览器性能建议不等同于 WCAG 成功准则。
|
||||
|
||||
“应”表示本规范推荐的基线;例外需要能解释用户收益和验证结果。“可以”表示可选手段。官方参数只是参考起点,不能仅凭数值一致就认定设计正确,也不能据本规范宣称整体符合 WCAG。
|
||||
|
||||
### 1.1 Transitions 是什么
|
||||
|
||||
【官方】Transitions.dev 提供交互动效展示、可复用的 CSS recipes、可调变量与面向编码 Agent 的使用指南。官方仓库还包含辅助工具;本规范仅研究交互设计依据,不要求采用这些工具。[官方 README][readme]
|
||||
|
||||
| 资料 | 解决的问题 | 可提炼的通用内容 | 不直接升级为通用规范的内容 |
|
||||
|---|---|---|---|
|
||||
| 动效示例 | 看清变化的节奏、方向和视觉关系 | 状态连续性、层级、强调程度 | 展示页布局、配色、品牌风格与演示数据 |
|
||||
| CSS recipes | 查看一个具体效果如何表达 | 进入/退出关系、动作范围、减少动态处理 | 选择器、DOM 结构、测量方式、具体曲线与数值 |
|
||||
| Motion tokens | 用有限词汇描述与调整动效 | 按用途选参数、一致性和可解释的例外 | 全量变量集合及其默认值 |
|
||||
| Agent 指南 | 帮助 Agent 将需求匹配到 recipe | 先识别交互用途,再选效果;合适时选择较低开销方案 | 安装、复制代码及工具执行步骤 |
|
||||
|
||||
【归纳】四者可理解为:示例帮助观察,recipe 提供具体表达,tokens 提供可调语言,指南帮助选择。适合参考的问题包括突兀的状态切换、难以理解的出现位置、缺少操作反馈、过多互不一致的节奏。动效不能弥补不清楚的文案、错误的状态、不合理的信息结构或无法操作的控件。
|
||||
|
||||
### 1.2 资料中的差异如何处理
|
||||
|
||||
【官方】本次固定版本的指南仍有“32 个”的描述,但索引列到了 35;README 的部分数量更旧。数量不是本规范的能力或质量依据,以实际读到的条目为准。[指南][guide]
|
||||
|
||||
【官方】`_root.css` 同时包含共享尺度和单个效果的调节变量,并非每个效果变量都引用共享 token。例如共享时长表把 toast close 对应到 350ms,而具体 toast 默认关闭时长为 250ms。不能假定复制共享 token 就能统一所有效果,也不能按“数字接近”机械替换。[tokens][tokens]
|
||||
|
||||
【建议】遇到类似差异,先确定所需交互语义,再比较具体 recipe 与参数;保留有理由的差异。指南中的复制要求属于其使用方式,不成为跨项目设计约束。
|
||||
|
||||
## 2. 原则:动效应该帮助用户理解什么
|
||||
|
||||
以下为【归纳】,验证要求为【建议】。
|
||||
|
||||
| 目的 | 选择依据 | 保持静态更合适的情况 | 可观察的验证 |
|
||||
|---|---|---|---|
|
||||
| 状态变化 | 变化不容易被察觉,且旧状态与新状态存在关联 | 高频更新造成持续闪动,或变化本来已经清楚 | 用户能指出现在的状态,不需要等动画结束 |
|
||||
| 空间关系 | 需要说明来源、去向、展开范围或层级 | 两个对象没有空间关系,移动反而暗示错误联系 | 方向与触发位置、阅读方向和实际层级相符 |
|
||||
| 操作反馈 | 用户需要知道输入已被接收、正在处理或已完成 | 静态文字、按下态或选中态已足够 | 反馈及时,过程状态和真实结果没有混淆 |
|
||||
| 注意力层级 | 新信息确实需要被注意 | 内容与当前操作无关,重复提醒打断阅读 | 重要变化可见,次要变化不会争夺注意力 |
|
||||
|
||||
【建议】先用一句话描述“用户需要理解什么”,再选择效果。无法说明收益时,优先使用静态变化。对偶发、重要变化可以适度强调;对高频操作要降低运动幅度和重复成本。品牌表达可以是附加收益,但不能牺牲可操作性或信息完整性。
|
||||
|
||||
## 3. 选择依据:建立动效设计语言
|
||||
|
||||
### 3.1 各维度怎样选
|
||||
|
||||
下表为【归纳/建议】,不是官方数值标准。
|
||||
|
||||
| 维度 | 作用 | 选择依据与边界 |
|
||||
|---|---|---|
|
||||
| 时长 | 决定变化可被追踪的时间 | 高频、局部反馈宜短;复杂关系可留更多观察时间。持续更久不代表更自然,短到难以察觉也可能失去目的 |
|
||||
| 缓动 | 描述速度如何变化 | 减速有助于落定;连续往复需要衔接;匀速适合表达持续过程。弹性只在额外强调有价值时使用,避免暗示状态反复 |
|
||||
| 延迟 | 区分意图或组织顺序 | 悬停提示可用短延迟避免误触发;直接操作应先反馈。延迟不能变成等待装饰效果的门槛 |
|
||||
| 位移 | 表达来源、方向和关联 | 用足以看清关系的距离;大面积、长距离移动成本更高。不要让无关对象沿相同路径制造假关联 |
|
||||
| 缩放 | 表达层级、出现和轻度强调 | 保持内容可读、位置可追踪;少量缩放通常比强烈弹跳更克制。大区域缩放会放大视觉运动 |
|
||||
| 透明度 | 表达显隐、旧新内容交接 | 适合关系简单的变化;避免两段文字同时可读造成重影。透明不等于不可聚焦或对辅助技术隐藏 |
|
||||
| 模糊 | 弱化过渡中的细节 | 可选装饰;会降低瞬时可读性并增加渲染成本。文字密集、低性能或减少动态场景优先省略 |
|
||||
| 错峰 | 让一组变化体现顺序 | 仅在顺序有意义时使用;总等待随数量增长,不能让末尾内容长期不可用 |
|
||||
|
||||
【建议】同时考虑“多久开始反馈”和“多久结束视觉变化”。用户可以立即得到已接收反馈,视觉过渡稍后完成。顺序播放的总时间包含各段时长、间隔及错峰;并行播放不能简单相加。不要只检查某一个 duration。
|
||||
|
||||
### 3.2 如何使用语义化 motion tokens
|
||||
|
||||
【官方】指南强调按**用途**匹配 token,而不是按原有数字找最近值。[指南的 Motion tokens 与 refine 说明][guide]
|
||||
|
||||
【建议】语义化意味着能回答“这个值负责什么”:例如“轻反馈”“表面进入”“表面退出”“强调反馈”。这些是本规范的说明性词汇,不是要求新增的变量名。先识别已有语义,同类用途保持一致;只有确实不同的交互需要,才保留独立参数。
|
||||
|
||||
下面仅摘取少量官方参考,完整定义见 [固定版本 tokens][tokens]:
|
||||
|
||||
| 官方 token | 该版本参考值 | 官方所举用途 | 使用时需要判断 |
|
||||
|---|---|---|---|
|
||||
| `--duration-quick` | 150ms | 弹层关闭、文字替换等 | 是否足以理解变化,是否存在额外串行等待 |
|
||||
| `--duration-fast` | 250ms | 弹层打开、选中指示器移动等 | 频率、范围和内容可用时间 |
|
||||
| `--duration-stagger` | 40ms | 相邻项目的错峰间隔 | 总数量造成的累计延迟 |
|
||||
| `--ease-smooth-out` | `cubic-bezier(0.22, 1, 0.36, 1)` | 表面显隐与位置变化等 | 是否符合动作的起止关系 |
|
||||
| `--distance-micro` | 4px | 文字替换 | 内容是否仍能轻松阅读 |
|
||||
| `--scale-large` | 0.96 | modal 显隐 | 名称描述其体系内的缩放幅度,不能解释为通用尺寸 |
|
||||
|
||||
【建议】完整的动效约定至少表达:触发条件、变化目的、运动属性、节奏、结束状态、中断策略、减少动态方案。只有一张毫秒表,不能构成设计语言。
|
||||
|
||||
简短示例:选择变化立即成立;指示器从当前位置移动至新位置;快速改选时转向最新目标;减少动态时直接落位。这里的核心是选择连续性,并不依赖固定时长。
|
||||
|
||||
## 4. 通用模式:从用户需要理解的变化出发
|
||||
|
||||
本节每项“官方参考”描述来源能力;“选择、误用、验证”为【归纳/建议】。
|
||||
|
||||
### 4.1 出现与消失
|
||||
|
||||
- **用户需要理解**:什么出现了,它与当前内容是什么关系,关闭后回到哪里。
|
||||
- **动效如何帮助**:淡入淡出表达显隐,轻缩放表达浮于原内容之上的表面;运动方向应符合真实关系。
|
||||
- **官方参考**:[Modal open / close][modal] 展示中心缩放的进入与退出;该版本参考打开 250ms、关闭 150ms。
|
||||
- **适用条件**:表面的出现需要建立层级。仅提示小范围状态变化时,静态提示或淡变可能足够,不必引入更重的表面。
|
||||
- **不适用与误用**:把所有内容都做成弹出;用相同长时长处理每次关闭;透明退出后仍拦截操作;为了演完动画延迟焦点处理。
|
||||
- **验证**:进入、退出、退出中重新打开都能到达正确状态;焦点留在可理解的位置;关闭后不会留下不可见的交互区域。
|
||||
|
||||
### 4.2 展开与收起
|
||||
|
||||
- **用户需要理解**:哪些内容属于这个标题,收起隐藏了哪一部分。
|
||||
- **动效如何帮助**:让内容区域的变化和展开标记保持对应,减少突然挤动造成的迷失。
|
||||
- **官方参考**:[Accordion expand][accordion] 通过区域高度变化与箭头翻转表达展开状态;具体 grid 技巧属于实现细节。
|
||||
- **适用条件**:局部内容与触发器有明确归属,尺寸变化可以被稳定呈现。
|
||||
- **不适用与误用**:长内容展开产生大面积运动;为控制动画而裁掉内容;视觉收起后隐藏控件仍能获得焦点;箭头与真实状态不一致。
|
||||
- **验证**:短长内容、内容动态变化、快速反向操作均能正确落定。若正在隐藏的内容含焦点,焦点去向应有明确安排。区域重排过大时采用静态展开或更轻的提示。
|
||||
|
||||
### 4.3 切换与替换
|
||||
|
||||
- **用户需要理解**:哪项被选中,什么内容被替换,是否仍在同一个上下文。
|
||||
- **动效如何帮助**:移动选中指示器表达连续选择;轻量交接表达旧内容变成新内容。
|
||||
- **官方参考**:[Tabs sliding][tabs-recipe] 展示选中指示器移动;[Text states swap][text-swap] 展示文字退出后新文字进入。前者首屏定位与尺寸调整不播放切换动画。
|
||||
- **适用条件**:互斥选项有明确位置关系,或同一位置的状态发生可理解的变化。
|
||||
- **不适用与误用**:让指示器移动代替选中语义;文字不断翻动难以阅读;排队播放已经过期的选择;让键盘必须等动画结束。
|
||||
- **验证**:快速连续选择后显示最新有效状态;焦点、选中态、内容一致;初次显示没有从错误位置滑入。高频数据更新可以直接替换,保留稳定标签帮助比较。
|
||||
|
||||
### 4.4 加载与完成
|
||||
|
||||
- **用户需要理解**:内容尚未准备好,系统仍有响应,何时可以使用结果。
|
||||
- **动效如何帮助**:适当占位维持空间预期,轻微活动表达处理中,结果出现后结束活动。
|
||||
- **官方参考**:[Skeleton loader and reveal][skeleton] 展示占位与内容的交接;其实际揭示函数与演示重放计时是分开的。
|
||||
- **适用条件**:等待具有可感知长度,且能够合理预估内容结构。未知结构可以用简洁的状态提示。
|
||||
- **不适用与误用**:为了播放完整循环而延迟已就绪内容;用演示计时代表真实完成;错误发生后仍无限加载;未知进度伪装成准确百分比。
|
||||
- **验证**:即时完成、长等待、失败、取消均有清楚状态;内容就绪即可使用;关闭动态后仍能知道正在处理;占位不会制造严重布局跳变。
|
||||
|
||||
### 4.5 成功与错误反馈
|
||||
|
||||
- **用户需要理解**:操作是否完成,或哪里需要修正。
|
||||
- **动效如何帮助**:有限强调吸引注意,随后保留清楚、可读的结果。
|
||||
- **官方参考**:[Success check][success] 展示完成标记;[Error state shake][error] 展示抖动、错误样式和提示,并包含自动恢复的示例。
|
||||
- **适用条件**:重要但不频繁的结果需要确认,或用户可能忽略局部错误。抖动和庆祝都是可选手段。
|
||||
- **不适用与误用**:每次成功都强烈庆祝;连续输入不断抖动;仅靠颜色或运动表达错误;机械沿用错误提示的自动消失计时。
|
||||
- **验证**:结果与真实状态一致;错误未解决时,修正所需信息仍然可获得;减少动态时仍有文字或语义明确的标记。短暂提示只有在不承担唯一必要信息时才适合自行消失。
|
||||
|
||||
### 4.6 悬停、聚焦和选中
|
||||
|
||||
- **用户需要理解**:哪些对象可操作、当前焦点在哪里、哪些状态已被选定。
|
||||
- **动效如何帮助**:轻量反馈确认当前目标,选中后的稳定外观表达持久状态。
|
||||
- **官方参考**:[Tooltip open/close][tooltip] 展示悬停或聚焦提示及短延迟;[Toggle][toggle] 展示开关位置变化;选中位置关系可参考 [Tabs sliding][tabs-recipe]。
|
||||
- **适用条件**:提示提供补充说明,状态变化有清楚语义。可选的鼠标装饰不必在键盘下逐帧复刻,但信息和操作能力应等价。
|
||||
- **不适用与误用**:把必要信息只放在 hover;把聚焦当作已选中;焦点提示必须等淡入;指针快速经过仍弹出迟到提示;开关弹跳造成状态反复的错觉。
|
||||
- **验证**:键盘可用、焦点立即可见、触屏不依赖悬停;取消悬停或转移焦点后不会出现过期提示。适用的额外提示满足可关闭、可悬停、持续显示要求,见第 6 节。
|
||||
|
||||
## 5. 连续性与可中断性
|
||||
|
||||
【归纳】连续性不是要求所有动画都完整播放,而是让用户理解当前状态如何变成下一状态。进入与退出应共享对象身份和合理路径,但不要求完全镜像或相同时长。
|
||||
|
||||
【建议】按以下情境判断,而不是默认禁用操作等待动画结束:
|
||||
|
||||
| 情境 | 推荐行为 | 需要避免 |
|
||||
|---|---|---|
|
||||
| 进入中关闭、退出中打开 | 从当前可见状态继续或反向;静态落位也是可接受退化 | 先跳回起点再重播、已关闭的表面突然复现 |
|
||||
| 同一动作反复触发 | 需要再次确认时给轻反馈;同一目标不必重复完整动画 | 重复输入造成队列越积越长 |
|
||||
| 快速切换 | 对表达当前状态的动效,以最新有效目标为准 | 旧计时或旧完成通知覆盖新状态 |
|
||||
| 连续事件有独立意义 | 保留事件信息,可合并视觉呈现 | 为追求“最新”丢掉必须保留的信息 |
|
||||
| 用户取消 | 表达实际取消结果或待确认状态,停止无意义运动 | 只停止动画却继续显示错误的过程状态 |
|
||||
| 改变尺寸或内容 | 重新保持目标位置与可读性,必要时直接落位 | 把初始化和重排误演成一次用户操作 |
|
||||
| 减少动态或关闭动画 | 直接形成正确终态,保留反馈与操作能力 | 依赖必然发生的动画结束事件才能解锁界面 |
|
||||
|
||||
【建议】最低检查序列:打开→立刻关闭→重新打开;连续切换多个目标;处理中取消;内容或尺寸在过渡中改变;动画进行中启用减少动态偏好。每种情况都检查视觉、语义、焦点和可操作性,而不只看最终截图。
|
||||
|
||||
## 6. 无障碍与性能边界
|
||||
|
||||
### 6.1 减少动态不是隐藏信息
|
||||
|
||||
【官方】所读取 recipes 提供 `prefers-reduced-motion` 处理,并要求保留。[指南][guide]
|
||||
|
||||
【建议】减少非必要位移、缩放、弹跳、模糊和循环活动,可直接切到终态,或在适合时采用更轻的透明度变化。减少动态不是把所有状态提示删掉。不能只检查媒体查询存在:还要实际启用偏好,确认等待逻辑、最终可见性、错误提示和焦点仍正确。
|
||||
|
||||
【外部标准】WCAG 2.2 的 [2.3.3 Animation from Interactions(AAA)][wcag-motion] 要求交互触发的运动动画可被禁用,除非对功能或信息表达必不可少。它不是所有 AA 检查中的同一条强制要求。本规范仍建议将减少动态作为基本体验设计的一部分;必要运动需具体说明为何不可替代。
|
||||
|
||||
### 6.2 键盘、焦点和状态语义
|
||||
|
||||
【外部标准】键盘操作、合理焦点顺序与可见焦点分别参见 WCAG [2.1.1][wcag-keyboard]、[2.4.3][wcag-order]、[2.4.7][wcag-focus];状态消息参见 [4.1.3][wcag-status]。视觉动画不能代替这些能力。
|
||||
|
||||
【建议】焦点不跟随装饰性物体移动,不留在已经隐藏或移除的内容中。选中、展开、错误和处理状态应具有可感知且可由辅助技术识别的表达;不需要为每一帧运动发出播报。
|
||||
|
||||
【外部标准/建议】Tabs 的方向键、进入方式和自动/手动激活应参考 [WAI-ARIA APG Tabs][apg-tabs],具体选择取决于内容能否及时显示。官方动效 recipe 的点击示例不等于完整 Tabs 行为。无有效选中项时仍应为可用选项保留合理键盘入口;禁用项的焦点策略按控件语义明确,不由滑动效果决定。
|
||||
|
||||
【外部标准】对于 hover/focus 触发的额外内容,WCAG [1.4.13(AA)][wcag-hover] 在适用条件下要求可关闭、可悬停且持续显示,并规定了例外。不能因为官方提示动画离开触发器就消失,直接认定整套提示行为符合该要求。
|
||||
|
||||
### 6.3 闪烁、循环与阅读干扰
|
||||
|
||||
【外部标准】WCAG [2.3.1(A)][wcag-flash] 对每秒闪烁次数及一般/红色闪光阈值有明确要求;不是仅凭动画时长或肉眼“温和”判断。存在高对比快速闪烁时,应按阈值评价。
|
||||
|
||||
【外部标准】[2.2.2(A)][wcag-pause] 对自动开始、超过五秒、与其他内容并行呈现的移动/闪烁/滚动信息,通常要求暂停、停止或隐藏机制,必要活动有例外;自动更新信息另有控制要求,不套用五秒门槛。
|
||||
|
||||
【建议】没有触及上述条件,也不意味着持续装饰值得保留。长时间阅读区域优先安静;对处理指示可用静态文字补足,避免为了“还活着”不断占用注意力。
|
||||
|
||||
### 6.4 渲染成本与收益
|
||||
|
||||
【外部指导】浏览器性能指南建议尽量使用 transform 和 opacity,检查布局、绘制与合成成本,并谨慎使用 `will-change`。[web.dev 性能指南][performance]
|
||||
|
||||
【建议】这些是优先选择,不是无条件禁令:展开区域可能确实需要改变布局;transform 也不能保证任何场景都流畅。模糊、大面积透明叠加、多个并发动画或大量独立图层可能产生显著成本。是否保留,要依据实际面积、内容数量、设备能力与测量结果。
|
||||
|
||||
【建议】不要因 recipe 出现 `will-change` 就将其升级为全局默认。关注长期图层和内存成本;先减少无必要属性、缩小运动范围或简化效果,再判断是否需要额外优化。仅在常用设备上看起来顺滑,不足以证明低性能设备可用。
|
||||
|
||||
## 7. 验证方式:怎样评价一个动效
|
||||
|
||||
### 7.1 六个判断维度
|
||||
|
||||
以下为【建议】。每项应有观察结果,不能仅给“流畅、自然”的评价。
|
||||
|
||||
| 维度 | 要回答的问题 | 合适的证据 |
|
||||
|---|---|---|
|
||||
| 明确目的 | 它帮助用户理解哪种变化? | 一句目的说明;与静态方案的对照 |
|
||||
| 理解变化 | 来源、目标、选中和结果是否清楚? | 观察操作后的解释与误判,而非只问好不好看 |
|
||||
| 及时且一致 | 是否及时响应,同类变化是否可预期? | 实际反馈延迟、完整序列与快速操作观察 |
|
||||
| 阅读和操作 | 是否妨碍阅读、点击、键盘或焦点? | 长内容、连续操作、键盘和辅助技术验证 |
|
||||
| 信息完整 | 减少或关闭动态后是否仍能完成同一操作? | 静态/减少动态模式下的状态与结果检查 |
|
||||
| 收益与成本 | 视觉收益是否值得渲染和注意力成本? | 有/无该效果的性能对照与用户表现 |
|
||||
|
||||
### 7.2 四类验证各自证明什么
|
||||
|
||||
| 类别 | 可以证明什么 | 不能单独证明什么 |
|
||||
|---|---|---|
|
||||
| 设计评审 | 意图、方向、层级、节奏和一致性是否合理 | 真实可操作性、运行性能、用户收益 |
|
||||
| 功能验证 | 状态、键盘、焦点、中断、减少动态是否按预期工作 | 大规模用户理解度或所有设备性能 |
|
||||
| 性能测量 | 指定设备、浏览器和负载下的帧时间、掉帧、长任务、布局/绘制与资源成本 | 用户是否理解或喜欢该效果 |
|
||||
| 真实用户研究 | 特定人群与任务中的理解、完成效率、错误、干扰与不适 | 未覆盖人群的结论或无条件因果关系 |
|
||||
|
||||
【建议】性能记录注明设备、浏览器、内容规模和并发情境;不把固定帧率口号当作全场景验收线。用户研究区分偏好和实际表现,样本与场景不足时说明局限;没有研究时不宣称“已经提升效率”。
|
||||
|
||||
### 7.3 最小评价记录
|
||||
|
||||
【建议】每个有实际用途的动效,只需能简要回答:
|
||||
|
||||
1. 用户要理解什么,静态方案是否已经足够?
|
||||
2. 为什么选择这个模式、范围和节奏?
|
||||
3. 进入、退出、重复、中断与取消分别怎样结束?
|
||||
4. 减少动态、键盘和辅助技术下信息是否完整?
|
||||
5. 做过哪些设计观察、功能检查和性能测量?哪些仍未验证?
|
||||
6. 若保留例外,它带来的收益是什么,有什么证据?
|
||||
|
||||
这是一套评价问题,不要求每个轻微反馈都开展完整研究。覆盖范围随交互频率、运动面积、复杂度和用户风险增加。
|
||||
|
||||
## 8. 来源、版本与复用说明
|
||||
|
||||
### 8.1 Transitions 官方来源
|
||||
|
||||
本次研究固定到提交 [`9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62`][commit],提交时间为 2026-10-14 13:48:14 UTC。下列仓库来源及本规范 recipe 链接均固定到该提交。
|
||||
|
||||
- [官方仓库 README][readme]:定位、展示与可复用 recipes 的关系。
|
||||
- [官方 Agent 指南及 recipes 索引][guide]:选择规则、语义化 token、效果边界。
|
||||
- [Motion tokens][tokens]:共享尺度与具体效果参数。
|
||||
- [官网页面源码][homepage-source]:核对产品展示内容。官网入口为 [Transitions.dev](https://transitions.dev/);本次直连返回 HTTP 403,因此使用固定版本页面源码,未声称已经浏览或验证在线交互效果。
|
||||
- 本文链接的九份具体 recipes:modal、accordion、tabs、text swap、skeleton、success、error、tooltip、toggle;用于核对具体表达和边界。
|
||||
|
||||
【官方】[仓库许可][license] 区分动效/skills 的自定义许可与工具代码的 MIT 许可,不能将整个仓库称为 MIT。动效许可允许在个人及商业产品中使用和修改,限制把集合或其重要部分重新发布为竞争动效库;模板等再分发场景有额外条款。具体复用还应查阅 [Skill 许可][skill-license] 与其指向的完整条款。本规范只保留少量参数、原创归纳和来源链接,不复制或重新分发动效集合;不作超出所读许可的授权承诺。
|
||||
|
||||
### 8.2 补充来源
|
||||
|
||||
- [WCAG 2.2][wcag]:本次读取正文中的相关成功准则;各处链接保留条目与等级。
|
||||
- [WAI-ARIA APG Tabs][apg-tabs]:组件键盘与语义模式指导。
|
||||
- [web.dev:高性能 CSS 动画][performance]:浏览器渲染与测量指导。
|
||||
|
||||
以上补充网页于 2026-10-15 读取,链接可能后续更新;不作为 Transitions 官方主张。
|
||||
|
||||
### 8.3 本规范新增了什么
|
||||
|
||||
【归纳】目的、维度与模式分类来自对官方材料的重新组织;并未把完整效果目录当作基础标准。
|
||||
|
||||
【建议】连续性与中断策略、静态退化、错误信息保留、验证矩阵和例外判断,是为了形成可长期复用的规范而补充。无障碍和性能结论另标外部来源,不冒充 Transitions 官方保证。
|
||||
|
||||
采用时,将本文作为动效原则的单一来源;已有基础规范可以通过引用纳入,或将本文内容合并后只保留一个权威版本。具体默认数值及适配决定可另行说明,但不重复维护一套相同原则。上游更新时先核对变化与理由,不因为版本更新就自动替换已经验证的设计选择。
|
||||
|
||||
[readme]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/README.md
|
||||
[guide]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/SKILL.md
|
||||
[tokens]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/_root.css
|
||||
[modal]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/06-modal.md
|
||||
[accordion]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/21-accordion.md
|
||||
[tabs-recipe]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/16-tabs-sliding.md
|
||||
[text-swap]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/04-text-states-swap.md
|
||||
[skeleton]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/14-skeleton-reveal.md
|
||||
[success]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/10-success-check.md
|
||||
[error]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/12-error-state-shake.md
|
||||
[tooltip]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/17-tooltip.md
|
||||
[toggle]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/27-toggle.md
|
||||
[homepage-source]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/index.html
|
||||
[license]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/LICENSE
|
||||
[skill-license]: https://github.com/Jakubantalik/transitions.dev/blob/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62/skills/transitions-dev/LICENSE.txt
|
||||
[commit]: https://github.com/Jakubantalik/transitions.dev/commit/9cb3a4afe2a8aea0e2fab66fc784fc37830ceb62
|
||||
[wcag-motion]: https://www.w3.org/TR/WCAG22/#animation-from-interactions
|
||||
[wcag-keyboard]: https://www.w3.org/TR/WCAG22/#keyboard
|
||||
[wcag-order]: https://www.w3.org/TR/WCAG22/#focus-order
|
||||
[wcag-focus]: https://www.w3.org/TR/WCAG22/#focus-visible
|
||||
[wcag-status]: https://www.w3.org/TR/WCAG22/#status-messages
|
||||
[wcag-hover]: https://www.w3.org/TR/WCAG22/#content-on-hover-or-focus
|
||||
[wcag-flash]: https://www.w3.org/TR/WCAG22/#three-flashes-or-below-threshold
|
||||
[wcag-pause]: https://www.w3.org/TR/WCAG22/#pause-stop-hide
|
||||
[wcag]: https://www.w3.org/TR/WCAG22/
|
||||
[apg-tabs]: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
|
||||
[performance]: https://web.dev/articles/animations-guide
|
||||
14
.trellis/spec/guides/code-reuse-thinking-guide.md
Normal file
14
.trellis/spec/guides/code-reuse-thinking-guide.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# 代码复用判断
|
||||
|
||||
新增 helper、组件、服务或合并相似代码前,先找已有实现、调用方与测试。
|
||||
|
||||
- 相似代码是否表达同一业务概念,还是写法碰巧相似?
|
||||
- 调用方是否应因同一原因一起变化?变化原因不同通常应保留独立实现。
|
||||
- 提取后依赖方向是否正确?不要为了共用把业务规则塞入通用 UI 或底层工具。
|
||||
- 抽象是否减少理解成本,还是增加开关、可选参数和隐藏状态?
|
||||
- 有无已有组件、类型、schema、仓储或解析入口可用?
|
||||
- 修改共享实现影响哪些调用方,怎样证明旧行为保持?
|
||||
|
||||
次数、行数和相似度不是自动抽象阈值。外部数据解析需要明确契约归属,但不因此要求新建通用解码框架。
|
||||
|
||||
具体规则:[组件](../frontend/component-guidelines.md)、[类型](../frontend/type-safety.md)、[分层](../backend/directory-structure.md)、[测试](../testing/index.md)。
|
||||
14
.trellis/spec/guides/cross-layer-thinking-guide.md
Normal file
14
.trellis/spec/guides/cross-layer-thinking-guide.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# 跨层修改判断
|
||||
|
||||
适用于前端、API、CLI、领域、持久化、扫描边界的变化。只沿受影响路径分析。
|
||||
|
||||
1. 梳理输入→转换→状态变更/存储→响应→消费者,指出契约的权威来源。
|
||||
2. Web、CLI、兼容接口、后台任务哪些消费者受影响?生成类型是否要更新?
|
||||
3. 区分格式校验、身份授权、领域不变量与数据库结构完整性;入口校验不能替代后续授权和状态检查,也不在每层重复相同规则。
|
||||
4. 成功、拒绝、超时、取消、重试和并发时,哪些状态已经改变?外部副作用能否回退?
|
||||
5. 新旧版本并行是否兼容?配置、迁移、应用与消费者的发布顺序是什么?
|
||||
6. 哪个断言能发现最可能的错误?类型通过能否证明失败后的状态正确?
|
||||
|
||||
按需读:[API](../backend/api-contracts.md)、[事务并发](../backend/transactions-and-concurrency.md)、[请求](../frontend/hook-guidelines.md)、[测试](../testing/index.md)、[部署](../deployment/index.md)。业务状态与权限引用正式 docs/OpenSpec。
|
||||
|
||||
经验写回所属专题,包含触发、原因、预防、测试锚点。上游 Trellis 平台模板、文档站和升级实现不是 SkillHub 项目规则。
|
||||
8
.trellis/spec/guides/index.md
Normal file
8
.trellis/spec/guides/index.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# 思考指南
|
||||
|
||||
这里帮助选择适用规范,不另立一套工程规则。先读[总索引](../index.md),只追加当前问题需要的指南。
|
||||
|
||||
- 新增抽象、组件或合并实现:[代码复用](./code-reuse-thinking-guide.md)。
|
||||
- 接口、状态、数据库或多个消费者变化:[跨层分析](./cross-layer-thinking-guide.md)。
|
||||
|
||||
工程约定和行为要求写入所属领域专题;产品契约留在 docs/OpenSpec。经验需有明确触发条件、可复用预防和实现或测试证据;一次性过程留任务记录。
|
||||
74
.trellis/spec/index.md
Normal file
74
.trellis/spec/index.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# SkillHub 工程规范索引
|
||||
|
||||
本目录是 SkillHub 工程约定的权威正文,随代码版本维护;不依赖工程 Skill 自动发现。
|
||||
适用 AGENTS 指令仍优先。先选择领域,再读取触发条件对应的专题;不要默认加载整个目录。
|
||||
|
||||
## 按任务读取
|
||||
|
||||
| 任务 | 入口 | 条件性追加 |
|
||||
|---|---|---|
|
||||
| 页面、组件、状态、请求 | [前端](./frontend/index.md) | 公共接口变化追加后端 API 专题;动效读现有动效正文 |
|
||||
| Java、接口、持久化、扫描集成 | [后端](./backend/index.md) | 按事务、错误、日志、扫描选专题,业务状态读正式契约 |
|
||||
| 测试、CI、交付验收 | [测试](./testing/index.md) | 受影响领域的行为验收,跨域取并集 |
|
||||
| 配置、镜像、发布、迁移 | [部署](./deployment/index.md) | 操作手册与环境检查;读取规范不是部署授权 |
|
||||
| CLI | [CLI](./cli/index.md) | 后端 API 契约、测试、配置按实际影响追加 |
|
||||
| 需求、开发、PR、收尾 | [工作流](./workflow/index.md) | 大型需求读 OpenSpec;已有任务收尾读归档 |
|
||||
| 复用判断或跨层分析 | [思考指南](./guides/index.md) | 按问题选指南,不以问题清单替代契约 |
|
||||
|
||||
实现与检查使用相同的适用正文,检查可追加验收材料。目录存在、Skill可见、hook配置或链接有效,
|
||||
分别不等于正文已读取、规则已执行或行为已验证。没有任务时直接读取,无需为加载规范创建任务。
|
||||
|
||||
## 轻量使用方式
|
||||
|
||||
| 情况 | 使用方式 |
|
||||
|---|---|
|
||||
| 改文案、补规范或小修 | 读相关规则,直接修改验证,不强制建任务 |
|
||||
| 多步功能 | 任务记录保存需求、上下文、进度、验证;尊重用户已有任务创建选择 |
|
||||
| 大型需求或架构调整 | OpenSpec明确需求设计,Trellis组织执行与接续 |
|
||||
| 已有权威文档 | 引用完整契约;关键规则可在 AGENTS 与专题重复,修改时检查一致性 |
|
||||
| 换worktree | 从本机模板显式复用通用规则与工具配置,任务记录各自管理;已跟踪规范以分支内容为准 |
|
||||
|
||||
## 五类内容的职责
|
||||
|
||||
| 类型 | 应写什么 |
|
||||
|---|---|
|
||||
| 工程约定 | 范围、职责、模式、理由、例外 |
|
||||
| 接口与行为契约 | 输入输出、成功失败、取消、并发、持久化及断言;具体产品语义引用正式来源 |
|
||||
| 质量要求 | 改动触发条件、检查、可观察结果、证据限制 |
|
||||
| 项目经验 | 真实触发、根因、预防和回归锚点;一次性过程留任务记录 |
|
||||
| 思考指南 | 决策问题和规则链接,不暗中新增硬规则 |
|
||||
|
||||
五类是审查维度,不要求每篇填满,也不机械建立五套目录。
|
||||
专题按独立适用条件拆分;详细契约有明确归属,关键规则可在 AGENTS 和专题重复,变更时一并核对,不能出现互相冲突的版本。
|
||||
规则需有适用范围、例外和源码/测试/设计来源。
|
||||
代码现状不自动等于正确标准:分别标明目标、已知偏差和已验证行为,冲突不能静默裁定。
|
||||
|
||||
## 正文应具体到什么程度
|
||||
|
||||
索引负责选择,思考指南可以提出问题;实现专题不能只写“注意兼容”“合理测试”“复用现有能力”。
|
||||
对本次新增或调整的规则,评审时应能回答:
|
||||
|
||||
- **何时读:** 哪种改动触发它,哪些情况不适用?例如仅改翻译文字不等于修改请求行为。
|
||||
- **改哪里:** 指向真实类、函数、配置字段或测试;命名只是建议时明确标注,不虚构已有 helper。
|
||||
- **怎么做:** 给出本项目的代表例子,必要时附反例;不把第三方项目的框架和业务规则照搬过来。
|
||||
- **怎样判定:** 指出输入与可观察结果,例如错误退出码、缓存是否清空、数据库是否回滚,而非只列测试命令。
|
||||
- **哪些尚未做到:** 将现有行为、目标要求和缺口分开;已存在测试不等于本次已运行,计划中的断言不写成已有覆盖。
|
||||
|
||||
简单命名规则不必填五段模板,复杂专题也不靠凑示例数量证明完整。新增命令应说明工作目录、依赖和副作用。
|
||||
规则变化同时核对 AGENTS 中的摘要、所属索引和专题;链接检查只能证明路径可达,内容仍需对照来源审查。
|
||||
|
||||
## 与其他资料的关系
|
||||
|
||||
- AGENTS直接保留关键产品、架构、安全与高频工程规则;本目录补充场景、例外、实现依据和验证方法。
|
||||
- docs保留架构、业务语义和操作手册;OpenSpec保留正式需求、设计、行为变化与验收。
|
||||
- POM、package.json、schema、源码、测试和脚本提供当前事实;规范链接它们,不维护重复版本清单。
|
||||
- Trellis执行Skill只组织启动、读取、检查、收尾,不持有另一套项目规范。
|
||||
- 学到经验时先判断是否稳定复用;需沉淀则更新所属专题及对应检查,不要求每次任务新增文档。
|
||||
|
||||
## 版本与本机模板
|
||||
|
||||
仅 `.trellis/spec/` 纳入Git;任务、journal、运行状态、个人配置和执行工具保持本地。
|
||||
公开规范不能依赖私人绝对路径或未提交正文。新检出无需安装Trellis即可阅读本目录。
|
||||
本机模板是显式复用与分发工具,不能静默覆盖当前分支已跟踪规范或同步其他worktree的任务。
|
||||
本地同步/发布脚本存在时先核对实际范围、来源差异及备份;不因同步绕过AGENTS修改审批。
|
||||
归档与清理按[任务记录](./workflow/task-archive.md)处理;无记录不强制创建。
|
||||
39
.trellis/spec/testing/backend-testing.md
Normal file
39
.trellis/spec/testing/backend-testing.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# 后端测试设计
|
||||
|
||||
测试放在对应 Maven 模块的 `src/test/java/`。使用现有 JUnit 5、Mockito、AssertJ 与 Spring 测试切片;
|
||||
沿用 `{ControllerName}Test`、`{ServiceName}Test`、`{FlowName}IntegrationTest`、
|
||||
`{ControllerName}SecurityTest` 等相邻命名,不为统一命名重命名现有测试。
|
||||
|
||||
## 按需要证明的行为选层次
|
||||
|
||||
| 行为 | 测试边界与断言 |
|
||||
|---|---|
|
||||
| 领域状态、权限决策、计算 | 领域单测;正常、边界、拒绝及失败后状态,mock 外部端口而非被测规则 |
|
||||
| HTTP 参数、认证上下文、响应与异常映射 | Controller / 安全测试;状态码、响应字段、匿名/越权负例;服务单测不能证明过滤器链 |
|
||||
| JPA 查询、数据库约束、锁、原生 SQL | 仓储或数据库集成测试;检查实际结果与约束,不只 verify repository 调用 |
|
||||
| 事务回滚、提交后副作用、并发更新 | 独立事务与连接,控制竞争顺序,检查最终持久状态、重复副作用与失败返回 |
|
||||
| Flyway 迁移 | 适用 PostgreSQL 上运行迁移并验证升级后的 schema/数据;文本扫描仅为辅助护栏 |
|
||||
|
||||
数据库方言、锁或事务是风险来源时,H2 与 mock 不足以代替 PostgreSQL。复用项目的 Testcontainers
|
||||
模式,或者任务已授权的隔离实例;禁止把测试指向共享/生产库。测试依赖缺失应报告受阻,
|
||||
不能删除断言或跳过用例后宣称该契约已验证。
|
||||
|
||||
## 隔离与并发
|
||||
|
||||
- fixture 显式表达角色、归属、版本和初始状态,避免依赖本机已有数据。
|
||||
- 回滚型测试只证明其覆盖的事务;后台线程、异步任务、提交回调需要单独等待与清理。
|
||||
- 用 latch/barrier 或明确事务顺序触发竞争,设有界超时;不要依赖机器速度与 sleep。
|
||||
- 验证允许的一组结果以及最终不变量,不强求未由业务契约指定的竞争赢家。
|
||||
- 涉及 Redis、对象存储或消息时分别证明持久化和外部副作用,测试结束释放资源。
|
||||
|
||||
## 执行与已知经验
|
||||
|
||||
使用 `make test-backend-app`(含 `-am`)或 `make test-backend`。
|
||||
只运行 `./mvnw -pl skillhub-app clean test` 可能使用本地 Maven 仓库的旧兄弟模块产物,
|
||||
产生误导性的符号/签名错误。出现这类错误先核对 reactor 范围,不直接修改正确的业务签名。
|
||||
过滤单个测试时同样考虑依赖模块及无匹配测试的处理,运行前核对当前 Maven 配置。
|
||||
|
||||
来源: [Makefile](../../../Makefile)、[测试依赖](../../../server/skillhub-app/pom.xml)、
|
||||
[PostgreSQL 邀请并发用例](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/repository/NamespaceInvitationPostgresTest.java)、
|
||||
[迁移静态护栏](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/db/FlywayMigrationGuardrailTest.java)。
|
||||
前者可参考真实连接和并发接受邀请的组织方式;后者不替代数据库迁移运行。
|
||||
24
.trellis/spec/testing/ci.md
Normal file
24
.trellis/spec/testing/ci.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CI 检查与证据
|
||||
|
||||
修改流水线、路径过滤、测试入口或交付门槛时读取。工作流名称不证明它运行了什么,
|
||||
必须核对 `on`、job/step 条件、矩阵和执行命令。
|
||||
|
||||
## 读取入口
|
||||
|
||||
- [pr-tests.yml](../../../.github/workflows/pr-tests.yml):前端构建/单测、后端单测、条件性文档构建。
|
||||
- [pr-e2e.yml](../../../.github/workflows/pr-e2e.yml):当前启动开发栈并执行前端 E2E,
|
||||
不能沿用“仅 staging smoke”的旧描述。
|
||||
- [pr-cli.yml](../../../.github/workflows/pr-cli.yml):CLI 平台矩阵、lint/typecheck/test/build 与 Node 运行验证。
|
||||
- [工作流目录](../../../.github/workflows/):脚本、Helm、Scanner 等其他路径按变更追加,不在此复制易过期完整清单。
|
||||
|
||||
## 质量要求
|
||||
|
||||
1. 路径过滤要覆盖源代码、共享依赖、锁文件和检查自身;没有触发不等于检查通过。
|
||||
2. 修改聚合结果或退出处理时验证成功、失败、跳过和取消传播;不能吞掉失败或仅检查进程启动。
|
||||
3. draft 跳过、路径无关跳过、环境失败、用户取消分别报告。只有运行过且断言通过才算相应范围通过。
|
||||
4. 需要的分支保护检查以实际仓库配置为准;仅看到 YAML 不声称已设为 required。
|
||||
5. 工具版本以各模块清单和工作流实际配置核对;发现不一致记录差异,不在规范整理时顺手升级。
|
||||
6. 测试产物、构建产物、镜像发布和真实部署分别证明。产物应关联被检查的提交,旧运行不覆盖后续改动。
|
||||
|
||||
本地反馈和交付按[选测矩阵](./index.md)执行;CI必需检查仍以实际仓库配置为准。
|
||||
本文件不赋予跳过适用门槛、触发部署或发布的权限。
|
||||
30
.trellis/spec/testing/e2e.md
Normal file
30
.trellis/spec/testing/e2e.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# 真实 E2E 与 Smoke
|
||||
|
||||
涉及跨页面业务、认证、权限、发布与运维流程时读取。操作步骤见[现有 E2E 手册](../../../docs/e2e.md),
|
||||
配置事实以 [Playwright 配置](../../../web/playwright.config.ts)和[Smoke 配置](../../../web/playwright.smoke.config.ts)为准。
|
||||
不要复制手册中的固定用例数量、地址或历史执行结果为规范。
|
||||
|
||||
## 真实链路与数据
|
||||
|
||||
- `web/e2e` 保持真实请求,不用 `page.route` 拦截关键 API 或伪造业务响应。
|
||||
隔离的组件/视觉 mock 测试可以存在,但应放在适用测试层且明确不是真实后端 E2E。
|
||||
- 复用 [session helper](../../../web/e2e/helpers/session.ts) 的认证与 worker 账号隔离;
|
||||
数据创建与清理复用[数据 helper](../../../web/e2e/helpers/test-data-builder.ts)。
|
||||
新场景核对 helper 实际清理能力,不假定它会回收所有数据。
|
||||
- 测试账号和数据必须专用。记录本次创建的对象,失败时也按精确对象清理,不清空共享数据库。
|
||||
- 优先 `getByRole`、`getByLabel`、`getByTestId`;等待 URL、状态或元素条件,不新增盲等。
|
||||
- 操作成功需验证用户结果或持久状态;按钮可点、页面可达不能替代完整业务验收。
|
||||
|
||||
## 执行与环境
|
||||
|
||||
入口为 `make test-e2e-frontend`、`make test-e2e-smoke-frontend`。
|
||||
默认 Playwright 可启动前端,但不会因此自动提供完整后端;先识别 URL、服务和账号来源。
|
||||
外部目标使用当前配置支持的 `PLAYWRIGHT_BASE_URL`,不要未经确认指向真实用户环境。
|
||||
Smoke 是关键路径子集;通过不能宣称全量覆盖。CI/本地重试后通过应记录首次失败并排查不稳定原因。
|
||||
|
||||
运维流程变化时更新相应脚本:
|
||||
[basic](../../../scripts/smoke-test.sh)、[namespace](../../../scripts/namespace-smoke-test.sh)、
|
||||
[governance](../../../scripts/governance-smoke-test.sh)、[promotion](../../../scripts/promotion-smoke-test.sh)。
|
||||
脚本会产生业务写入,运行前读取参数、账号、创建和清理逻辑。
|
||||
认证链排查需核对 cookie jar、登录后 CSRF token 与实际权限,不能靠关闭安全检查让 Smoke 通过。
|
||||
报告保留命令、目标环境、版本、失败步骤及脱敏证据;trace、截图和日志不可携带会话凭证公开传播。
|
||||
57
.trellis/spec/testing/frontend-testing.md
Normal file
57
.trellis/spec/testing/frontend-testing.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# 前端测试设计
|
||||
|
||||
适用于组件、Hook、页面状态与请求行为。使用现有 Vitest、Testing Library 和 Playwright,
|
||||
工具配置以 [package.json](../../../web/package.json)、[Vite 配置](../../../web/vite.config.ts)为准。
|
||||
|
||||
## 选择边界
|
||||
|
||||
- 纯转换、校验、格式化用单元测试覆盖输入边界;不要为简单常量或文案写镜像实现的测试。
|
||||
- 组件测试从用户动作与可见结果断言,包括错误提示、禁用/重复点击、键盘与焦点;
|
||||
需要 DOM 的用例按相邻测试配置 jsdom,不假定全部测试有浏览器环境。
|
||||
- Hook 的参数映射、query key、失效范围可以隔离验证;若问题在请求竞争、缓存或重渲染,
|
||||
必须保留对应机制进行行为测试,完全 mock React Query 不能证明缓存隔离正确。
|
||||
- 浏览器布局、真实导航、认证联动进入 [E2E](./e2e.md);截图只证明捕获状态,不能证明流程完成。
|
||||
|
||||
## 异步、fixture 与隔离
|
||||
|
||||
1. 每例建立所需最小状态;清理 DOM、全局 stub、存储、计时器、监听器及 QueryClient 缓存,
|
||||
避免一个测试先运行才能通过。不同用户/命名空间用可区分的数据。
|
||||
2. 用受控 promise 驱动 A、B 请求乱序完成,断言最终显示 B;涉及取消时验证取消后不覆盖状态,
|
||||
不能只断言调用了 abort。卸载、筛选切换、退出登录按修改范围加入。
|
||||
3. 乐观更新验证成功确认和失败恢复,尤其不能把期间另一项成功修改回滚掉;没有乐观更新的流程不强加它。
|
||||
4. 需要测试时钟时只模拟所需计时器并恢复;等待可观察条件,不能加固定 sleep 掩盖竞态。
|
||||
5. fixture 保持真实类型与必要字段,复用已有 helper;测试 mock 不应成为生产类型来源。
|
||||
|
||||
先运行相关用例,再按[入口矩阵](./index.md)执行类型、lint、单测与构建。
|
||||
可见改动还需核对长内容、小屏、键盘与 reduced-motion,具体要求见[前端验收](../frontend/quality-guidelines.md)。
|
||||
|
||||
## 来源与经验边界
|
||||
|
||||
[use-promotion-list.test.ts](../../../web/src/features/promotion/use-promotion-list.test.ts)
|
||||
可参考参数与缓存键断言;目前未断言 mutation 的缓存失效,其 mock 方式也不证明 React Query 的真实竞态行为。
|
||||
这是选择测试边界的例子,不要求所有 Hook 使用同一模板。
|
||||
|
||||
## 可直接执行的三个验证切片
|
||||
|
||||
以下命令从仓库根目录运行,依赖需已按锁文件安装。只选择与此次修改相关的切片,不因这里列出就全量执行。
|
||||
|
||||
| 改动 | 命令 | 通过时具体证明什么 |
|
||||
|---|---|---|
|
||||
| 晋升列表的分页/排序/key | `pnpm -C web test src/features/promotion/use-promotion-list.test.ts` | PENDING 默认参数、历史排序参数及 ASC/DESC 键区分 |
|
||||
| 会话缓存清理 | `pnpm -C web test src/features/notification/notification-session.test.ts` | 用户键分区、指定私有缓存移除、公共搜索缓存保留 |
|
||||
| 客户端路径/运行配置 | `pnpm -C web test src/api/client.test.ts` | 当前测试覆盖的 base URL 路径前缀、端点与运行配置行为;不证明取消或授权闭环 |
|
||||
|
||||
### 会话切换:为什么只断言“调用清理函数”不够
|
||||
|
||||
实际路径为 `useSessionBootstrap.onSuccess` → `clearSessionScopedQueries` →
|
||||
`QueryClient.removeQueries`,随后才写入 `['auth', 'me']`。若新增私有查询却未纳入清理/分区机制,
|
||||
清理函数照常被调用,仍可能留下上一用户的数据。
|
||||
|
||||
[现有缓存测试](../../../web/src/features/notification/notification-session.test.ts)使用真实 QueryClient:
|
||||
先放入 A 的通知、标签和个人技能缓存,再清理,断言它们不存在,同时断言公共搜索缓存仍存在。
|
||||
修改该机制时沿用这种“清理前有数据 → 清理后私有数据消失、公共数据保留”的正反断言,
|
||||
并为新增缓存前缀补一项,而不是只检查 mock 函数调用次数。
|
||||
|
||||
当前测试使用已完成的缓存数据,不包含在途请求或真实登录流程。若改动涉及 A 请求尚未完成时
|
||||
切到 B,另用受控 Promise 和真实 QueryClient 验证迟到的 A 数据不会成为 B 的结果。
|
||||
不得把现有三个测试通过写成“账号切换、取消和竞态已全部验证”。
|
||||
53
.trellis/spec/testing/index.md
Normal file
53
.trellis/spec/testing/index.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# 测试与质量入口
|
||||
|
||||
实现后验证、测试设计、CI 或交付验收时读取。本目录说明怎样选测与执行;领域
|
||||
quality-guidelines 说明必须验证的行为,业务预期来自正式设计、接口和验收标准。
|
||||
|
||||
## 按影响选择检查
|
||||
|
||||
先运行最小相关检查获得反馈,再执行本次影响范围的交付检查。跨领域修改取并集。
|
||||
表中命令从仓库根目录运行;具体测试过滤参数按当前工具和相邻用例核对,不猜测。
|
||||
|
||||
| 改动与风险 | 检查入口 | 应留下的可观察证据 |
|
||||
|---|---|---|
|
||||
| 工程规范与索引 | `python3 scripts/check-engineering-specs.py`;检查命令来源、规则冲突 | 链接目标、迁移去向、检查范围;不是规则加载成功的证明 |
|
||||
| 仅修改既有翻译资源的显示文案 | 检查资源语法、键/插值一致性与相关界面;影响布局时检查长文本和小屏 | 显示语义与资源消费正确;规范校验器不能替代文案验证 |
|
||||
| 仅 CLI 帮助显示字符串或说明文档 | 源码帮助字符串执行相关帮助输出测试,确认用法与退出语义;仅文档检查链接和命令来源 | 实际帮助输出或文档引用;不追加无关前后端构建 |
|
||||
| 前端状态、组件、请求 | `make typecheck-web`、`make lint-web`、`make test-frontend`;交付补 `make build-frontend` | 受影响行为与错误分支通过;可见改动另做浏览器验收 |
|
||||
| 后端应用与领域规则 | `make test-backend-app` | 当前源码及依赖模块测试结果,包含适用拒绝/失败分支 |
|
||||
| 共享后端模块、构建依赖、广泛契约 | `make test-backend` | 受影响消费方和全模块结果 |
|
||||
| SQL、事务、并发、迁移 | 后端检查加[数据库与并发测试](./backend-testing.md) | 在真实依赖上观察提交、回滚、竞争与迁移;mock 不替代 |
|
||||
| API 契约或生成类型 | `make generate-api`,按需 `./scripts/check-openapi-generated.sh`;前端类型与消费方检查 | schema 差异已审查;响应与错误语义有断言 |
|
||||
| 身份、权限、操作流程 | 后端边界测试 + [真实 E2E / Smoke](./e2e.md) | 允许与拒绝、跨账号/命名空间隔离、最终持久状态 |
|
||||
| CLI | `make lint-cli`、`make typecheck-cli`、`make test-cli`、`make build-cli` | 退出码、输出、状态文件和子进程行为;见[CLI](../cli/index.md) |
|
||||
| 配置、容器与发布路径 | `make validate-release-config` + 受影响构建/Smoke | 配置生效与业务路径分开证明;见[部署](../deployment/index.md) |
|
||||
| CI 与检查脚本 | [CI 规范](./ci.md) + 代表性通过/失败/跳过输入 | 能检出错误,失败正确传递,没有误报通过 |
|
||||
|
||||
上述文案例外仅适用于不改变键、插值参数、分支、事件、样式、参数解析或生成逻辑的显示文本。
|
||||
一旦涉及这些行为或工具链,升级到对应领域检查;跨域取并集,不仅凭文件扩展名决定范围。
|
||||
文档站内容另核实际构建/CI要求。既有相关断言失败要调查修正,不能以“纯文案”为由跳过失败。
|
||||
|
||||
`make generate-api` 读取运行中后端并写生成文件。`check-openapi-generated.sh` 会启动
|
||||
Compose 依赖和后端、写生成文件,并在退出时执行 `docker compose down`;必须先检查
|
||||
目标项目、端口、已有服务和清理范围,使用隔离环境,不能当作纯静态检查。
|
||||
`make staging` 会构建并启动容器、执行可写 Smoke,亦不是静态检查。
|
||||
|
||||
## 提交前门槛
|
||||
|
||||
[AGENTS.md](../../../AGENTS.md) 与[CONTRIBUTING](../../../CONTRIBUTING.md)采用上表的按影响检查。
|
||||
纯文档执行文档验证;代码改动覆盖相关行为;跨域取并集,不将快速反馈等同完整交付验证。
|
||||
容器、运行配置或集成链路变化时追加 `make staging` 或覆盖同等受影响路径的隔离集成检查,
|
||||
明确选择依据、环境和断言。安全、权限、事务或并发行为不能只靠类型或编译通过验收。
|
||||
环境受阻时报告未执行项目及原因,不把受阻等同通过;CI必需检查仍须按实际仓库配置满足。
|
||||
|
||||
## 专题与证据
|
||||
|
||||
- [前端测试](./frontend-testing.md):组件、Hook、异步与隔离。
|
||||
- [后端测试](./backend-testing.md):领域、HTTP、真实数据库与事务。
|
||||
- [E2E 与 Smoke](./e2e.md):真实会话、数据和清理。
|
||||
- [CI](./ci.md):触发条件、状态、产物和本地检查的关系。
|
||||
- [前端验收](../frontend/quality-guidelines.md)、[后端验收](../backend/quality-guidelines.md):领域行为要求。
|
||||
|
||||
报告写实际命令、检出版本、范围、通过/失败/未运行、环境限制;不要用编译替代运行,
|
||||
用截图存在替代目视检查,用旧提交结果替代当前修改。缺陷修复的测试应能区分修复前后行为。
|
||||
来源:[Makefile](../../../Makefile)、[PR 模板](../../../.github/pull_request_template.md)。
|
||||
37
.trellis/spec/workflow/development-flow.md
Normal file
37
.trellis/spec/workflow/development-flow.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# 开发与交付流程
|
||||
|
||||
## 轻量使用分层
|
||||
|
||||
| 情况 | 使用方式 |
|
||||
|---|---|
|
||||
| 文案、小修或补规范 | 读取相关规则,直接修改验证,不强制建任务 |
|
||||
| 多步功能 | 使用任务记录保存需求、上下文、进度、验证;尊重用户已有不创建任务的选择 |
|
||||
| 大型需求或架构调整 | [OpenSpec](./openspec.md)明确正式需求设计,Trellis组织执行接续 |
|
||||
| 已有权威文档 | 引用详细契约,关键规则可在 AGENTS 重复;变更时核对入口与正文一致 |
|
||||
| 换worktree | 从本机模板显式同步可复用规则及工具配置,任务各自管理;已跟踪spec以分支内容为准,模板更新必须比较后显式应用 |
|
||||
|
||||
## 实现与交接
|
||||
|
||||
从[总索引](../index.md)选择领域与专题,无需工程 Skill 自动触发。需求不清先澄清。
|
||||
实现前明确行为差距、影响文件、来源与验证方式。实现和检查使用同一组正文,检查再追加[测试入口](../testing/index.md)。
|
||||
有任务时复用现有上下文清单;无任务时直接传递路径与范围,不补建任务。
|
||||
文件存在、Skill可见或hook配置不等于实际读取。
|
||||
|
||||
## 开发环境
|
||||
|
||||
命令以[Makefile](../../../Makefile)和[开发手册](../../../docs/dev-workflow.md)为准,版本查POM/package.json。
|
||||
启动停止先确认服务与数据,重置、推送、PR、发布不是普通检查。热更新不替代验证。
|
||||
并行环境见[手册](../../../docs/13-parallel-workflow.md),不默认操作其他实例。
|
||||
|
||||
## 检查与PR
|
||||
|
||||
按测试入口区分快速反馈与交付门槛。API变化检查消费者和生成类型,运维变化检查Smoke,文档检查引用。
|
||||
提交前检查diff、生成文件和敏感信息;不自动提交、推送、创建PR或部署。
|
||||
标题和提交采用 `<type>(<scope>): <description>`,type为feat/fix/docs/test/refactor/chore,scope为受影响领域。
|
||||
一次提交一个逻辑变化。PR说明问题、改变后的行为、必要实现细节、验证及兼容影响,参考[贡献指南](../../../CONTRIBUTING.md)与[PR模板](../../../.github/pull_request_template.md)。
|
||||
评审给适用规范和源码证据,不以个人偏好替代规则。
|
||||
|
||||
## 收尾
|
||||
|
||||
区分通过、失败、未运行和环境受阻。复用经验写所属专题,普通修复不强制新增规则。
|
||||
已有任务或清理worktree时读[归档](./task-archive.md);不存在记录不为归档补建任务。
|
||||
53
.trellis/spec/workflow/grill-with-docs.md
Normal file
53
.trellis/spec/workflow/grill-with-docs.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Grill With Docs
|
||||
|
||||
Clarify requirements when misunderstood terms, boundaries, or trade-offs would make implementation risky.
|
||||
`$grill-with-docs` is an optional local aid when installed; this workflow does not require any Skill installation.
|
||||
|
||||
## Position in the Workflow
|
||||
|
||||
Run this before OpenSpec or implementation planning when any of these are true:
|
||||
|
||||
- The user describes a large feature in broad terms.
|
||||
- Domain terms are overloaded or not yet canonical.
|
||||
- The design has several meaningful branches.
|
||||
- The decision may create ADR-worthy trade-offs.
|
||||
- Future agents will need the reasoning, not just the final tasks.
|
||||
|
||||
Recommended sequence:
|
||||
|
||||
1. Clarify the unsettled requirements directly, or use an available clarification Skill
|
||||
2. OpenSpec change for large requirements
|
||||
3. Trellis task for multi-step execution context when authorized; small edits can proceed directly
|
||||
4. Backend/frontend/workflow specs for implementation
|
||||
|
||||
## What the Skill Does
|
||||
|
||||
Some local setups compose two skills behind `$grill-with-docs`; inspect the installed entry rather than assuming it exists:
|
||||
|
||||
- `$grilling`: asks frontier questions as a decision tree until the design is no longer silently
|
||||
assuming key choices.
|
||||
- `$domain-modeling`: sharpens domain language, updates `CONTEXT.md` when terms are resolved, and
|
||||
creates ADRs only for hard-to-reverse, surprising, trade-off decisions.
|
||||
|
||||
If these skills are unavailable, clarify directly: identify the concrete user outcome, ambiguous terms,
|
||||
affected actors and systems, success/failure cases, constraints and unresolved trade-offs. Ask only questions
|
||||
whose answers materially change the work. Summarize decisions and select task size using the development flow.
|
||||
|
||||
## SkillHub Documentation Policy
|
||||
|
||||
- Do not create `CONTEXT.md` just to satisfy a process. Create it only when a SkillHub-specific term
|
||||
is resolved and worth reusing.
|
||||
- Do not create `docs/adr/` just to record a normal implementation choice. ADRs require a real,
|
||||
costly trade-off.
|
||||
- OpenSpec remains the place for formal product behavior, acceptance scenarios, and task breakdown.
|
||||
- `CONTEXT.md` is glossary only. It is not an implementation spec or scratch pad.
|
||||
|
||||
## Handoff Output
|
||||
|
||||
At the end of a grill session, capture:
|
||||
|
||||
- Settled decisions.
|
||||
- Remaining open decisions.
|
||||
- Canonical terms and avoided synonyms, if any.
|
||||
- ADR candidates accepted or explicitly skipped.
|
||||
- Whether the next artifact is an OpenSpec change, a Trellis task, or a direct edit.
|
||||
28
.trellis/spec/workflow/index.md
Normal file
28
.trellis/spec/workflow/index.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Workflow Guidelines
|
||||
|
||||
This layer explains how SkillHub chooses between direct edits, Trellis tasks, and OpenSpec changes.
|
||||
These shared conventions are readable without installing Trellis. Local execution tools are optional.
|
||||
Respect an existing user choice about task creation; do not repeatedly ask or silently create a task.
|
||||
|
||||
## Pre-Development Checklist
|
||||
|
||||
- Product capability or scope changes: read [Product Direction](../../../docs/00-product-direction.md); preserve registry-first positioning and compatibility requirements.
|
||||
- Read `.trellis/spec/index.md` for the unified routing map.
|
||||
- Read [Grill With Docs](./grill-with-docs.md) when the requirement, domain language, or design
|
||||
boundary is still fuzzy.
|
||||
- Read [OpenSpec Workflow](./openspec.md) for large requirements.
|
||||
- Read [Development Flow](./development-flow.md) for task-size routing and verification expectations.
|
||||
- For implementation work, also read the relevant domain index selected by the root routing map, including CLI when applicable.
|
||||
|
||||
## Quality Check
|
||||
|
||||
- Does this task have the right container: direct edit, Trellis task, or OpenSpec change?
|
||||
- If the requirement was fuzzy, did `$grill-with-docs` settle the decision tree before planning?
|
||||
- If an OpenSpec change exists, did implementation stay aligned with its proposal/spec/design/tasks?
|
||||
- Did verification read [Testing and Quality](../testing/index.md) and the relevant domain checks?
|
||||
- Did runtime/release work read [Deployment](../deployment/index.md) before executing commands?
|
||||
|
||||
## Task History
|
||||
|
||||
Finish/cleanup and cross-worktree history lookup follow [Local Task Archive](./task-archive.md).
|
||||
When no record exists for this task, do not create one merely to archive it. Preserve unrelated task records.
|
||||
34
.trellis/spec/workflow/openspec.md
Normal file
34
.trellis/spec/workflow/openspec.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# OpenSpec Workflow
|
||||
|
||||
OpenSpec remains the formal workflow for large SkillHub requirements.
|
||||
|
||||
Authoritative source:
|
||||
|
||||
- `openspec/`
|
||||
- Existing example: `openspec/changes/add-skill-suites/`
|
||||
|
||||
## When to Use OpenSpec
|
||||
|
||||
Use or create `openspec/changes/<change-id>/` for large requirements or architecture decisions, such as:
|
||||
|
||||
- A substantial new product capability or large user-facing workflow.
|
||||
- Architecture or module-boundary change.
|
||||
- Breaking or broad public API, persistence, security, or lifecycle changes requiring coordinated design and acceptance.
|
||||
|
||||
An ordinary multi-step feature uses task records; changing one endpoint or adding one test does not
|
||||
automatically require a new OpenSpec change. If an accepted OpenSpec already owns the affected behavior,
|
||||
update that source rather than creating a competing contract. Preserve the user's existing task-creation choice.
|
||||
|
||||
## Expected Artifacts
|
||||
|
||||
Each large change should have:
|
||||
|
||||
- `proposal.md`: why the change exists and what problem it solves.
|
||||
- `specs/<capability>/spec.md`: behavior deltas and acceptance scenarios.
|
||||
- `design.md`: technical approach and important trade-offs.
|
||||
- `tasks.md`: implementation and verification checklist.
|
||||
|
||||
## Trellis Relationship
|
||||
|
||||
Trellis tasks may link to an OpenSpec change. Trellis should not replace OpenSpec for formal
|
||||
requirements. If task work changes accepted behavior, update the OpenSpec source first.
|
||||
14
.trellis/spec/workflow/task-archive.md
Normal file
14
.trellis/spec/workflow/task-archive.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# 任务记录与归档
|
||||
|
||||
适用于已有任务的收尾和worktree清理;任务、运行状态、个人报告不属于公开规范。
|
||||
|
||||
- 按已配置的本地执行入口读取归档工具说明,保存当前单个任务的摘要、原始记录与证据链接。
|
||||
- 摘要说明结果、仓库/分支、提交或PR(若有)、验证范围与未完成项;未提交和未验证如实记录。
|
||||
- 清理前保存到worktree外,验证目标可读、内容完整。失败保留源记录,不继续依赖归档成功的清理。
|
||||
- 检查凭证和私人数据;链接报告优于复制源码和环境文件。归档不授予提交、推送或删除权限。
|
||||
- 稳定任务写入后再快照,不覆盖同标识但不同内容的记录。
|
||||
- 未配置工具时不假设本机命令存在,说明缺口并保留原记录。没有任务记录不强制创建。
|
||||
|
||||
工具路径、参数和私有档案由本地工作流或执行Skill配置,不成为公开规范硬依赖。
|
||||
原生archive可能修改状态、移动目录、触发hooks或提交;运行前核对,不把它等同跨worktree备份。
|
||||
验证观察已保存摘要和原始记录;Git clean不证明忽略的任务文件已经保存。
|
||||
692
AGENTS.md
692
AGENTS.md
|
|
@ -1,593 +1,99 @@
|
|||
# SkillHub — AGENTS.md
|
||||
|
||||
**SkillHub** is an **enterprise-grade, self-hosted agent skill registry** for publishing,
|
||||
discovering, and managing reusable skill packages across an organization. It provides a **REST API
|
||||
backend**, a **React web UI**, a **security scanner**, and a **ClawHub CLI compatibility layer**.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Item | Value |
|
||||
|------------|------------------------------------------------------------|
|
||||
| Backend | Spring Boot 3.2.3, Java 21, Maven multi-module (7 modules) |
|
||||
| Frontend | React 19, TypeScript, Vite, pnpm |
|
||||
| Scanner | Python (FastAPI), port 8000 |
|
||||
| Database | PostgreSQL 16 (Flyway migrations) |
|
||||
| Cache | Redis 7 (sessions, distributed locks, idempotency) |
|
||||
| Storage | LocalFile (dev) / S3/MinIO (prod) |
|
||||
| Build | `make dev-all` (dev), `make staging` (pre-PR) |
|
||||
| Docs | `docs/` (design), `docs/skillhub/` (VitePress user guide) |
|
||||
| CI | GitHub Actions (`.github/workflows/`) |
|
||||
|
||||
## Directory Map
|
||||
|
||||
```
|
||||
skillhub/
|
||||
├── server/ # Maven multi-module Spring Boot backend
|
||||
│ ├── skillhub-app/ # Application layer: bootstrap, controllers, assembly
|
||||
│ │ ├── bootstrap/ # Bootstrap admin & local dev data initializers
|
||||
│ │ ├── compat/ # ClawHub CLI compatibility layer controllers
|
||||
│ │ ├── config/ # Spring configuration classes
|
||||
│ │ ├── controller/ # REST controllers (transport only)
|
||||
│ │ │ ├── admin/ # Admin controllers (user mgmt, labels, search)
|
||||
│ │ │ ├── portal/ # Portal controllers (skills, governance, security)
|
||||
│ │ │ └── support/ # Package extractors (zip, multipart)
|
||||
│ │ ├── dto/ # Request/response DTOs
|
||||
│ │ ├── exception/ # Exception handling
|
||||
│ │ ├── filter/ # Servlet filters (auth context, rate limiting)
|
||||
│ │ ├── listener/ # Event listeners (notification recipients, etc.)
|
||||
│ │ ├── metrics/ # Micrometer metrics
|
||||
│ │ ├── projection/ # Lifecycle projection models
|
||||
│ │ ├── ratelimit/ # Rate limiting logic
|
||||
│ │ ├── repository/ # Query repositories (read-model assembly)
|
||||
│ │ ├── security/ # Security configuration
|
||||
│ │ ├── service/ # App services (workflow orchestration)
|
||||
│ │ ├── stream/ # SSE streaming endpoints
|
||||
│ │ ├── task/ # Background task scheduling
|
||||
│ │ └── SkillhubApplication.java # Spring Boot entry point
|
||||
│ │
|
||||
│ ├── skillhub-domain/ # Domain layer: entities, rules, services (innermost)
|
||||
│ │ ├── audit/ # AuditLog entity, repository, service
|
||||
│ │ ├── auth/ # Password reset entities
|
||||
│ │ ├── event/ # Domain event classes (SkillPublishedEvent, etc.)
|
||||
│ │ ├── governance/ # Governance notification service
|
||||
│ │ ├── idempotency/ # Idempotency records
|
||||
│ │ ├── label/ # Skill label management
|
||||
│ │ ├── namespace/ # Namespace, members, roles, policies
|
||||
│ │ ├── report/ # Skill reporting/governance
|
||||
│ │ ├── review/ # Review tasks, promotion requests
|
||||
│ │ ├── security/ # Security scanning domain model
|
||||
│ │ ├── shared/ # Shared domain utilities
|
||||
│ │ │ └── exception/ # Domain exceptions (LocalizedDomainException, etc.)
|
||||
│ │ ├── skill/ # Core skill entities and services
|
||||
│ │ │ ├── metadata/ # SKILL.md frontmatter parsing
|
||||
│ │ │ ├── service/ # Skill domain services (publish, query, governance)
|
||||
│ │ │ └── validation/ # Package validation (SkillPackagePolicy, etc.)
|
||||
│ │ ├── social/ # Star, rating, subscription entities
|
||||
│ │ └── user/ # UserAccount, profile moderation
|
||||
│ │
|
||||
│ ├── skillhub-auth/ # Authentication & authorization
|
||||
│ │ ├── config/ # Spring Security configuration
|
||||
│ │ ├── device/ # OAuth Device Flow for CLI auth
|
||||
│ │ ├── identity/ # Identity binding service
|
||||
│ │ ├── local/ # Local (password) auth
|
||||
│ │ ├── merge/ # Account merging
|
||||
│ │ ├── oauth/ # OAuth2 login handlers
|
||||
│ │ ├── policy/ # Route security policies
|
||||
│ │ ├── rbac/ # RBAC service and role definitions
|
||||
│ │ ├── token/ # API token management
|
||||
│ │ └── user/ # User-related auth services
|
||||
│ │
|
||||
│ ├── skillhub-search/ # Search SPI + PostgreSQL full-text implementation
|
||||
│ │ ├── postgres/ # PostgresFullTextIndexService, QueryService
|
||||
│ │ └── service/ # Search SPI interfaces
|
||||
│ │
|
||||
│ ├── skillhub-storage/ # Object storage SPI
|
||||
│ │ ├── local/ # LocalFileStorageService
|
||||
│ │ └── s3/ # S3StorageService (AWS SDK v2)
|
||||
│ │
|
||||
│ ├── skillhub-infra/ # Infrastructure: JPA repos, utilities
|
||||
│ │ └── repository/ # Spring Data JPA repository implementations
|
||||
│ │
|
||||
│ ├── skillhub-notification/ # Notification service (SSE, email)
|
||||
│ │ ├── domain/ # Notification domain model
|
||||
│ │ ├── service/ # Notification delivery services
|
||||
│ │ └── sse/ # SSE endpoint support
|
||||
│ │
|
||||
│ ├── Dockerfile.dev # Dockerfile for staging builds
|
||||
│ ├── Dockerfile # Production multi-stage build
|
||||
│ ├── pom.xml # Parent POM (Spring Boot 3.2.3 parent)
|
||||
│ └── scripts/
|
||||
│ └── run-dev-app.sh # Local dev startup script
|
||||
│
|
||||
├── web/ # React frontend (Vite + pnpm)
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # OpenAPI-generated types + fetch client
|
||||
│ │ │ └── generated/
|
||||
│ │ │ └── schema.d.ts # Generated OpenAPI types (CHECKED IN)
|
||||
│ │ ├── app/ # Router, layout, global providers
|
||||
│ │ ├── docs/ # In-app documentation pages
|
||||
│ │ ├── entities/ # Domain entity display logic
|
||||
│ │ │ ├── skill/ # Skill card, detail components
|
||||
│ │ │ ├── user/ # User profile components
|
||||
│ │ │ └── namespace/ # Namespace display components
|
||||
│ │ ├── features/ # Business feature modules
|
||||
│ │ │ ├── admin/ # Admin panel features
|
||||
│ │ │ ├── auth/ # Login, OAuth flows
|
||||
│ │ │ ├── governance/ # Skill governance actions
|
||||
│ │ │ ├── namespace/ # Namespace management
|
||||
│ │ │ ├── notification/ # User notifications
|
||||
│ │ │ ├── promotion/ # Skill promotion workflows
|
||||
│ │ │ ├── publish/ # Skill upload/publish UI
|
||||
│ │ │ ├── report/ # Skill reporting
|
||||
│ │ │ ├── review/ # Review workflow UI
|
||||
│ │ │ ├── search/ # Skill search and filtering
|
||||
│ │ │ ├── security-audit/ # Security audit viewer
|
||||
│ │ │ ├── skill/ # Skill detail, listing
|
||||
│ │ │ ├── social/ # Stars, ratings, subscriptions
|
||||
│ │ │ └── token/ # API token management
|
||||
│ │ ├── i18n/ # Internationalization
|
||||
│ │ ├── pages/ # Route-level page components
|
||||
│ │ ├── shared/ # Shared UI, hooks, utilities
|
||||
│ │ │ ├── components/ # Reusable UI components
|
||||
│ │ │ ├── hooks/ # Custom React hooks
|
||||
│ │ │ ├── lib/
|
||||
│ │ │ │ └── utils.ts # cn() class merging utility
|
||||
│ │ │ └── ui/ # Radix UI-based primitives
|
||||
│ │ └── types/ # Additional TypeScript types
|
||||
│ ├── e2e/ # Playwright E2E tests
|
||||
│ ├── nginx.conf.template # Nginx runtime config template
|
||||
│ ├── Dockerfile # Multi-stage build (Node → Nginx)
|
||||
│ └── package.json # Dependencies (React 19, TanStack Query, Radix UI, etc.)
|
||||
│
|
||||
├── scanner/ # Security scanner (Python/FastAPI)
|
||||
│ ├── docs/ # Scanner documentation
|
||||
│ ├── examples/ # Example scan inputs/outputs
|
||||
│ └── Dockerfile # Scanner container build
|
||||
│
|
||||
├── docs/ # Design documents (source of truth)
|
||||
│ ├── prds/ # Product requirement documents
|
||||
│ ├── skillhub/ # VitePress user guide source
|
||||
│ └── superpowers/ # Internal tooling docs
|
||||
│
|
||||
├── deploy/k8s/ # Kubernetes manifests (basic)
|
||||
├── monitoring/ # Prometheus + Grafana stack
|
||||
├── scripts/ # Build, test, and deployment scripts
|
||||
│ ├── smoke-test.sh # Basic API smoke test
|
||||
│ ├── namespace-smoke-test.sh # Namespace workflow smoke test
|
||||
│ ├── governance-smoke-test.sh # Governance flow smoke test
|
||||
│ ├── promotion-smoke-test.sh # Promotion flow smoke test
|
||||
│ ├── check-openapi-generated.sh # Verify OpenAPI SDK is not stale
|
||||
│ ├── validate-release-config.sh # Validate release env configuration
|
||||
│ ├── dev-process.sh # Local process manager (PID-based)
|
||||
│ ├── runtime.sh # Runtime deployment script
|
||||
│ ├── parallel-init.sh # Parallel worktree initialization
|
||||
│ ├── parallel-sync.sh # Merge worktrees in integration branch
|
||||
│ ├── parallel-up.sh # Merge + start dev environment
|
||||
│ ├── parallel-down.sh # Stop parallel dev environment
|
||||
│ └── prepare-pr-batch.sh # Batch PR preparation
|
||||
│
|
||||
├── .github/
|
||||
│ ├── workflows/ # GitHub Actions CI/CD
|
||||
│ ├── ISSUE_TEMPLATE/ # Issue templates
|
||||
│ └── scripts/ # Deno scripts for triage, release notes, rewards
|
||||
│
|
||||
├── AGENTS.md # AI agent rules (this file)
|
||||
├── .agents/skills/ # Focused AI skill definitions
|
||||
├── Makefile # Top-level build/test/dev orchestration
|
||||
├── docker-compose.yml # Local dev dependency services
|
||||
├── compose.release.yml # Production release compose file
|
||||
├── CONTRIBUTING.md # Contribution guidelines
|
||||
├── CODE_OF_CONDUCT.md # Community standards
|
||||
└── README.md # Project overview
|
||||
```
|
||||
|
||||
**Key Locations for Common Tasks:**
|
||||
|
||||
| Task | Where to Look |
|
||||
|------|---------------|
|
||||
| Add REST endpoint | `server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/` |
|
||||
| Add domain entity/service | `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/` |
|
||||
| Add auth logic | `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/` |
|
||||
| Add search logic | `server/skillhub-search/src/main/java/com/iflytek/skillhub/search/` |
|
||||
| Add query repository | `server/skillhub-app/src/main/java/com/iflytek/skillhub/repository/` |
|
||||
| Change RBAC/roles | `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/rbac/` |
|
||||
| Change skill validation | `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/validation/` |
|
||||
| Add frontend page | `web/src/pages/` |
|
||||
| Add frontend feature | `web/src/features/` |
|
||||
| Add shared component | `web/src/shared/components/` |
|
||||
| Change API contract | Backend controller → run `make generate-api` → commit generated file |
|
||||
| Add smoke test | `scripts/` (new `.sh` file) |
|
||||
| Add E2E test | `web/e2e/` (Playwright) |
|
||||
| Add backend test | `server/skillhub-*/src/test/java/` (alongside source module) |
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### Do Not Manually Edit Generated Files
|
||||
|
||||
- `web/src/api/generated/schema.d.ts` — regenerated via `make generate-api`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/dto/` — some DTOs may be generated
|
||||
|
||||
### After Making Changes
|
||||
|
||||
**Backend changes:**
|
||||
- Edit Java code → `make dev-server-restart` (local dev)
|
||||
- Add/modify controller → `make generate-api` to regenerate frontend types
|
||||
- Add/modify domain service → `make test-backend-app` to verify tests
|
||||
|
||||
**Frontend changes:**
|
||||
- Edit TypeScript/React → Vite HMR handles reload automatically
|
||||
- After `make generate-api` → commit updated `web/src/api/generated/schema.d.ts`
|
||||
|
||||
**Always run before PR:**
|
||||
```bash
|
||||
make test-backend-app # Backend tests (with dependent modules)
|
||||
make typecheck-web # Frontend type check
|
||||
make lint-web # Frontend lint
|
||||
make staging # Full staging regression + smoke test
|
||||
```
|
||||
|
||||
### File-Specific Requirements
|
||||
|
||||
- **Controllers** (`skillhub-app/controller/`) are transport only: extract auth context,
|
||||
bind request params, wrap responses. No business logic.
|
||||
- **App Services** (`skillhub-app/service/`) orchestrate workflows. Do not embed complex
|
||||
read-model assembly here — extract to query repositories.
|
||||
- **Query Repositories** (`skillhub-app/repository/`) handle read-model joins and presentation
|
||||
projection. Named like `*QueryRepository`.
|
||||
- **Domain Services** (`skillhub-domain/*/service/`) contain business rules and state transitions.
|
||||
Return domain objects, not DTOs.
|
||||
- **Repository Interfaces** are defined in `skillhub-domain`, implemented in `skillhub-infra`.
|
||||
- **Domain Exceptions** use `LocalizedDomainException` for user-facing messages with i18n keys.
|
||||
- **Package-info files** (`package-info.java`) should exist for all packages.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Build & Start
|
||||
|
||||
```bash
|
||||
make dev-all # Start full stack: Postgres, Redis, MinIO, backend, frontend
|
||||
make dev-all-down # Stop everything
|
||||
make dev-all-reset # Full reset (clears data volumes)
|
||||
make dev-status # Check service status
|
||||
make dev-server-restart # Restart backend after Java changes
|
||||
```
|
||||
|
||||
**Access points:**
|
||||
- Web UI: `http://localhost:3000`
|
||||
- Backend API: `http://localhost:8080`
|
||||
- Scanner: `http://localhost:8000`
|
||||
|
||||
**Local mock users** (no password needed):
|
||||
|
||||
| User ID | Role | Header |
|
||||
|---------|------|--------|
|
||||
| `local-user` | Regular user | `X-Mock-User-Id: local-user` |
|
||||
| `local-admin` | Super admin | `X-Mock-User-Id: local-admin` |
|
||||
|
||||
**Bootstrap admin** (password-based, local profile):
|
||||
- Username: `admin` / Password: `ChangeMe!2026`
|
||||
- Disable with `BOOTSTRAP_ADMIN_ENABLED=false`
|
||||
|
||||
### Lint & Format
|
||||
|
||||
```bash
|
||||
# Backend: enforced by Maven build (no separate lint target)
|
||||
# Frontend:
|
||||
make lint-web # ESLint check
|
||||
make typecheck-web # TypeScript check
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
make test-backend-app # Backend unit tests (skillhub-app + dependencies)
|
||||
make test-backend # All backend module tests
|
||||
make test-frontend # Frontend unit tests (Vitest)
|
||||
make test-e2e-frontend # Frontend E2E tests (Playwright)
|
||||
make test-e2e-smoke-frontend # Frontend E2E smoke tests
|
||||
./scripts/smoke-test.sh # API smoke test
|
||||
make namespace-smoke # Namespace workflow smoke test
|
||||
```
|
||||
|
||||
### Staging (Pre-PR Regression)
|
||||
|
||||
```bash
|
||||
make staging # Build backend Docker image + frontend static + smoke test
|
||||
make staging-down # Tear down
|
||||
SERVICE=web make staging-logs # View Nginx logs
|
||||
```
|
||||
|
||||
Staging validates the containerized deployment path:
|
||||
- Backend: built as Docker image from local source
|
||||
- Frontend: built as static files, served by Nginx
|
||||
- Dependencies: same Postgres/Redis/MinIO as local dev
|
||||
|
||||
### Parallel Agent Workflow
|
||||
|
||||
For parallel development with isolated worktrees:
|
||||
|
||||
```bash
|
||||
make parallel-init TASK=feature-name
|
||||
```
|
||||
|
||||
Creates dedicated Claude, Codex, and integration worktrees as sibling directories.
|
||||
See `docs/13-parallel-workflow.md` for details.
|
||||
|
||||
## PR Submission
|
||||
|
||||
### PR Title Format
|
||||
|
||||
Use conventional commit style:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation changes
|
||||
- `test`: Adding or updating tests
|
||||
- `refactor`: Code restructuring (no behavior change)
|
||||
- `chore`: Build, CI, or maintenance tasks
|
||||
|
||||
**Scopes:** Module or domain name (e.g., `auth`, `search`, `publish`, `review`, `namespace`)
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
feat(auth): add local account login
|
||||
fix(publish): resolve null pointer in skill validation
|
||||
docs(deploy): clarify runtime image usage
|
||||
test(namespace): add membership service tests
|
||||
refactor(review): extract query repository for governance list
|
||||
chore(ci): add parallel workflow scripts
|
||||
```
|
||||
|
||||
### Pre-PR Checklist
|
||||
|
||||
- [ ] Backend tests pass: `make test-backend-app`
|
||||
- [ ] Frontend typecheck passes: `make typecheck-web`
|
||||
- [ ] If API changed: `make generate-api` was run and `web/src/api/generated/schema.d.ts` is committed
|
||||
- [ ] Smoke test passes: `make staging`
|
||||
- [ ] Follow existing module boundaries and dependency direction
|
||||
- [ ] Add/update tests for new behavior
|
||||
- [ ] Update docs when APIs, auth flows, deployment, or operator workflows change
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Backend Clean Architecture
|
||||
|
||||
```
|
||||
app → domain, auth, search, storage, infra, notification
|
||||
infra → domain # implements domain repository interfaces
|
||||
auth → domain
|
||||
search → domain
|
||||
notification → domain
|
||||
storage → (independent) # pure SPI
|
||||
```
|
||||
|
||||
**Design intent**: `skillhub-domain` is the innermost layer. It defines entities, repository
|
||||
interfaces, and domain services without depending on infra, auth, search, or storage.
|
||||
|
||||
**Code reality**: `skillhub-domain` declares a Maven dependency on `skillhub-storage`, and several
|
||||
domain services (`SkillHardDeleteService`, `SkillDownloadService`, `SkillPublishService`,
|
||||
`SkillGovernanceService`, `SkillQueryService`, `SkillStorageDeletionCompensationService`) import
|
||||
`com.iflytek.skillhub.storage.ObjectStorageService`. This is an existing deviation from the ideal.
|
||||
New code should avoid adding further cross-module dependencies from domain.
|
||||
|
||||
### Repository / Query Boundary
|
||||
|
||||
When adding new read logic, follow these rules:
|
||||
|
||||
1. **Domain repository ports** (`skillhub-domain`): Aggregate reads, state transitions, rule
|
||||
evaluation. Used by domain services.
|
||||
2. **App query repositories** (`com.iflytek.skillhub.repository`): Read-model assembly that joins
|
||||
multiple sources, presentation projection. Used by controllers and app services.
|
||||
3. **App services** (`com.iflytek.skillhub.service`): Workflow orchestration. Should express "what
|
||||
this endpoint does", not "how it assembles DTOs".
|
||||
4. **Direct SQL / EntityManager**: Only when necessary, with class-level comment explaining why.
|
||||
|
||||
**Do not** add complex read-model assembly logic inside app services. Extract it into a query
|
||||
repository when it joins multiple sources, does presentation projection, or is reused across services.
|
||||
|
||||
### Skill Lifecycle
|
||||
|
||||
`SkillVersionStatus` values: `DRAFT`, `SCANNING`, `SCAN_FAILED`, `UPLOADED`, `PENDING_REVIEW`,
|
||||
`PUBLISHED`, `REJECTED`, `YANKED`.
|
||||
|
||||
`SkillStatus` enum values: `ACTIVE`, `HIDDEN`, `ARCHIVED`.
|
||||
|
||||
The design doc (`docs/14-skill-lifecycle.md`) specifies that `hidden` should be treated as a
|
||||
governance overlay rather than a lifecycle state. The current code still defines
|
||||
`SkillStatus.HIDDEN` in the enum. Follow the design doc's intent for new code.
|
||||
|
||||
**Key transitions:**
|
||||
- Normal user first upload → `PENDING_REVIEW` (no initial DRAFT)
|
||||
- SUPER_ADMIN first upload → `PUBLISHED` (direct publish)
|
||||
- Review approve → `PENDING_REVIEW` → `PUBLISHED` (updates `latestVersionId`)
|
||||
- Review reject → `PENDING_REVIEW` → `REJECTED`
|
||||
- Withdraw review → `PENDING_REVIEW` → `UPLOADED` (also deletes pending review_task)
|
||||
- Yank → `PUBLISHED` → `YANKED` (must recalculate `latestVersionId`)
|
||||
- Hide/restore → independent `hidden` flag (governance overlay)
|
||||
- Archive/Unarchive → `ACTIVE` ↔ `ARCHIVED` (container state)
|
||||
|
||||
### Namespace Coordinate System
|
||||
|
||||
SkillHub uses `@{namespace_slug}/{skill_slug}`:
|
||||
- `@global/my-skill` — Platform-level public namespace
|
||||
- `@my-team/my-skill` — Team/department namespace
|
||||
|
||||
ClawHub CLI compatibility maps:
|
||||
| SkillHub | Canonical Slug |
|
||||
|----------|---------------|
|
||||
| `@global/my-skill` | `my-skill` |
|
||||
| `@team-name/my-skill` | `team-name--my-skill` |
|
||||
|
||||
### Authentication
|
||||
|
||||
- Web: OAuth2 (GitHub) + local password auth
|
||||
- CLI: OAuth Device Flow (web authorization → CLI credentials)
|
||||
- Programmatic: API tokens (prefix-based secure hashing)
|
||||
- Session: Spring Session + Redis
|
||||
|
||||
### RBAC
|
||||
|
||||
Platform roles: `SUPER_ADMIN`, `SKILL_ADMIN`, `USER_ADMIN`, `AUDITOR`
|
||||
Namespace roles: `OWNER`, `ADMIN`, `MEMBER`
|
||||
|
||||
### Skill Package Protocol
|
||||
|
||||
- Root: `SKILL.md` with YAML frontmatter (`name`, `description` required)
|
||||
- Allowed extensions (50+ types): `.md`, `.txt`, `.json`, `.yaml`, `.yml`, `.js`, `.ts`, `.py`,
|
||||
`.sh`, `.png`, `.jpg`, `.svg`, and many more (see `SkillPackagePolicy.ALLOWED_EXTENSIONS`)
|
||||
- Limits: 10MB per file, 100MB total, 500 files max
|
||||
- File type signatures validated (PNG magic bytes, SVG content check, etc.)
|
||||
|
||||
### Frontend State Management
|
||||
|
||||
- **TanStack Query** (`@tanstack/react-query`): All server state (API data)
|
||||
- **Zustand**: Local/UI state (theme, sidebar, modals)
|
||||
- **Never** use `useEffect` for data fetching
|
||||
|
||||
### Frontend Component Composition
|
||||
|
||||
- **Radix UI** primitives: `@radix-ui/react-dropdown-menu`, `@radix-ui/react-select`
|
||||
- **class-variance-authority** (cva) for component variants
|
||||
- **clsx** + **tailwind-merge** for class merging
|
||||
- **`cn()` utility**: `web/src/shared/lib/utils.ts`
|
||||
- shadcn/ui is NOT used as a library — only Radix primitives + utility composition
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Code Style
|
||||
|
||||
**Java:**
|
||||
- User identity type is always `String` throughout the codebase
|
||||
- Use Java 21 features (records, pattern matching, virtual threads)
|
||||
- Follow existing naming patterns in the domain layer
|
||||
- Error strings for `DomainBadRequestException`, etc. should be clear and actionable
|
||||
|
||||
**TypeScript:**
|
||||
- Strict mode. No `any` types.
|
||||
- Use generated OpenAPI types for all API interactions.
|
||||
- Feature-Sliced Design: place code at the lowest appropriate layer.
|
||||
|
||||
### Testing Philosophy
|
||||
|
||||
- Backend: JUnit 5 + Mockito + AssertJ
|
||||
- Frontend: Vitest for unit tests, Playwright for E2E
|
||||
- **Use `make test-backend-app`** (includes `-am` for dependent modules) — never run
|
||||
`./mvnw -pl skillhub-app clean test` directly, as it can use stale Maven cache artifacts
|
||||
- Test behaviors, not implementations
|
||||
- Use Spring Boot test slices where possible (`@WebMvcTest`, `@DataJpaTest`)
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
```bash
|
||||
make test-frontend # Vitest unit tests
|
||||
make test-e2e-frontend # Playwright E2E
|
||||
make test-e2e-smoke-frontend # Playwright smoke (subset of E2E)
|
||||
```
|
||||
|
||||
### Logging Conventions
|
||||
|
||||
- **Backend**: SLF4J + Spring Boot logging. Use structured logging with MDC for request tracing.
|
||||
- **Frontend**: `console.error` for errors, `console.warn` for deprecations, avoid `console.log` in production code.
|
||||
- **Scanner**: Python logging module with structured JSON output.
|
||||
|
||||
### Security
|
||||
|
||||
- API tokens are stored as prefix-based secure hashes, never in plaintext
|
||||
- OAuth2 client secrets and other secrets must not be logged or committed
|
||||
- User identity is `String` (supports external SSO/OIDC/SCIM identity sources)
|
||||
- The bootstrap admin (`BOOTSTRAP_ADMIN_ENABLED`) is for zero-config quickstart only
|
||||
|
||||
## Search Tips
|
||||
|
||||
```bash
|
||||
# Find all REST endpoints
|
||||
rg "@(Get|Post|Put|Delete|Patch)Mapping" --type java
|
||||
|
||||
# Find domain services
|
||||
rg "class.*Service" server/skillhub-domain/
|
||||
|
||||
# Find query repositories
|
||||
rg "QueryRepository" server/skillhub-app/
|
||||
|
||||
# Find controllers
|
||||
rg "@RestController" server/skillhub-app/
|
||||
|
||||
# Find RBAC role checks
|
||||
rg "@PreAuthorize" server/skillhub-app/
|
||||
|
||||
# Find skill validation logic
|
||||
rg "SkillPackage" server/skillhub-domain/
|
||||
|
||||
# Find frontend features
|
||||
rg "export" web/src/features/
|
||||
|
||||
# Find OpenAPI type generation script
|
||||
rg "generate-api" web/package.json
|
||||
|
||||
# Find event listeners
|
||||
rg "@EventListener" server/
|
||||
```
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
- **Hub first**: The server-side registry is the core product; CLI and agent integrations are entry capabilities
|
||||
- **Compatibility first**: Support `SKILL.md` format and common directory conventions
|
||||
- **Layered architecture**: Search and object storage must have replaceable boundaries (SPI pattern)
|
||||
- **Open authentication**: OAuth2-based, extensible to multiple providers beyond GitHub
|
||||
- **Audit first**: Enterprise distribution requires audit trails for publish, download, delete, and authorization
|
||||
|
||||
## References
|
||||
|
||||
### Essential Files
|
||||
- **`Makefile`** — All build/test/dev automation targets
|
||||
- **`CONTRIBUTING.md`** — Contribution guidelines and commit style
|
||||
- **`CODE_OF_CONDUCT.md`** — Community standards
|
||||
- **`server/pom.xml`** — Maven parent POM, module definitions, dependency versions
|
||||
- **`web/package.json`** — Frontend dependencies and scripts
|
||||
- **`.github/workflows/pr-tests.yml`** — PR test pipeline
|
||||
- **`.github/workflows/publish-images.yml`** — Docker image publish to GHCR
|
||||
|
||||
### Key Directories
|
||||
- **`server/skillhub-domain/`** — Core domain (entities, services, rules)
|
||||
- **`server/skillhub-app/controller/`** — REST API endpoints
|
||||
- **`server/skillhub-app/repository/`** — Query repositories
|
||||
- **`server/skillhub-app/compat/`** — ClawHub CLI compatibility layer
|
||||
- **`server/skillhub-auth/`** — Authentication and authorization
|
||||
- **`web/src/features/`** — Frontend feature modules
|
||||
- **`web/src/api/generated/`** — Generated OpenAPI types
|
||||
- **`docs/`** — Design documents
|
||||
- **`scripts/`** — Build, test, and deployment scripts
|
||||
|
||||
### Important Scripts
|
||||
- **`scripts/smoke-test.sh`** — Basic API smoke test
|
||||
- **`scripts/namespace-smoke-test.sh`** — Namespace workflow test
|
||||
- **`scripts/check-openapi-generated.sh`** — Verify frontend SDK is current
|
||||
- **`scripts/validate-release-config.sh`** — Validate production env config
|
||||
- **`scripts/dev-process.sh`** — Local process manager (PID-based lifecycle)
|
||||
- **`scripts/parallel-init.sh`** — Create isolated worktrees for parallel development
|
||||
|
||||
### Design Documents
|
||||
- **`00-product-direction.md`** — Product positioning, MVP scope, coordinate system
|
||||
- **`01-system-architecture.md`** — System architecture, module structure, dependency rules
|
||||
- **`02-domain-model.md`** — Domain entities and relationships
|
||||
- **`03-authentication-design.md`** — OAuth2, CLI Device Flow, API tokens
|
||||
- **`04-search-architecture.md`** — Search SPI and implementations
|
||||
- **`05-business-flows.md`** — Business process flows
|
||||
- **`06-api-design.md`** — API contract specifications
|
||||
- **`07-skill-protocol.md`** — SKILL.md format, package structure, CLI compatibility
|
||||
- **`08-frontend-architecture.md`** — Frontend patterns and conventions
|
||||
- **`14-skill-lifecycle.md`** — Skill state model (authoritative)
|
||||
- **`dev-workflow.md`** — Local development workflow guide
|
||||
|
||||
### External Resources
|
||||
- **SkillHub Docs**: https://iflytek.github.io/skillhub/
|
||||
- **DeepWiki**: https://deepwiki.com/iflytek/skillhub
|
||||
- **Discord**: https://discord.gg/qHYvtDNPHS
|
||||
- **OpenSkills**: https://agents.md/ (skill package format reference)
|
||||
- **OpenClaw**: https://github.com/openclaw/openclaw (CLI compatibility)
|
||||
- **AstronClaw**: https://agent.xfyun.cn/astron-claw (cloud AI assistant integration)
|
||||
# SkillHub — Agent Entry
|
||||
|
||||
SkillHub includes a Java backend, React web UI, CLI and security-scanning integration.
|
||||
Read [README](./README.md) for setup and [CONTRIBUTING](./CONTRIBUTING.md) for contributions.
|
||||
|
||||
## Read before changing code
|
||||
|
||||
1. Start with [.trellis/spec/index.md](./.trellis/spec/index.md).
|
||||
2. Read only the applicable domain index and the topic documents selected by the change.
|
||||
3. Implementation and review use the same applicable contracts; add the testing guidance for verification.
|
||||
4. Read actual source, tests, schemas and scripts before treating documentation as implemented behavior.
|
||||
Report conflicts and known deviations; do not silently redefine business policy.
|
||||
|
||||
| Change | Entry |
|
||||
|---|---|
|
||||
| Frontend UI, state, requests, types | [Frontend](./.trellis/spec/frontend/index.md) |
|
||||
| Backend modules, APIs, persistence, scanning | [Backend](./.trellis/spec/backend/index.md) |
|
||||
| Tests, CI, acceptance | [Testing](./.trellis/spec/testing/index.md) |
|
||||
| Configuration, containers, release, migrations | [Deployment](./.trellis/spec/deployment/index.md) |
|
||||
| CLI | [CLI](./.trellis/spec/cli/index.md) |
|
||||
| Development, PR, completion | [Workflow](./.trellis/spec/workflow/index.md) |
|
||||
| Reuse or cross-layer decisions | [Thinking guides](./.trellis/spec/guides/index.md) |
|
||||
|
||||
## Core project rules
|
||||
|
||||
- The registry is the core product; CLI and agent integrations are entry points.
|
||||
- Preserve SKILL.md package compatibility and existing namespace/CLI contracts.
|
||||
- Keep search and object storage behind replaceable interfaces.
|
||||
- Authentication must remain extensible beyond a single identity provider.
|
||||
- Changes to publishing, downloading, deletion or authorization must check
|
||||
that the required audit trail is preserved.
|
||||
|
||||
## Backend essentials
|
||||
|
||||
- Controllers handle transport and trusted identity extraction; application
|
||||
services orchestrate use cases; domain services own business rules.
|
||||
- Put complex joins and presentation projections in app query repositories.
|
||||
Domain services return domain objects, not app DTOs.
|
||||
- Domain repository interfaces belong in domain; persistence adapters belong
|
||||
in infra. Do not introduce new dependencies that violate module boundaries.
|
||||
- The existing domain-to-storage dependency is a known deviation, not a
|
||||
precedent for adding further cross-module dependencies.
|
||||
- User identity is String throughout authentication, APIs and persistence.
|
||||
This does not imply that every resource ID is String.
|
||||
- Use existing localized domain exceptions and request-tracing facilities.
|
||||
- Store API tokens using the existing secure hashing mechanism, never plaintext.
|
||||
|
||||
## Frontend essentials
|
||||
|
||||
- Use TanStack Query for server state and Zustand/local state for UI state.
|
||||
Do not introduce useEffect-based API fetching.
|
||||
- Reuse existing shared UI components, variants and cn() utilities.
|
||||
- Use generated API types; do not hand-edit generated schema files.
|
||||
- Keep user-facing text in translation resources.
|
||||
- Use console.error for errors and console.warn for deprecation warnings;
|
||||
avoid production console.log debugging and never log credentials.
|
||||
|
||||
## High-value checks
|
||||
|
||||
- For backend app tests, use make test-backend-app, or an equivalent Maven
|
||||
invocation that includes -am, to avoid stale dependent-module artifacts.
|
||||
- API contract changes must check generated types and affected consumers.
|
||||
- Lifecycle changes must compare docs, source and tests; report disagreements
|
||||
rather than silently changing business policy.
|
||||
- Search changes: read docs/04-search-architecture.md.
|
||||
- Business-flow changes: read docs/05-business-flows.md.
|
||||
|
||||
## Work proportionately
|
||||
|
||||
- Small edits: read relevant rules, edit and verify; do not require a task record.
|
||||
- Multi-step work: use a task record for requirements, context, progress and verification, respecting an existing user choice not to create one.
|
||||
- Large requirements or architecture changes: OpenSpec owns formal requirements and design; Trellis organizes execution and continuation.
|
||||
- Repeat critical rules here when they should be visible at session entry.
|
||||
Keep detailed contracts in the relevant specs and authoritative documents.
|
||||
When changing a repeated rule, check both locations for consistency.
|
||||
- Each worktree owns its task records. Tracked specs follow the checked-out branch; local template updates are explicit and must not silently overwrite them.
|
||||
|
||||
## Verification and delivery
|
||||
|
||||
Choose checks using the [change-impact matrix](./.trellis/spec/testing/index.md).
|
||||
Start with focused feedback; broaden for shared behavior, public interfaces, configuration and integration changes.
|
||||
Documentation-only changes require relevant document/source checks, not starting the application stack.
|
||||
Report passed, failed, not-run and environment-blocked checks separately.
|
||||
A build, unit test, screenshot or static configuration check is not proof of deployment or end-to-end behavior.
|
||||
|
||||
Use the existing Makefile and package tooling. Do not hand-edit generated OpenAPI types.
|
||||
Read commands before execution: staging and schema-drift checks may start/stop services.
|
||||
Preserve unrelated changes; do not commit, push, publish, deploy or remove worktrees without authorization.
|
||||
Do not commit or log credentials; do not reset data to make verification pass.
|
||||
|
||||
## Sources and maintenance
|
||||
|
||||
- Engineering conventions: [.trellis/spec/](./.trellis/spec/index.md).
|
||||
- Architecture, business semantics and operations: [docs/](./docs/), including [lifecycle](./docs/14-skill-lifecycle.md).
|
||||
- Formal changes: [OpenSpec workflow](./.trellis/spec/workflow/openspec.md).
|
||||
- Versions, commands and current behavior: dependency manifests, Makefile, schemas, source and tests.
|
||||
- Only `.trellis/spec/` is shared in Git. Tasks, runtime state and personal Trellis tooling remain local.
|
||||
- Execution Skills may help run a workflow; they do not own a second copy of project conventions.
|
||||
- Preserve and verify existing task records before authorized cleanup; see [archive requirements](./.trellis/spec/workflow/task-archive.md).
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ preserve the existing architecture and product direction documented in
|
|||
[`docs/`](./docs).
|
||||
|
||||
AI coding agents working in this repository should follow the rules in
|
||||
[`AGENTS.md`](./AGENTS.md), which documents repository architecture,
|
||||
dependency rules, and agent-specific conventions.
|
||||
[`AGENTS.md`](./AGENTS.md), which routes to the engineering conventions in
|
||||
[`.trellis/spec/index.md`](./.trellis/spec/index.md). These documents can be read
|
||||
without installing Trellis or any engineering Skill. Architecture and business
|
||||
design remain in `docs/`; execution tools and task records remain local.
|
||||
|
||||
## Before You Start
|
||||
|
||||
|
|
@ -63,7 +65,10 @@ Before opening a pull request, make sure:
|
|||
|
||||
- The branch is rebased or merged cleanly from the target branch.
|
||||
- Relevant backend tests pass.
|
||||
- Frontend typecheck/build passes when frontend files changed.
|
||||
- Frontend checks follow the [change-impact matrix](.trellis/spec/testing/index.md).
|
||||
Changes limited to existing translation display text use its narrow exception;
|
||||
frontend behavior or tooling changes require the applicable typecheck/build checks.
|
||||
This does not waive required CI checks or failures in relevant existing tests.
|
||||
- `make generate-api` or `./scripts/check-openapi-generated.sh` has been run when
|
||||
backend API contracts changed.
|
||||
- Smoke coverage is updated when operator-facing workflows change.
|
||||
|
|
|
|||
67
scripts/check-engineering-specs.py
Normal file
67
scripts/check-engineering-specs.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check portable engineering specs and references to retired engineering Skills."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
RETIRED = (
|
||||
"api-and-namespace-design", "backend-module-structure", "code-conventions",
|
||||
"dev-workflow", "frontend-conventions", "pr-submission", "skill-lifecycle",
|
||||
"testing-and-ci",
|
||||
)
|
||||
LINK = re.compile(r"(?<!!)\[[^\]\n]*\]\(([^)\n]+)\)")
|
||||
|
||||
|
||||
def check(root: Path) -> tuple[list[str], int, int]:
|
||||
spec = root / ".trellis/spec"
|
||||
errors = []
|
||||
files = sorted(spec.rglob("*.md"))
|
||||
if not (spec / "index.md").is_file():
|
||||
errors.append("missing .trellis/spec/index.md")
|
||||
paths = files + [root / name for name in ("AGENTS.md", "CONTRIBUTING.md")]
|
||||
links = 0
|
||||
for path in paths:
|
||||
if not path.is_file():
|
||||
errors.append(f"missing {path.relative_to(root)}")
|
||||
continue
|
||||
body = path.read_text(encoding="utf-8")
|
||||
label = path.relative_to(root)
|
||||
for name in RETIRED:
|
||||
if f".agents/skills/{name}" in body:
|
||||
errors.append(f"{label}: reference to retired Skill {name}")
|
||||
if path in files and re.search(r"/(?:home|Users|tmp)/|(?<!\w)~/", body):
|
||||
errors.append(f"{label}: machine-specific path in shared spec")
|
||||
for match in LINK.finditer(body):
|
||||
target = match.group(1).strip().split(' "', 1)[0].strip("<>")
|
||||
parts = urlsplit(target)
|
||||
if parts.scheme or parts.netloc or not parts.path:
|
||||
continue
|
||||
links += 1
|
||||
target_path = Path(unquote(parts.path))
|
||||
resolved = (path.parent / target_path).resolve()
|
||||
if not resolved.is_relative_to(root):
|
||||
errors.append(f"{label}: link escapes checkout: {target}")
|
||||
elif not resolved.exists():
|
||||
errors.append(f"{label}: missing link target: {target}")
|
||||
elif resolved.is_relative_to(root / ".trellis") and not resolved.is_relative_to(spec):
|
||||
errors.append(f"{label}: link depends on local Trellis state: {target}")
|
||||
for name in RETIRED:
|
||||
if (root / ".agents/skills" / name).exists():
|
||||
errors.append(f"retired engineering Skill still present: {name}")
|
||||
return errors, len(files), links
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
errors, files, links = check(root)
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"Engineering specs: {files} documents, {links} local links checked.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Reference in a new issue