Merge pull request #958 from iflytek/chore/trellis-engineering-specs

docs(workflow): 将工程 Skill 迁移为 Trellis 分层规范
This commit is contained in:
XiaoSeS 2026-10-15 17:16:02 +08:00 • committed by GitHub
commit 1cff1bbe0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 1962 additions and 1660 deletions

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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 |

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View file

@ -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/

View 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 指针、警告确认、删除约束均须核对当前服务与测试,不从历史快照复制规则。

View 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)。

View 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)。

View 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。

View 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);列出实际调用链的数据库写入、外部副作用与失败返回 |
上述是读取触发条件,不表示现有提供方已完成互换验证。涉及替换时按受影响行为验证,不凭“兼容协议”跳过验证。

View 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` 断言只能证明调用参数;改动审计持久化或事务时还需验证实际提交/回滚结果。

View 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) 为准。
单测通过、真实依赖集成通过、环境健康与生产验收分别报告;静态阅读测试文件不算运行测试。

View 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) 补环境验证。

View 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)。

View 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。

View 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)。

View 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 | 真实需求成立再查依赖,不默认安装大型工具链 |
清单是调查入口,不代表对应版本、文档、注册表或依赖已经在当前任务验证。

View 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)。

View 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)。导出测试只证明路径有效,不证明请求行为。

View 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)。

View 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 自动发现。架构背景与业务契约仍按上表引用。
遇到文档与源码或其他规则冲突,记录差异,按适用指令优先级处理,不借整理规范默改业务规则。

View 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 节评价方法,分别说明已验证内容与未验证内容。

View 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;场景需要可调整。
空态与加载态分别处理,骨架调用方提供可访问加载说明。
复用已有标题、分页、复制、确认和骨架,不引入重复组合或包裹一切的空抽象。

View 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 按仓库约定维护。

View 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)。

View 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)执行。
不能把类型检查通过等同于真实接口、授权或运行时响应验证通过。

View 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) 增加共享变体。
兼容类与营销页面现有特例不自动成为新页面模板,也不因此批量清理存量。

View 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

View file

@ -0,0 +1,14 @@
# 代码复用判断
新增 helper、组件、服务或合并相似代码前,先找已有实现、调用方与测试。
- 相似代码是否表达同一业务概念,还是写法碰巧相似?
- 调用方是否应因同一原因一起变化?变化原因不同通常应保留独立实现。
- 提取后依赖方向是否正确?不要为了共用把业务规则塞入通用 UI 或底层工具。
- 抽象是否减少理解成本,还是增加开关、可选参数和隐藏状态?
- 有无已有组件、类型、schema、仓储或解析入口可用?
- 修改共享实现影响哪些调用方,怎样证明旧行为保持?
次数、行数和相似度不是自动抽象阈值。外部数据解析需要明确契约归属,但不因此要求新建通用解码框架。
具体规则:[组件](../frontend/component-guidelines.md)、[类型](../frontend/type-safety.md)、[分层](../backend/directory-structure.md)、[测试](../testing/index.md)。

View 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 项目规则。

View 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
View 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)处理;无记录不强制创建。

View 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)。
前者可参考真实连接和并发接受邀请的组织方式;后者不替代数据库迁移运行。

View 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必需检查仍以实际仓库配置为准。
本文件不赋予跳过适用门槛、触发部署或发布的权限。

View 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、截图和日志不可携带会话凭证公开传播。

View 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 的结果。
不得把现有三个测试通过写成“账号切换、取消和竞态已全部验证”。

View 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)。

View 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);不存在记录不为归档补建任务。

View 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.

View 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.

View 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.

View file

@ -0,0 +1,14 @@
# 任务记录与归档
适用于已有任务的收尾和worktree清理;任务、运行状态、个人报告不属于公开规范。
- 按已配置的本地执行入口读取归档工具说明,保存当前单个任务的摘要、原始记录与证据链接。
- 摘要说明结果、仓库/分支、提交或PR(若有)、验证范围与未完成项;未提交和未验证如实记录。
- 清理前保存到worktree外,验证目标可读、内容完整。失败保留源记录,不继续依赖归档成功的清理。
- 检查凭证和私人数据;链接报告优于复制源码和环境文件。归档不授予提交、推送或删除权限。
- 稳定任务写入后再快照,不覆盖同标识但不同内容的记录。
- 未配置工具时不假设本机命令存在,说明缺口并保留原记录。没有任务记录不强制创建。
工具路径、参数和私有档案由本地工作流或执行Skill配置,不成为公开规范硬依赖。
原生archive可能修改状态、移动目录、触发hooks或提交;运行前核对,不把它等同跨worktree备份。
验证观察已保存摘要和原始记录;Git clean不证明忽略的任务文件已经保存。

692
AGENTS.md
View file

@ -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).

View file

@ -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.

View 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())