create-yss-spec 2.1.3 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/template/.agents/skills/code-review/SKILL.md +38 -10
- package/template/.agents/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.agents/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.agents/skills/prototype-review/SKILL.md +6 -2
- package/template/.agents/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.agents/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.agents/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.agents/skills/yss-design-system/SKILL.md +6 -5
- package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.agents/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.agents/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.agents/skills/yss-router/SKILL.md +2 -2
- package/template/.agents/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.agents/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/.claude/skills/code-review/SKILL.md +38 -10
- package/template/.claude/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.claude/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.claude/skills/prototype-review/SKILL.md +6 -2
- package/template/.claude/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.claude/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.claude/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.claude/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.claude/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.claude/skills/yss-design-system/SKILL.md +6 -5
- package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.claude/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.claude/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.claude/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.claude/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.claude/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.claude/skills/yss-router/SKILL.md +2 -2
- package/template/.claude/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.claude/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/.codex/skills/code-review/SKILL.md +38 -10
- package/template/.codex/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.codex/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.codex/skills/prototype-review/SKILL.md +6 -2
- package/template/.codex/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.codex/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.codex/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.codex/skills/yss-design-system/SKILL.md +6 -5
- package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.codex/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.codex/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.codex/skills/yss-router/SKILL.md +2 -2
- package/template/.codex/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.codex/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/.hermes/skills/code-review/SKILL.md +38 -10
- package/template/.hermes/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.hermes/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.hermes/skills/prototype-review/SKILL.md +6 -2
- package/template/.hermes/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.hermes/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.hermes/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.hermes/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.hermes/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.hermes/skills/yss-design-system/SKILL.md +6 -5
- package/template/.hermes/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.hermes/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.hermes/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.hermes/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.hermes/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.hermes/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.hermes/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.hermes/skills/yss-router/SKILL.md +2 -2
- package/template/.hermes/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.hermes/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/.pi/skills/code-review/SKILL.md +38 -10
- package/template/.pi/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.pi/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.pi/skills/prototype-review/SKILL.md +6 -2
- package/template/.pi/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.pi/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.pi/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.pi/skills/yss-design-system/SKILL.md +6 -5
- package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.pi/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.pi/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.pi/skills/yss-router/SKILL.md +2 -2
- package/template/.pi/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.pi/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/.qoder/skills/code-review/SKILL.md +38 -10
- package/template/.qoder/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.qoder/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.qoder/skills/prototype-review/SKILL.md +6 -2
- package/template/.qoder/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.qoder/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.qoder/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.qoder/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.qoder/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.qoder/skills/yss-design-system/SKILL.md +6 -5
- package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.qoder/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.qoder/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.qoder/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.qoder/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.qoder/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.qoder/skills/yss-router/SKILL.md +2 -2
- package/template/.qoder/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.qoder/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/.trae/skills/code-review/SKILL.md +38 -10
- package/template/.trae/skills/high-fidelity-html-prototype/SKILL.md +16 -12
- package/template/.trae/skills/product-design-prototype/SKILL.md +6 -2
- package/template/.trae/skills/prototype-review/SKILL.md +6 -2
- package/template/.trae/skills/wireframe-prototype/SKILL.md +5 -1
- package/template/.trae/skills/yss-api-integration/SKILL.md +25 -7
- package/template/.trae/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
- package/template/.trae/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
- package/template/.trae/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
- package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
- package/template/.trae/skills/yss-design-system/SKILL.md +6 -5
- package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
- package/template/.trae/skills/yss-openapi-draft-review/SKILL.md +13 -17
- package/template/.trae/skills/yss-openapi-governance/SKILL.md +89 -114
- package/template/.trae/skills/yss-openapi-governance/agents/openai.yaml +2 -2
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +13 -7
- package/template/.trae/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +7 -2
- package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +8 -0
- package/template/.trae/skills/yss-prototype-stage/SKILL.md +40 -0
- package/template/.trae/skills/yss-router/SKILL.md +2 -2
- package/template/.trae/skills/yss-router/references/router-contract.yaml +9 -8
- package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +4 -1
- package/template/.trae/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/AGENTS.md +7 -5
- package/template/CONTEXT.md +15 -1
- package/template/README.md +2 -1
- package/template/__yss_dotfile__.gitignore +4 -0
- package/template/docs/adr/0001-product-design-prototype-entrypoint.md +1 -1
- package/template/docs/adr/0003-machine-readable-lifecycle-registry.md +5 -0
- package/template/docs/adr/0004-split-lifecycle-and-router-registry-domains.md +5 -0
- package/template/docs/adr/0005-cross-repository-ecosystem-release-manifest.md +5 -0
- package/template/docs/adr/0006-stable-lifecycle-ids-and-generated-structure.md +5 -0
- package/template/docs/adr/0007-separate-skill-routing-registry-from-integrity-lock.md +5 -0
- package/template/docs/agents/issue-tracker.md +1 -0
- package/template/docs/api/templates/openapi-draft-review-checklist.md +8 -6
- package/template/docs/api/templates/openapi-freeze-record-template.md +8 -2
- package/template/docs/api/templates/openapi-json-export-record-template.md +60 -0
- package/template/docs/architecture/templates/architecture-review-checklist.md +6 -19
- package/template/docs/architecture/templates/functional-architecture-template.md +4 -4
- package/template/docs/architecture/templates/system-overview-design-template.md +10 -5
- package/template/docs/architecture/templates/tech-design-template.md +4 -11
- package/template/docs/design/README.md +4 -2
- package/template/docs/design/design.md +4 -0
- package/template/docs/design/templates/interaction-spec-template.md +3 -3
- package/template/docs/design/templates/prototype-confirmation-template.md +9 -6
- package/template/docs/design/templates/prototype-evidence-template.yaml +46 -0
- package/template/docs/design/templates/prototype-review-checklist.md +4 -4
- package/template/docs/discovery/templates/discovery-template.md +0 -1
- package/template/docs/implementation/create-yss-spec-repository-mode-contract.md +2 -2
- package/template/docs/process/harness-process-tailoring.md +2 -0
- package/template/docs/process/harness-work-unit-map.md +14 -10
- package/template/docs/process/lifecycle-artifact-map.md +84 -39
- package/template/docs/process/lifecycle-registry-baseline.json +70 -0
- package/template/docs/process/lifecycle-registry.yaml +289 -0
- package/template/docs/process/schemas/lifecycle-registry.schema.json +35 -0
- package/template/docs/reviews/lifecycle-registry-phase0-1-red-green-2026-08-17.md +61 -0
- package/template/docs/reviews/matt-yss-p0-ticket-review-git-red-green-2026-08-17.md +91 -0
- package/template/docs/reviews/openapi-skill-primary-source-research-2026-08-16.md +76 -0
- package/template/docs/reviews/openapi-yaml-first-independent-review-2026-08-16.md +121 -0
- package/template/docs/reviews/openapi-yaml-first-pressure-scenarios-2026-08-16.md +191 -0
- package/template/docs/reviews/openapi-yaml-first-red-green-2026-08-16.md +76 -0
- package/template/docs/reviews/openapi-yaml-json-output-independent-review-2026-08-16.md +30 -0
- package/template/docs/reviews/openapi-yaml-json-output-scope-red-green-2026-08-16.md +154 -0
- package/template/docs/reviews/research-create-yss-spec-refresh-2026-08-16.md +164 -0
- package/template/docs/reviews/security-permission-lifecycle-simplification-red-green-2026-08-16.md +43 -0
- package/template/docs/reviews/template-release-review-remediation-red-green-2026-08-17.md +41 -0
- package/template/docs/templates/agent-brief-template.md +2 -2
- package/template/docs/templates/build-architecture-checklist-template.md +3 -4
- package/template/docs/templates/implementation-repo-registry-template.md +0 -2
- package/template/docs/templates/implementation-routing-template.md +6 -6
- package/template/docs/templates/openapi-spec-template.yaml +0 -6
- package/template/docs/templates/review-report-template.md +2 -1
- package/template/docs/templates/spec-delta-template.md +1 -1
- package/template/docs/templates/spec-template.md +0 -1
- package/template/docs/templates/vertical-slice-ticket-template.md +2 -2
- package/template/docs/user-guide//344/272/247/345/223/201/347/224/237/345/221/275/345/221/250/346/234/237/345/267/245/344/275/234/346/265/201.md +6 -6
- package/template/docs/user-guide//344/272/247/345/223/201/347/240/224/345/217/221/345/205/250/347/224/237/345/221/275/345/221/250/346/234/237/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
- package/template/docs/user-guide//345/244/226/351/203/250/345/221/275/344/273/244/350/241/214/345/267/245/345/205/267/345/256/236/350/267/265/346/214/207/345/215/227.md +1 -1
- package/template/docs/user-guide//347/224/237/345/221/275/345/221/250/346/234/237/346/234/200/344/275/263/345/256/236/350/267/265.md +1 -1
- package/template/scripts/generate-lifecycle-artifacts +26 -0
- package/template/scripts/lifecycle-registry.rb +194 -0
- package/template/scripts/sync-skills +1 -0
- package/template/scripts/test-export-yss-skills.rb +10 -6
- package/template/scripts/test-lifecycle-registry.rb +74 -0
- package/template/scripts/update-skill-lock +1 -0
- package/template/scripts/verify-governance-release +39 -0
- package/template/scripts/verify-lifecycle-registry +67 -0
- package/template/scripts/verify-lifecycle-scenarios +193 -23
- package/template/scripts/verify-matt-yss-integration-scenarios +297 -1
- package/template/scripts/verify-openapi-json-handoff-scenarios +270 -0
- package/template/scripts/verify-openapi-yaml-first-scenarios +120 -0
- package/template/scripts/verify-prototype-backend-scaffold-scenarios +26 -4
- package/template/scripts/verify-template +27 -3
- package/template/scripts/verify-yss-router-scenarios +51 -14
- package/template/skills-lock.json +25 -25
- package/template/wiki/raw/skills-lock.json +0 -15
- package/template/wiki/wiki/OpenAPI/345/245/221/347/272/246.md +1 -1
- package/template/wiki/wiki/YSS/345/267/245/347/250/213/346/212/200/350/203/275/344/275/223/347/263/273.md +1 -1
- package/template/yss-public-skills.json +5 -5
- package/template.snapshot.json +4 -4
- package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.agents/skills/yss-openapi/SKILL.md +0 -116
- package/template/.agents/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.agents/skills/yss-openapi/img.png +0 -0
- package/template/.agents/skills/yss-openapi/img_1.png +0 -0
- package/template/.claude/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.claude/skills/yss-openapi/SKILL.md +0 -116
- package/template/.claude/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.claude/skills/yss-openapi/img.png +0 -0
- package/template/.claude/skills/yss-openapi/img_1.png +0 -0
- package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.codex/skills/yss-openapi/SKILL.md +0 -116
- package/template/.codex/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.codex/skills/yss-openapi/img.png +0 -0
- package/template/.codex/skills/yss-openapi/img_1.png +0 -0
- package/template/.hermes/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.hermes/skills/yss-openapi/SKILL.md +0 -116
- package/template/.hermes/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.hermes/skills/yss-openapi/img.png +0 -0
- package/template/.hermes/skills/yss-openapi/img_1.png +0 -0
- package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.pi/skills/yss-openapi/SKILL.md +0 -116
- package/template/.pi/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.pi/skills/yss-openapi/img.png +0 -0
- package/template/.pi/skills/yss-openapi/img_1.png +0 -0
- package/template/.qoder/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.qoder/skills/yss-openapi/SKILL.md +0 -116
- package/template/.qoder/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.qoder/skills/yss-openapi/img.png +0 -0
- package/template/.qoder/skills/yss-openapi/img_1.png +0 -0
- package/template/.trae/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
- package/template/.trae/skills/yss-openapi/SKILL.md +0 -116
- package/template/.trae/skills/yss-openapi/agents/openai.yaml +0 -4
- package/template/.trae/skills/yss-openapi/img.png +0 -0
- package/template/.trae/skills/yss-openapi/img_1.png +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "pathname"
|
|
5
5
|
require "yaml"
|
|
6
|
+
require "digest"
|
|
6
7
|
|
|
7
8
|
ROOT = Pathname.new(__dir__).join("..").realpath
|
|
8
9
|
|
|
@@ -76,6 +77,46 @@ def flow_decision(definition, repository_mode:, flow:)
|
|
|
76
77
|
{"result" => "allowed", "reason" => nil}
|
|
77
78
|
end
|
|
78
79
|
|
|
80
|
+
def git_operation_authorized?(definition, facts)
|
|
81
|
+
return false unless definition["requires_explicit_user_authorization"] == true
|
|
82
|
+
return false unless definition["authorized_value"] == true
|
|
83
|
+
|
|
84
|
+
required = definition.fetch("required", [])
|
|
85
|
+
return false unless required.all? { |field| facts.key?(field) }
|
|
86
|
+
|
|
87
|
+
authorization_fields = required.grep(/_authorized\z/)
|
|
88
|
+
return false unless authorization_fields.all? { |field| facts[field] == definition["authorized_value"] }
|
|
89
|
+
|
|
90
|
+
definition.fetch("non_empty", []).all? { |field| !facts.fetch(field, "").to_s.empty? }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def uint64be(value)
|
|
94
|
+
[value].pack("Q>")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def uint32be(value)
|
|
98
|
+
[value].pack("N")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def canonical_worktree_candidate_stream(snapshot, tracked_diff:, untracked:)
|
|
102
|
+
header = snapshot.fetch("header_ascii").b + [Integer(snapshot.fetch("terminator_byte"))].pack("C")
|
|
103
|
+
tracked = snapshot.fetch("tracked_record")
|
|
104
|
+
stream = header + [Integer(tracked.fetch("type_byte"))].pack("C") + uint64be(tracked_diff.bytesize) + tracked_diff
|
|
105
|
+
kinds = snapshot.fetch("untracked_record").fetch("entry_kinds")
|
|
106
|
+
|
|
107
|
+
untracked.sort_by { |entry| entry.fetch(:path).b }.each do |entry|
|
|
108
|
+
path = entry.fetch(:path).b
|
|
109
|
+
content = entry.fetch(:content).b
|
|
110
|
+
kind = entry.fetch(:kind).to_s
|
|
111
|
+
stream << [Integer(snapshot.fetch("untracked_record").fetch("type_byte"))].pack("C")
|
|
112
|
+
stream << uint64be(path.bytesize) << path
|
|
113
|
+
stream << uint32be(entry.fetch(:mode))
|
|
114
|
+
stream << [Integer(kinds.fetch(kind))].pack("C")
|
|
115
|
+
stream << uint64be(content.bytesize) << content
|
|
116
|
+
end
|
|
117
|
+
stream
|
|
118
|
+
end
|
|
119
|
+
|
|
79
120
|
entry = contract.fetch("entry_routing", {})
|
|
80
121
|
require_equal(failures, "entry manifest", entry["manifest"], "yss-project.yaml")
|
|
81
122
|
require_equal(failures, "entry valid modes", entry["valid_modes"], %w[template-source project-instance])
|
|
@@ -196,6 +237,222 @@ ready_required = contract.dig("ready_for_agent", "required") || []
|
|
|
196
237
|
(preconditions.dig("implement", "required") || []).reject { |field| field == "ready_for_agent" }.each do |field|
|
|
197
238
|
require_include(failures, "ready-for-agent 与 implement 前置条件", ready_required, field)
|
|
198
239
|
end
|
|
240
|
+
|
|
241
|
+
ticket_role_transition = contract.fetch("ticket_role_transition", {})
|
|
242
|
+
require_equal(
|
|
243
|
+
failures,
|
|
244
|
+
"YSS to-tickets initial role",
|
|
245
|
+
ticket_role_transition.dig("yss_active", "to_tickets_initial_role"),
|
|
246
|
+
"ready-for-human"
|
|
247
|
+
)
|
|
248
|
+
require_equal(
|
|
249
|
+
failures,
|
|
250
|
+
"YSS ticket promotion owner",
|
|
251
|
+
ticket_role_transition.dig("yss_active", "promotion_owner"),
|
|
252
|
+
"yss-product-lifecycle"
|
|
253
|
+
)
|
|
254
|
+
require_equal(
|
|
255
|
+
failures,
|
|
256
|
+
"YSS ticket promoted role",
|
|
257
|
+
ticket_role_transition.dig("yss_active", "promoted_role"),
|
|
258
|
+
"ready-for-agent"
|
|
259
|
+
)
|
|
260
|
+
require_equal(
|
|
261
|
+
failures,
|
|
262
|
+
"YSS ticket promotion condition",
|
|
263
|
+
ticket_role_transition.dig("yss_active", "promotion_requires"),
|
|
264
|
+
"ready_for_agent"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
review_input = contract.fetch("review_input", {})
|
|
268
|
+
require_equal(failures, "review input schema", review_input["schema_version"], 1)
|
|
269
|
+
%w[
|
|
270
|
+
review_mode
|
|
271
|
+
review_base_ref
|
|
272
|
+
implementation_candidate_ref
|
|
273
|
+
candidate_snapshot_ref
|
|
274
|
+
candidate_digest
|
|
275
|
+
spec_ref
|
|
276
|
+
ticket_ref
|
|
277
|
+
slice_contract_ref
|
|
278
|
+
build_architecture_checklist_ref
|
|
279
|
+
yss_execution_result_refs
|
|
280
|
+
].each do |field|
|
|
281
|
+
require_include(failures, "review input required fields", review_input["required"] || [], field)
|
|
282
|
+
end
|
|
283
|
+
%w[committed worktree].each do |mode|
|
|
284
|
+
require_include(failures, "review modes", review_input["modes"] || [], mode)
|
|
285
|
+
end
|
|
286
|
+
%w[committed staged unstaged untracked].each do |candidate_part|
|
|
287
|
+
require_include(
|
|
288
|
+
failures,
|
|
289
|
+
"worktree review coverage",
|
|
290
|
+
review_input.dig("worktree", "required_coverage") || [],
|
|
291
|
+
candidate_part
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
require_equal(
|
|
295
|
+
failures,
|
|
296
|
+
"worktree untracked inventory",
|
|
297
|
+
review_input.dig("worktree", "untracked_inventory_command"),
|
|
298
|
+
"git ls-files -z --others --exclude-standard"
|
|
299
|
+
)
|
|
300
|
+
require_equal(
|
|
301
|
+
failures,
|
|
302
|
+
"worktree tracked binary diff",
|
|
303
|
+
review_input.dig("worktree", "tracked_diff_command"),
|
|
304
|
+
"git diff --no-ext-diff --binary --full-index <merge-base>"
|
|
305
|
+
)
|
|
306
|
+
require_equal(
|
|
307
|
+
failures,
|
|
308
|
+
"worktree untracked binary diff",
|
|
309
|
+
review_input.dig("worktree", "untracked_diff_command"),
|
|
310
|
+
"git diff --no-index --no-ext-diff --binary --full-index -- /dev/null <path>"
|
|
311
|
+
)
|
|
312
|
+
require_equal(failures, "worktree capture once", review_input.dig("worktree", "capture_once"), true)
|
|
313
|
+
require_equal(failures, "worktree verify unchanged", review_input.dig("worktree", "verify_unchanged_after_review"), true)
|
|
314
|
+
require_equal(failures, "review snapshot immutable", review_input.dig("snapshot", "immutable"), true)
|
|
315
|
+
require_equal(failures, "review digest algorithm", review_input.dig("snapshot", "digest_algorithm"), "sha256")
|
|
316
|
+
require_equal(
|
|
317
|
+
failures,
|
|
318
|
+
"review digest framing",
|
|
319
|
+
review_input.dig("snapshot", "digest_framing"),
|
|
320
|
+
"length-prefixed-path-and-bytes"
|
|
321
|
+
)
|
|
322
|
+
require_equal(failures, "review digest path order", review_input.dig("snapshot", "path_order"), "bytewise")
|
|
323
|
+
require_equal(failures, "review digest change action", review_input.dig("snapshot", "on_digest_change"), "blocked")
|
|
324
|
+
require_equal(failures, "review completion digest guard", review_input.dig("snapshot", "completion_requires_current_digest"), true)
|
|
325
|
+
require_equal(failures, "review snapshot protocol", review_input.dig("snapshot", "protocol"), "yss-worktree-candidate-v1")
|
|
326
|
+
require_equal(failures, "review snapshot path encoding", review_input.dig("snapshot", "path_encoding"), "raw-bytes")
|
|
327
|
+
require_equal(failures, "review snapshot length encoding", review_input.dig("snapshot", "length_encoding"), "uint64-big-endian")
|
|
328
|
+
require_equal(failures, "review snapshot header", review_input.dig("snapshot", "header_ascii"), "YSS-WORKTREE-CANDIDATE-V1")
|
|
329
|
+
require_equal(failures, "review snapshot terminator", review_input.dig("snapshot", "terminator_byte"), "0x00")
|
|
330
|
+
require_equal(failures, "review snapshot tracked record", review_input.dig("snapshot", "tracked_record", "type_byte"), "0x54")
|
|
331
|
+
require_equal(failures, "review snapshot untracked record", review_input.dig("snapshot", "untracked_record", "type_byte"), "0x55")
|
|
332
|
+
require_equal(failures, "review snapshot unsupported entry action", review_input.dig("snapshot", "unsupported_entry_action"), "blocked")
|
|
333
|
+
require_equal(
|
|
334
|
+
failures,
|
|
335
|
+
"review snapshot tracked fields",
|
|
336
|
+
review_input.dig("snapshot", "tracked_record", "fields"),
|
|
337
|
+
%w[type_byte uint64be_content_length binary_diff_bytes]
|
|
338
|
+
)
|
|
339
|
+
require_equal(
|
|
340
|
+
failures,
|
|
341
|
+
"review snapshot untracked fields",
|
|
342
|
+
review_input.dig("snapshot", "untracked_record", "fields"),
|
|
343
|
+
%w[type_byte uint64be_path_length raw_path_bytes uint32be_file_mode entry_kind_byte uint64be_content_length content_bytes]
|
|
344
|
+
)
|
|
345
|
+
require_equal(
|
|
346
|
+
failures,
|
|
347
|
+
"review snapshot entry kinds",
|
|
348
|
+
review_input.dig("snapshot", "untracked_record", "entry_kinds"),
|
|
349
|
+
{"regular" => "0x52", "symlink" => "0x4c"}
|
|
350
|
+
)
|
|
351
|
+
fixture_tracked_diff = "COMMITTED\\nSTAGED\\nUNSTAGED\\nDELETE\\nMODE-CHANGE\\n".b
|
|
352
|
+
fixture_untracked = [
|
|
353
|
+
{path: "z\\nname".b, mode: 0o100644, kind: :regular, content: "untracked-bytes\\n".b},
|
|
354
|
+
{path: "a-link".b, mode: 0o120777, kind: :symlink, content: "target\\nname".b}
|
|
355
|
+
]
|
|
356
|
+
fixture_stream = canonical_worktree_candidate_stream(
|
|
357
|
+
review_input.fetch("snapshot"),
|
|
358
|
+
tracked_diff: fixture_tracked_diff,
|
|
359
|
+
untracked: fixture_untracked
|
|
360
|
+
)
|
|
361
|
+
require_equal(
|
|
362
|
+
failures,
|
|
363
|
+
"review snapshot canonical digest",
|
|
364
|
+
Digest::SHA256.hexdigest(fixture_stream),
|
|
365
|
+
"cc1b30990cc0d2a11721140cd94cb92a275f9dd24c6663e21ad7e5ea47898b7d"
|
|
366
|
+
)
|
|
367
|
+
reordered_stream = canonical_worktree_candidate_stream(
|
|
368
|
+
review_input.fetch("snapshot"),
|
|
369
|
+
tracked_diff: fixture_tracked_diff,
|
|
370
|
+
untracked: fixture_untracked.reverse
|
|
371
|
+
)
|
|
372
|
+
require_equal(failures, "review snapshot bytewise ordering", reordered_stream, fixture_stream)
|
|
373
|
+
review_return_digest = Digest::SHA256.hexdigest(fixture_stream)
|
|
374
|
+
completion_digest = Digest::SHA256.hexdigest(fixture_stream)
|
|
375
|
+
require_equal(failures, "review return digest guard", review_return_digest, Digest::SHA256.hexdigest(fixture_stream))
|
|
376
|
+
require_equal(failures, "review completion digest guard behavior", completion_digest, Digest::SHA256.hexdigest(fixture_stream))
|
|
377
|
+
drift_stream = canonical_worktree_candidate_stream(
|
|
378
|
+
review_input.fetch("snapshot"),
|
|
379
|
+
tracked_diff: fixture_tracked_diff,
|
|
380
|
+
untracked: fixture_untracked.map { |entry| entry.merge(content: entry[:content] + "changed".b) }
|
|
381
|
+
)
|
|
382
|
+
failures << "review return digest drift 未阻断" if Digest::SHA256.hexdigest(drift_stream) == review_return_digest
|
|
383
|
+
failures << "review completion digest drift 未阻断" if Digest::SHA256.hexdigest(drift_stream) == completion_digest
|
|
384
|
+
%w[merge_base tracked_diff_command untracked_inventory_command untracked_diff_command untracked_files commit_list_command].each do |field|
|
|
385
|
+
require_include(
|
|
386
|
+
failures,
|
|
387
|
+
"worktree manifest required fields",
|
|
388
|
+
review_input.dig("manifest_required_by_mode", "worktree") || [],
|
|
389
|
+
field
|
|
390
|
+
)
|
|
391
|
+
end
|
|
392
|
+
%w[merge_base tracked_diff_command commit_list_command].each do |field|
|
|
393
|
+
require_include(
|
|
394
|
+
failures,
|
|
395
|
+
"committed manifest required fields",
|
|
396
|
+
review_input.dig("manifest_required_by_mode", "committed") || [],
|
|
397
|
+
field
|
|
398
|
+
)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
git_authorization = contract.fetch("git_authorization", {})
|
|
402
|
+
{
|
|
403
|
+
"commit" => %w[commit_authorized commit_scope commit_authorization_ref],
|
|
404
|
+
"push" => %w[push_authorized push_scope push_authorization_ref]
|
|
405
|
+
}.each do |operation, required_fields|
|
|
406
|
+
require_equal(
|
|
407
|
+
failures,
|
|
408
|
+
"#{operation} explicit authorization",
|
|
409
|
+
git_authorization.dig(operation, "requires_explicit_user_authorization"),
|
|
410
|
+
true
|
|
411
|
+
)
|
|
412
|
+
required_fields.each do |field|
|
|
413
|
+
require_include(failures, "#{operation} authorization fields", git_authorization.dig(operation, "required") || [], field)
|
|
414
|
+
end
|
|
415
|
+
require_equal(
|
|
416
|
+
failures,
|
|
417
|
+
"#{operation} authorization value",
|
|
418
|
+
git_authorization.dig(operation, "authorized_value"),
|
|
419
|
+
true
|
|
420
|
+
)
|
|
421
|
+
required_fields.reject { |field| field.end_with?("_authorized") }.each do |field|
|
|
422
|
+
require_include(
|
|
423
|
+
failures,
|
|
424
|
+
"#{operation} non-empty authorization fields",
|
|
425
|
+
git_authorization.dig(operation, "non_empty") || [],
|
|
426
|
+
field
|
|
427
|
+
)
|
|
428
|
+
end
|
|
429
|
+
require_equal(
|
|
430
|
+
failures,
|
|
431
|
+
"#{operation} unauthorized action",
|
|
432
|
+
git_authorization.dig(operation, "unauthorized_action"),
|
|
433
|
+
"checkpoint-only"
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
definition = git_authorization.fetch(operation, {})
|
|
437
|
+
valid_facts = required_fields.to_h do |field|
|
|
438
|
+
[field, field.end_with?("_authorized") ? true : "explicit-value"]
|
|
439
|
+
end
|
|
440
|
+
allowed = git_operation_authorized?(definition, valid_facts)
|
|
441
|
+
failures << "#{operation} 完整显式授权被错误阻断" unless allowed
|
|
442
|
+
|
|
443
|
+
false_authorization = valid_facts.merge("#{operation}_authorized" => false)
|
|
444
|
+
false_allowed = git_operation_authorized?(definition, false_authorization)
|
|
445
|
+
failures << "#{operation} authorized=false 未阻断" if false_allowed
|
|
446
|
+
|
|
447
|
+
required_fields.reject { |field| field.end_with?("_authorized") }.each do |field|
|
|
448
|
+
blank = valid_facts.merge(field => "")
|
|
449
|
+
blank_allowed = git_operation_authorized?(definition, blank)
|
|
450
|
+
failures << "#{operation} #{field} 为空时未阻断" if blank_allowed
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
false_contract = definition.merge("authorized_value" => false)
|
|
454
|
+
failures << "#{operation} authorized_value=false 未阻断" if git_operation_authorized?(false_contract, valid_facts)
|
|
455
|
+
end
|
|
199
456
|
{
|
|
200
457
|
"to-spec" => %w[user_problem mvp_and_non_goals success_criteria test_seam terminology_review]
|
|
201
458
|
}.each do |flow, required|
|
|
@@ -447,6 +704,7 @@ end
|
|
|
447
704
|
|
|
448
705
|
adapter = read(ROOT, ".agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md")
|
|
449
706
|
to_spec = read(ROOT, ".agents/skills/to-spec/SKILL.md")
|
|
707
|
+
code_review = read(ROOT, ".agents/skills/code-review/SKILL.md")
|
|
450
708
|
require_markers(
|
|
451
709
|
failures,
|
|
452
710
|
"Matt to-spec core contract",
|
|
@@ -466,17 +724,55 @@ require_markers(
|
|
|
466
724
|
"发布提示由本适配层覆盖为 `ready-for-human`",
|
|
467
725
|
"to-spec",
|
|
468
726
|
"to-tickets",
|
|
727
|
+
"YSS active 的 `to-tickets`",
|
|
728
|
+
"`review_mode`",
|
|
729
|
+
"`commit_authorized`",
|
|
730
|
+
"`push_authorized`",
|
|
469
731
|
"Release",
|
|
470
732
|
"Retrospective"
|
|
471
733
|
]
|
|
472
734
|
)
|
|
735
|
+
require_markers(
|
|
736
|
+
failures,
|
|
737
|
+
"Matt code-review candidate modes",
|
|
738
|
+
code_review,
|
|
739
|
+
[
|
|
740
|
+
"Committed candidate",
|
|
741
|
+
"Worktree candidate",
|
|
742
|
+
"git diff --no-ext-diff --binary --full-index <merge-base>",
|
|
743
|
+
"git diff --no-index --no-ext-diff --binary --full-index -- /dev/null <path>",
|
|
744
|
+
"git ls-files -z --others --exclude-standard",
|
|
745
|
+
"staged",
|
|
746
|
+
"unstaged",
|
|
747
|
+
"untracked",
|
|
748
|
+
"candidate manifest",
|
|
749
|
+
"captured candidate",
|
|
750
|
+
"yss-worktree-candidate-v1",
|
|
751
|
+
"unsigned 64-bit big-endian",
|
|
752
|
+
"candidate_digest",
|
|
753
|
+
"stale candidate",
|
|
754
|
+
"immutable",
|
|
755
|
+
"length-prefixed",
|
|
756
|
+
"bytewise path order",
|
|
757
|
+
"return `blocked`"
|
|
758
|
+
]
|
|
759
|
+
)
|
|
473
760
|
|
|
474
761
|
lifecycle_skill = read(ROOT, ".agents/skills/yss-product-lifecycle/SKILL.md")
|
|
475
762
|
require_markers(
|
|
476
763
|
failures,
|
|
477
764
|
"lifecycle template-source guard",
|
|
478
765
|
lifecycle_skill,
|
|
479
|
-
[
|
|
766
|
+
[
|
|
767
|
+
"template-source",
|
|
768
|
+
"必须返回 `blocked`",
|
|
769
|
+
"template-source-product-artifact-forbidden",
|
|
770
|
+
"YSS active 调用 `to-spec` 时",
|
|
771
|
+
"ready-for-agent` 发布提示归一化为 Spec `ready-for-human`",
|
|
772
|
+
"YSS active 调用 `to-tickets` 时",
|
|
773
|
+
"`commit_authorized`",
|
|
774
|
+
"`push_authorized`"
|
|
775
|
+
]
|
|
480
776
|
)
|
|
481
777
|
orchestration = read(ROOT, ".agents/skills/yss-product-lifecycle/references/orchestration.md")
|
|
482
778
|
require_markers(
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
require "json"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
require "yaml"
|
|
8
|
+
|
|
9
|
+
# This validates the YAML-to-JSON handoff contract rather than invoking
|
|
10
|
+
# Redocly or any frontend generator. These fixtures prove that a JSON
|
|
11
|
+
# derivative preserves the frozen OpenAPI semantics and can be handed to an
|
|
12
|
+
# implementation repository as unchanged bytes.
|
|
13
|
+
module OpenapiJsonHandoff
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def validate!(source_yaml:, source_json:, materialized_json:, handoff:, **_unused)
|
|
17
|
+
validate_source_paths!(handoff)
|
|
18
|
+
assert!(handoff.fetch("freeze_status") == "approved", "OpenAPI Freeze 未批准")
|
|
19
|
+
assert!(handoff.fetch("export_status") == "exported", "JSON 导出未完成")
|
|
20
|
+
assert!(!handoff.fetch("freeze_record").to_s.empty?, "缺少 OpenAPI Freeze 记录")
|
|
21
|
+
assert!(!handoff.fetch("export_record").to_s.empty?, "缺少 OpenAPI JSON 派生记录")
|
|
22
|
+
|
|
23
|
+
yaml_documents = YAML.load_stream(File.binread(source_yaml).force_encoding("UTF-8"))
|
|
24
|
+
assert!(yaml_documents.length == 1, "YAML 必须是单一 document")
|
|
25
|
+
assert!(yaml_documents.first.is_a?(Hash) && yaml_documents.first["openapi"] == "3.1.0", "YAML 必须是 OAS 3.1")
|
|
26
|
+
|
|
27
|
+
json_document = JSON.parse(File.binread(source_json))
|
|
28
|
+
assert!(json_document["openapi"] == "3.1.0", "JSON 必须是 OAS 3.1")
|
|
29
|
+
validate_operation_contract!(yaml_documents.first, json_document)
|
|
30
|
+
validate_json_refs!(json_document)
|
|
31
|
+
assert!(Digest::SHA256.file(source_yaml).hexdigest == handoff.fetch("yaml_sha256"), "YAML SHA-256 不匹配")
|
|
32
|
+
assert!(Digest::SHA256.file(source_json).hexdigest == handoff.fetch("json_sha256"), "JSON SHA-256 不匹配")
|
|
33
|
+
assert!(Digest::SHA256.file(materialized_json).hexdigest == handoff.fetch("json_sha256"), "前端物化 JSON 与导出记录不一致")
|
|
34
|
+
|
|
35
|
+
bundle_command = handoff.fetch("bundle_command")
|
|
36
|
+
%w[pnpm\ exec\ redocly\ bundle --ext\ json --component-renaming-conflicts-severity=error].each do |marker|
|
|
37
|
+
assert!(bundle_command.include?(marker), "bundle 命令缺少 #{marker}")
|
|
38
|
+
end
|
|
39
|
+
assert!(bundle_command.include?(handoff.fetch("source_yaml")), "bundle 命令未使用冻结 YAML")
|
|
40
|
+
assert!(bundle_command.include?(handoff.fetch("source_json")), "bundle 命令未写入记录的 JSON")
|
|
41
|
+
assert!(!bundle_command.match?(/(?:\bmvn\b|smart[ _-]?doc)/i), "bundle 命令包含已移除的 Maven OpenAPI 链路")
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate_operation_contract!(yaml_document, json_document)
|
|
46
|
+
yaml_operations = operation_contracts(yaml_document)
|
|
47
|
+
json_operations = operation_contracts(json_document)
|
|
48
|
+
assert!(!yaml_operations.empty?, "YAML 缺少可生成客户端的操作")
|
|
49
|
+
assert!(yaml_operations == json_operations, "JSON 未保留 YAML 的路径、方法、operationId、请求或响应契约")
|
|
50
|
+
ids = yaml_operations.values.map { |contract| contract.fetch("operationId") }
|
|
51
|
+
assert!(ids.all? { |operation_id| operation_id.is_a?(String) && !operation_id.empty? }, "operationId 必须为非空字符串")
|
|
52
|
+
assert!(ids.uniq.length == ids.length, "operationId 必须唯一")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def operation_contracts(document)
|
|
56
|
+
methods = %w[get put post delete options head patch trace]
|
|
57
|
+
paths = document.fetch("paths", {})
|
|
58
|
+
paths.each_with_object({}) do |(path, path_item), operations|
|
|
59
|
+
next unless path_item.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
path_item.each do |method, operation|
|
|
62
|
+
next unless methods.include?(method.to_s.downcase) && operation.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
operations["#{method.to_s.upcase} #{path}"] = {
|
|
65
|
+
"operationId" => operation["operationId"],
|
|
66
|
+
"parameters" => operation.fetch("parameters", []),
|
|
67
|
+
"requestBody" => operation["requestBody"],
|
|
68
|
+
"responses" => operation.fetch("responses", {})
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_json_refs!(document)
|
|
75
|
+
refs = []
|
|
76
|
+
collect_refs(document, refs)
|
|
77
|
+
refs.each do |ref|
|
|
78
|
+
assert!(ref.start_with?("#/"), "JSON 不得包含外部或远程 $ref: #{ref}")
|
|
79
|
+
resolve_json_pointer!(document, ref)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def collect_refs(value, refs)
|
|
84
|
+
case value
|
|
85
|
+
when Hash
|
|
86
|
+
refs << value["$ref"] if value["$ref"].is_a?(String)
|
|
87
|
+
value.each_value { |child| collect_refs(child, refs) }
|
|
88
|
+
when Array
|
|
89
|
+
value.each { |child| collect_refs(child, refs) }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def resolve_json_pointer!(document, ref)
|
|
94
|
+
ref.delete_prefix("#/").split("/").reduce(document) do |node, segment|
|
|
95
|
+
key = segment.gsub("~1", "/").gsub("~0", "~")
|
|
96
|
+
assert!(node.is_a?(Hash) && node.key?(key), "JSON $ref 无法解析: #{ref}")
|
|
97
|
+
node.fetch(key)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def validate_source_paths!(handoff)
|
|
102
|
+
yaml_path = handoff.fetch("source_yaml")
|
|
103
|
+
json_path = handoff.fetch("source_json")
|
|
104
|
+
match = %r{\Adocs/\.scratch/([^/]+)/api/([^/]+)\.yaml\z}.match(yaml_path)
|
|
105
|
+
assert!(!match.nil? && match[1] == match[2], "冻结 YAML 路径不符合 feature 约定")
|
|
106
|
+
assert!(json_path == "docs/.scratch/#{match[1]}/api/#{match[2]}.json", "JSON 必须与冻结 YAML 同 feature 同目录")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def assert!(condition, message)
|
|
110
|
+
raise ArgumentError, message unless condition
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def build_context(root)
|
|
115
|
+
feature = "order"
|
|
116
|
+
api_dir = File.join(root, "docs", ".scratch", feature, "api")
|
|
117
|
+
frontend_dir = File.join(root, "frontend", "openapi")
|
|
118
|
+
Dir.mkdir(File.join(root, "docs"))
|
|
119
|
+
Dir.mkdir(File.join(root, "docs", ".scratch"))
|
|
120
|
+
Dir.mkdir(File.join(root, "docs", ".scratch", feature))
|
|
121
|
+
Dir.mkdir(api_dir)
|
|
122
|
+
Dir.mkdir(File.join(root, "frontend"))
|
|
123
|
+
Dir.mkdir(frontend_dir)
|
|
124
|
+
|
|
125
|
+
source_yaml = File.join(api_dir, "#{feature}.yaml")
|
|
126
|
+
source_json = File.join(api_dir, "#{feature}.json")
|
|
127
|
+
materialized_json = File.join(frontend_dir, "openapi.json")
|
|
128
|
+
File.write(source_yaml, <<~YAML)
|
|
129
|
+
openapi: 3.1.0
|
|
130
|
+
info:
|
|
131
|
+
title: Order API
|
|
132
|
+
version: 1.0.0
|
|
133
|
+
paths:
|
|
134
|
+
/api/v1/orders/{orderId}:
|
|
135
|
+
get:
|
|
136
|
+
operationId: getOrder
|
|
137
|
+
parameters:
|
|
138
|
+
- name: orderId
|
|
139
|
+
in: path
|
|
140
|
+
required: true
|
|
141
|
+
schema:
|
|
142
|
+
type: string
|
|
143
|
+
responses:
|
|
144
|
+
'200':
|
|
145
|
+
description: success
|
|
146
|
+
content:
|
|
147
|
+
application/json:
|
|
148
|
+
schema:
|
|
149
|
+
$ref: '#/components/schemas/Order'
|
|
150
|
+
components:
|
|
151
|
+
schemas:
|
|
152
|
+
Order:
|
|
153
|
+
type: object
|
|
154
|
+
required: [id]
|
|
155
|
+
properties:
|
|
156
|
+
id:
|
|
157
|
+
type: string
|
|
158
|
+
YAML
|
|
159
|
+
json_document = {
|
|
160
|
+
"openapi" => "3.1.0",
|
|
161
|
+
"info" => { "title" => "Order API", "version" => "1.0.0" },
|
|
162
|
+
"paths" => {
|
|
163
|
+
"/api/v1/orders/{orderId}" => {
|
|
164
|
+
"get" => {
|
|
165
|
+
"operationId" => "getOrder",
|
|
166
|
+
"parameters" => [{ "name" => "orderId", "in" => "path", "required" => true, "schema" => { "type" => "string" } }],
|
|
167
|
+
"responses" => {
|
|
168
|
+
"200" => {
|
|
169
|
+
"description" => "success",
|
|
170
|
+
"content" => { "application/json" => { "schema" => { "$ref" => "#/components/schemas/Order" } } }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"components" => {
|
|
177
|
+
"schemas" => {
|
|
178
|
+
"Order" => {
|
|
179
|
+
"type" => "object",
|
|
180
|
+
"required" => ["id"],
|
|
181
|
+
"properties" => { "id" => { "type" => "string" } }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
File.write(source_json, JSON.generate(json_document))
|
|
187
|
+
File.binwrite(materialized_json, File.binread(source_json))
|
|
188
|
+
|
|
189
|
+
source_yaml_path = "docs/.scratch/#{feature}/api/#{feature}.yaml"
|
|
190
|
+
source_json_path = "docs/.scratch/#{feature}/api/#{feature}.json"
|
|
191
|
+
handoff = {
|
|
192
|
+
"freeze_status" => "approved",
|
|
193
|
+
"export_status" => "exported",
|
|
194
|
+
"freeze_record" => "docs/.scratch/#{feature}/api/#{feature}-freeze.md",
|
|
195
|
+
"export_record" => "docs/.scratch/#{feature}/api/#{feature}-json-export.md",
|
|
196
|
+
"source_yaml" => source_yaml_path,
|
|
197
|
+
"source_json" => source_json_path,
|
|
198
|
+
"yaml_sha256" => Digest::SHA256.file(source_yaml).hexdigest,
|
|
199
|
+
"json_sha256" => Digest::SHA256.file(source_json).hexdigest,
|
|
200
|
+
"bundle_command" => "pnpm exec redocly bundle #{source_yaml_path} --output #{source_json_path} --ext json --component-renaming-conflicts-severity=error"
|
|
201
|
+
}
|
|
202
|
+
{
|
|
203
|
+
source_yaml: source_yaml,
|
|
204
|
+
source_json: source_json,
|
|
205
|
+
materialized_json: materialized_json,
|
|
206
|
+
handoff: handoff
|
|
207
|
+
}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def expect_rejected(label)
|
|
211
|
+
yield
|
|
212
|
+
raise "负向场景未被阻断: #{label}"
|
|
213
|
+
rescue ArgumentError
|
|
214
|
+
true
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
Dir.mktmpdir("openapi-handoff-") do |root|
|
|
218
|
+
context = build_context(root)
|
|
219
|
+
OpenapiJsonHandoff.validate!(**context)
|
|
220
|
+
|
|
221
|
+
expect_rejected("Draft 不能导出") do
|
|
222
|
+
OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("freeze_status" => "draft")))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
expect_rejected("Maven 链路不能作为导出命令") do
|
|
226
|
+
OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("bundle_command" => "mvn package")))
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
expect_rejected("运行时 URL 不能替代治理 JSON") do
|
|
230
|
+
OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("source_json" => "https://example.invalid/openapi.json")))
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
original_json = File.binread(context.fetch(:source_json))
|
|
234
|
+
|
|
235
|
+
changed_operation = JSON.parse(original_json)
|
|
236
|
+
changed_operation.fetch("paths").fetch("/api/v1/orders/{orderId}").fetch("get")["operationId"] = "getOtherOrder"
|
|
237
|
+
File.write(context.fetch(:source_json), JSON.generate(changed_operation))
|
|
238
|
+
expect_rejected("JSON 必须保留冻结 YAML 的 operationId") do
|
|
239
|
+
OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("json_sha256" => Digest::SHA256.file(context.fetch(:source_json)).hexdigest)))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
invalid_ref = JSON.parse(original_json)
|
|
243
|
+
invalid_ref.fetch("paths").fetch("/api/v1/orders/{orderId}").fetch("get").fetch("responses").fetch("200").fetch("content").fetch("application/json").fetch("schema")["$ref"] = "#/components/schemas/Missing"
|
|
244
|
+
File.write(context.fetch(:source_json), JSON.generate(invalid_ref))
|
|
245
|
+
expect_rejected("JSON $ref 必须可解析") do
|
|
246
|
+
OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("json_sha256" => Digest::SHA256.file(context.fetch(:source_json)).hexdigest)))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
changed_response = JSON.parse(original_json)
|
|
250
|
+
changed_response.fetch("paths").fetch("/api/v1/orders/{orderId}").fetch("get").fetch("responses").fetch("200").fetch("content").fetch("application/json").fetch("schema")["$ref"] = "#/components/schemas/ChangedOrder"
|
|
251
|
+
changed_response.fetch("components").fetch("schemas")["ChangedOrder"] = { "type" => "string" }
|
|
252
|
+
File.write(context.fetch(:source_json), JSON.generate(changed_response))
|
|
253
|
+
expect_rejected("JSON 必须保留冻结 YAML 的请求和响应契约") do
|
|
254
|
+
OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("json_sha256" => Digest::SHA256.file(context.fetch(:source_json)).hexdigest)))
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
File.binwrite(context.fetch(:source_json), original_json)
|
|
258
|
+
File.write(context.fetch(:materialized_json), "{\"openapi\":\"3.1.0\",\"paths\":{\"/tampered\":{}}}")
|
|
259
|
+
expect_rejected("前端 JSON 被手改") do
|
|
260
|
+
OpenapiJsonHandoff.validate!(**context)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
File.binwrite(context.fetch(:materialized_json), original_json)
|
|
264
|
+
File.write(context.fetch(:source_json), "{\"openapi\":\"3.1.0\",\"paths\":{\"/tampered-source\":{}}}")
|
|
265
|
+
expect_rejected("治理 JSON 被手改") do
|
|
266
|
+
OpenapiJsonHandoff.validate!(**context)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
puts "OpenAPI YAML-first JSON handoff scenarios passed"
|