create-yss-spec 2.1.3 → 2.1.4
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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.agents/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +1 -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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.claude/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +1 -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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.codex/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +1 -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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.hermes/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +1 -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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.pi/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +1 -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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.qoder/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +1 -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/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 +1 -1
- 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 +11 -5
- 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 +75 -28
- 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-router/SKILL.md +2 -2
- package/template/.trae/skills/yss-router/references/router-contract.yaml +4 -6
- package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.trae/skills/yss-router/references/yss-skill-execution-result.md +2 -1
- package/template/AGENTS.md +6 -4
- package/template/CONTEXT.md +15 -1
- package/template/README.md +2 -1
- package/template/__yss_dotfile__.gitignore +4 -0
- 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/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 +75 -39
- package/template/docs/process/lifecycle-registry-baseline.json +61 -0
- package/template/docs/process/lifecycle-registry.yaml +255 -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 +8 -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 +114 -17
- 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-template +27 -3
- package/template/scripts/verify-yss-router-scenarios +42 -14
- package/template/skills-lock.json +9 -24
- 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 +3 -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
|
@@ -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"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require "yaml"
|
|
7
|
+
|
|
8
|
+
ROOT = Pathname.new(__dir__).join("..").realpath
|
|
9
|
+
PROJECTION_ROOTS = %w[
|
|
10
|
+
.agents/skills
|
|
11
|
+
.claude/skills
|
|
12
|
+
.codex/skills
|
|
13
|
+
.hermes/skills
|
|
14
|
+
.pi/skills
|
|
15
|
+
.qoder/skills
|
|
16
|
+
.trae/skills
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
failures = []
|
|
20
|
+
|
|
21
|
+
template_path = ROOT.join("docs/templates/openapi-spec-template.yaml")
|
|
22
|
+
documents = YAML.load_stream(template_path.read)
|
|
23
|
+
failures << "OpenAPI YAML 模板必须是单一 YAML document" unless documents.length == 1
|
|
24
|
+
document = documents.first
|
|
25
|
+
unless document.is_a?(Hash) && document["openapi"] == "3.1.0"
|
|
26
|
+
failures << "OpenAPI YAML 模板必须是 OAS 3.1 文档"
|
|
27
|
+
end
|
|
28
|
+
%w[pipeline stage status owner].each do |field|
|
|
29
|
+
failures << "OpenAPI YAML 模板不得把生命周期元数据写入根字段: #{field}" if document.is_a?(Hash) && document.key?(field)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
PROJECTION_ROOTS.each do |root|
|
|
33
|
+
failures << "已移除的 yss-openapi 仍存在: #{root}/yss-openapi" if ROOT.join(root, "yss-openapi").exist?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
lock = JSON.parse(ROOT.join("skills-lock.json").read)
|
|
37
|
+
if lock.dig("skills", "shared", "yss-openapi")
|
|
38
|
+
failures << "skills-lock.json 仍登记 yss-openapi"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
public_skills = JSON.parse(ROOT.join("yss-public-skills.json").read)
|
|
42
|
+
if public_skills.fetch("skills").include?("yss-openapi")
|
|
43
|
+
failures << "yss-public-skills.json 仍公开 yss-openapi"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
router = ROOT.join(".agents/skills/yss-router/references/router-contract.yaml").read
|
|
47
|
+
removed_skill_pattern = /(?<![a-z0-9-])yss-openapi(?![a-z0-9-])/
|
|
48
|
+
failures << "Router 仍路由到 yss-openapi" if router.match?(removed_skill_pattern)
|
|
49
|
+
failures << "Router 脚手架合同仍允许旧 OpenAPI 基线" if router.include?("smart_doc_baseline")
|
|
50
|
+
unless router.include?("api-integration:\n when_regeneration: [yss-openapi-governance]")
|
|
51
|
+
failures << "Router 未将客户端重新生成路由到 yss-openapi-governance"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
lifecycle_contract = ROOT.join(".agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml").read
|
|
55
|
+
failures << "Lifecycle 脚手架合同仍允许旧 OpenAPI 基线" if lifecycle_contract.include?("smart_doc_baseline")
|
|
56
|
+
lifecycle_skill = ROOT.join(".agents/skills/yss-product-lifecycle/SKILL.md").read
|
|
57
|
+
failures << "Lifecycle 说明仍保留旧 OpenAPI 基线" if lifecycle_skill.match?(/smart[ -]?doc/i)
|
|
58
|
+
implementation_routing_template = ROOT.join("docs/templates/implementation-routing-template.md").read
|
|
59
|
+
failures << "实现路由模板仍保留旧 OpenAPI 基线" if implementation_routing_template.match?(/smart[ -]?doc/i)
|
|
60
|
+
|
|
61
|
+
scaffold_pom = ROOT.join(".agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template").read
|
|
62
|
+
failures << "后端脚手架仍包含 smart-doc Maven 插件" if scaffold_pom.include?("smart-doc-maven-plugin")
|
|
63
|
+
failures << "后端脚手架仍保留 smart-doc 配置模板" if ROOT.join(".agents/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template").exist?
|
|
64
|
+
|
|
65
|
+
scaffold_generator = ROOT.join(".agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py").read
|
|
66
|
+
failures << "后端脚手架生成器仍生成 smart-doc 配置" if scaffold_generator.include?("smart-doc.json") || scaffold_generator.include?("smart-doc-maven-plugin")
|
|
67
|
+
|
|
68
|
+
governance = ROOT.join(".agents/skills/yss-openapi-governance/SKILL.md").read
|
|
69
|
+
%w[YAML-first 唯一权威 redocly\ bundle --ext\ json operationId].each do |marker|
|
|
70
|
+
failures << "OpenAPI Governance 缺少 YAML-first JSON 派生规则: #{marker}" unless governance.include?(marker)
|
|
71
|
+
end
|
|
72
|
+
failures << "OpenAPI Governance 仍依赖 smart-doc" if governance.include?("smart-doc")
|
|
73
|
+
%w[pnpm\ generate:api orval.config unsafeDisableValidation].each do |marker|
|
|
74
|
+
failures << "OpenAPI Governance 越界到前端代码生成或 Orval 配置: #{marker}" if governance.include?(marker)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
api_integration = ROOT.join(".agents/skills/yss-api-integration/SKILL.md").read
|
|
78
|
+
%w[冻结的\ OpenAPI\ YAML redocly\ bundle 原样物化 既有前端代码生成流程].each do |marker|
|
|
79
|
+
failures << "API Integration 缺少冻结 JSON 的下游交接规则: #{marker}" unless api_integration.include?(marker)
|
|
80
|
+
end
|
|
81
|
+
failures << "API Integration 仍引用 yss-openapi" if api_integration.match?(removed_skill_pattern)
|
|
82
|
+
%w[docs/.scratch/<feature>/api/<feature>.json SHA-256 受控交接].each do |marker|
|
|
83
|
+
failures << "API Integration 未绑定治理 JSON 派生物: #{marker}" unless api_integration.include?(marker)
|
|
84
|
+
end
|
|
85
|
+
%w[orval.config unsafeDisableValidation].each do |marker|
|
|
86
|
+
failures << "API Integration 越界要求修改或核验 Orval 配置: #{marker}" if api_integration.include?(marker)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
frontend_scaffold = ROOT.join(".agents/skills/yss-frontend-scaffold-generator/SKILL.md").read
|
|
90
|
+
failures << "前端脚手架仍接受任意 openapi_source" if frontend_scaffold.include?("openapi_source")
|
|
91
|
+
%w[OpenAPI\ Freeze\ 记录 OpenAPI\ JSON\ 派生记录 SHA-256 openapi/openapi.json 既有前端代码生成流程].each do |marker|
|
|
92
|
+
failures << "前端脚手架未绑定冻结 JSON 交接: #{marker}" unless frontend_scaffold.include?(marker)
|
|
93
|
+
end
|
|
94
|
+
%w[配置并读取实际\ `orval.config.*` unsafeDisableValidation].each do |marker|
|
|
95
|
+
failures << "前端脚手架越界修改或核验既有 Orval 配置: #{marker}" if frontend_scaffold.include?(marker)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
export_record = ROOT.join("docs/api/templates/openapi-json-export-record-template.md")
|
|
99
|
+
failures << "缺少 OpenAPI JSON 派生记录模板" unless export_record.file?
|
|
100
|
+
if export_record.file?
|
|
101
|
+
export_record_text = export_record.read
|
|
102
|
+
%w[Orval\ 输入路径 orval.config unsafeDisableValidation pnpm\ generate:api].each do |marker|
|
|
103
|
+
failures << "JSON 派生记录越界记录前端 Orval 配置或执行: #{marker}" if export_record_text.include?(marker)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
freeze_record = ROOT.join("docs/api/templates/openapi-freeze-record-template.md")
|
|
108
|
+
%w[orval.config unsafeDisableValidation].each do |marker|
|
|
109
|
+
failures << "OpenAPI Freeze 记录越界规定 Orval 配置: #{marker}" if freeze_record.read.include?(marker)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
draft_checklist = ROOT.join("docs/api/templates/openapi-draft-review-checklist.md")
|
|
113
|
+
failures << "Draft 评审清单越界要求 Orval 输入路径" if draft_checklist.read.include?("Orval 输入路径")
|
|
114
|
+
|
|
115
|
+
unless failures.empty?
|
|
116
|
+
warn failures.join("\n")
|
|
117
|
+
exit 1
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
puts "OpenAPI YAML-first 场景验证通过"
|
|
@@ -29,6 +29,9 @@ required_files=(
|
|
|
29
29
|
"AGENTS.md"
|
|
30
30
|
"CONTEXT.md"
|
|
31
31
|
"skills-lock.json"
|
|
32
|
+
"docs/process/lifecycle-registry.yaml"
|
|
33
|
+
"docs/process/lifecycle-registry-baseline.json"
|
|
34
|
+
"docs/process/schemas/lifecycle-registry.schema.json"
|
|
32
35
|
"docs/process/lifecycle-artifact-map.md"
|
|
33
36
|
"docs/process/harness-process-tailoring.md"
|
|
34
37
|
"docs/templates/local-parent-ticket-template.md"
|
|
@@ -39,11 +42,17 @@ required_files=(
|
|
|
39
42
|
"scripts/update-skill-lock"
|
|
40
43
|
"scripts/implementation-path-policy.rb"
|
|
41
44
|
"scripts/verify-lifecycle-scenarios"
|
|
45
|
+
"scripts/generate-lifecycle-artifacts"
|
|
46
|
+
"scripts/verify-lifecycle-registry"
|
|
47
|
+
"scripts/test-lifecycle-registry.rb"
|
|
48
|
+
"scripts/verify-governance-release"
|
|
42
49
|
"scripts/verify-implementation-path-scenarios"
|
|
43
50
|
"scripts/verify-scaffold-generator-scenarios"
|
|
44
51
|
"scripts/verify-prototype-backend-scaffold-scenarios"
|
|
45
52
|
"scripts/verify-matt-yss-integration-scenarios"
|
|
46
53
|
"scripts/verify-yss-ui-scenarios"
|
|
54
|
+
"scripts/verify-openapi-yaml-first-scenarios"
|
|
55
|
+
"scripts/verify-openapi-json-handoff-scenarios"
|
|
47
56
|
)
|
|
48
57
|
|
|
49
58
|
required_skills=(
|
|
@@ -120,19 +129,28 @@ lock.fetch("skills", {}).fetch("shared", {}).each do |skill_name, metadata|
|
|
|
120
129
|
end
|
|
121
130
|
|
|
122
131
|
openapi_path = root.join("docs/templates/openapi-spec-template.yaml")
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
documents = YAML.load_stream(openapi_path.binread.force_encoding("UTF-8"))
|
|
133
|
+
unless documents.length == 1 && documents.first.is_a?(Hash) && documents.first["openapi"] == "3.1.0"
|
|
134
|
+
warn "docs/templates/openapi-spec-template.yaml 必须是单一 OAS 3.1 YAML document"
|
|
135
|
+
exit 1
|
|
136
|
+
end
|
|
125
137
|
RUBY
|
|
126
138
|
|
|
127
139
|
scripts/sync-skills --check
|
|
128
140
|
scripts/update-skill-lock --check
|
|
141
|
+
scripts/verify-lifecycle-registry
|
|
129
142
|
scripts/verify-lifecycle-scenarios
|
|
143
|
+
scripts/verify-governance-release
|
|
130
144
|
scripts/verify-implementation-path-scenarios
|
|
131
145
|
scripts/verify-scaffold-generator-scenarios
|
|
132
146
|
scripts/verify-prototype-backend-scaffold-scenarios
|
|
133
147
|
ruby scripts/verify-matt-yss-integration-scenarios
|
|
134
148
|
scripts/verify-yss-router-scenarios
|
|
135
149
|
scripts/verify-yss-ui-scenarios
|
|
150
|
+
scripts/verify-openapi-yaml-first-scenarios
|
|
151
|
+
scripts/verify-openapi-json-handoff-scenarios
|
|
152
|
+
ruby scripts/test-lifecycle-registry.rb
|
|
153
|
+
ruby scripts/test-export-yss-skills.rb
|
|
136
154
|
|
|
137
155
|
ruby <<'RUBY'
|
|
138
156
|
# encoding: UTF-8
|
|
@@ -161,7 +179,8 @@ obsolete_names = [
|
|
|
161
179
|
"obsidian-vault",
|
|
162
180
|
"writing-great-skills",
|
|
163
181
|
"code-review-process",
|
|
164
|
-
"yss-domain-modeling"
|
|
182
|
+
"yss-domain-modeling",
|
|
183
|
+
"yss-openapi"
|
|
165
184
|
].freeze
|
|
166
185
|
|
|
167
186
|
agent_roots.each do |agent_root|
|
|
@@ -266,6 +285,11 @@ ruby -c scripts/sync-skills >/dev/null
|
|
|
266
285
|
ruby -c scripts/update-skill-lock >/dev/null
|
|
267
286
|
ruby -c scripts/implementation-path-policy.rb >/dev/null
|
|
268
287
|
ruby -c scripts/verify-lifecycle-scenarios >/dev/null
|
|
288
|
+
ruby -c scripts/lifecycle-registry.rb >/dev/null
|
|
289
|
+
ruby -c scripts/generate-lifecycle-artifacts >/dev/null
|
|
290
|
+
ruby -c scripts/verify-lifecycle-registry >/dev/null
|
|
291
|
+
ruby -c scripts/test-lifecycle-registry.rb >/dev/null
|
|
292
|
+
ruby -c scripts/verify-governance-release >/dev/null
|
|
269
293
|
ruby -c scripts/verify-prototype-backend-scaffold-scenarios >/dev/null
|
|
270
294
|
ruby -c scripts/verify-matt-yss-integration-scenarios >/dev/null
|
|
271
295
|
ruby -c scripts/verify-yss-ui-scenarios >/dev/null
|
|
@@ -38,7 +38,7 @@ def routing_input_violations(contract, input)
|
|
|
38
38
|
contract.fetch("exclusive_flags").each do |left, right|
|
|
39
39
|
violations << "#{left}/#{right}" unless flags[left] != flags[right]
|
|
40
40
|
end
|
|
41
|
-
violations
|
|
41
|
+
violations.uniq
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def active_impacts(input)
|
|
@@ -49,7 +49,8 @@ base_input = {
|
|
|
49
49
|
"slice_id" => "order-create",
|
|
50
50
|
"impact_flags" => {
|
|
51
51
|
"ui_impact" => true, "api_impact" => true, "no_api_impact" => false,
|
|
52
|
-
"data_impact" => false, "backend_impact" => false, "cross_repo" => false
|
|
52
|
+
"data_impact" => false, "backend_impact" => false, "cross_repo" => false,
|
|
53
|
+
"existing_frozen_baseline" => false
|
|
53
54
|
},
|
|
54
55
|
"lifecycle" => {}, "implementation" => {}, "acceptance" => {}
|
|
55
56
|
}
|
|
@@ -60,6 +61,16 @@ failures << "Invalid lifecycle input type was accepted" if routing_input_violati
|
|
|
60
61
|
both_api_modes = base_input["impact_flags"].merge("api_impact" => true, "no_api_impact" => true)
|
|
61
62
|
failures << "API/no-API exclusivity was not enforced" if routing_input_violations(CONTRACT, base_input.merge("impact_flags" => both_api_modes)).empty?
|
|
62
63
|
|
|
64
|
+
legacy_posture_key = %w[security permission posture].join("_")
|
|
65
|
+
failures << "Router 输入仍要求已废弃的专项输入" if CONTRACT.fetch("routing_input_required").include?(legacy_posture_key)
|
|
66
|
+
failures << "Router 仍声明已废弃的专项合同" if CONTRACT.key?(legacy_posture_key)
|
|
67
|
+
legacy_impact_flags = CONTRACT.fetch("impact_flags_required").select do |flag|
|
|
68
|
+
%w[permission security].any? { |prefix| flag.start_with?("#{prefix}_") }
|
|
69
|
+
end
|
|
70
|
+
failures << "Router 仍要求安全 / 权限派生旗标" unless (CONTRACT.fetch("impact_flags_required") & legacy_impact_flags).empty?
|
|
71
|
+
explicit_authorization_input = base_input.merge("acceptance" => {"behavior" => "角色授权规则变化"})
|
|
72
|
+
failures << "显式权限行为无法按普通输入进入 Router" unless routing_input_violations(CONTRACT, explicit_authorization_input).empty?
|
|
73
|
+
|
|
63
74
|
common_ready = CONTRACT.dig("readiness", "common").to_h { |key| [key, true] }
|
|
64
75
|
ui_ready = CONTRACT.dig("readiness", "ui_impact").to_h { |key| [key, true] }
|
|
65
76
|
api_ready = CONTRACT.dig("readiness", "api_impact").to_h { |key| [key, true] }
|
|
@@ -504,14 +515,26 @@ backend_skill_mutation["backend"]["required_skills"].delete("mapstruct")
|
|
|
504
515
|
failures << "Backend contract accepted incomplete skill closure" unless slice_contract_decision_violations(CONTRACT, backend_input, backend_evidence, backend_skill_mutation, backend_expected_skills).include?("backend.required_skills")
|
|
505
516
|
failures << "Backend contract accepted omitted skill closure" if slice_contract_decision_violations(CONTRACT, backend_input, backend_evidence, backend_slice, []).empty?
|
|
506
517
|
|
|
518
|
+
component_route_examples = {
|
|
519
|
+
"Current-user" => {"impact" => "current-user-context", "skills" => ["yss-userinfo"]},
|
|
520
|
+
"Audit" => {"impact" => "audit-event", "skills" => ["yss-audit-log", "yss-userinfo", "yss-dto"]},
|
|
521
|
+
"Encryption" => {"impact" => "encryption-or-signing", "skills" => ["yss-security-algorithm", "yss-exception"]}
|
|
522
|
+
}
|
|
523
|
+
component_route_examples.each do |label, example|
|
|
524
|
+
candidate = Marshal.load(Marshal.dump(backend_slice))
|
|
525
|
+
candidate["backend"]["component_impacts"] = [example.fetch("impact")]
|
|
526
|
+
required_skills = dependency_closure(CONTRACT, backend_expected_skills + example.fetch("skills"), {})
|
|
527
|
+
candidate["backend"]["required_skills"] = required_skills
|
|
528
|
+
candidate["common"]["required_skills"] = required_skills
|
|
529
|
+
failures << "#{label} component route was rejected" unless slice_contract_decision_violations(
|
|
530
|
+
CONTRACT, backend_input, backend_evidence, candidate, required_skills
|
|
531
|
+
).empty?
|
|
532
|
+
end
|
|
507
533
|
component_slice = Marshal.load(Marshal.dump(backend_slice))
|
|
508
534
|
component_slice["backend"]["component_impacts"] = ["current-user-context"]
|
|
509
535
|
component_skills = dependency_closure(CONTRACT, backend_expected_skills + ["yss-userinfo"], {})
|
|
510
536
|
component_slice["backend"]["required_skills"] = component_skills
|
|
511
537
|
component_slice["common"]["required_skills"] = component_skills
|
|
512
|
-
failures << "Current-user component route was rejected" unless slice_contract_decision_violations(
|
|
513
|
-
CONTRACT, backend_input, backend_evidence, component_slice, component_skills
|
|
514
|
-
).empty?
|
|
515
538
|
unknown_component = Marshal.load(Marshal.dump(component_slice))
|
|
516
539
|
unknown_component["backend"]["component_impacts"] = ["unknown-component"]
|
|
517
540
|
unless slice_contract_decision_violations(CONTRACT, backend_input, backend_evidence, unknown_component, component_skills).include?("backend.component_impacts")
|
|
@@ -730,6 +753,17 @@ mutations = {
|
|
|
730
753
|
"drift" => valid_result.merge("status" => "drift"),
|
|
731
754
|
"violation" => valid_result.merge("status" => "violation")
|
|
732
755
|
}
|
|
756
|
+
|
|
757
|
+
valid_deferred = valid_result.merge(
|
|
758
|
+
"status" => "seam-deferred",
|
|
759
|
+
"seam_deferred" => [{
|
|
760
|
+
"risk" => "temporary adapter", "owner" => "team", "follow_up_ticket" => "T-2",
|
|
761
|
+
"verification_plan" => "contract test", "target_version_or_release_date" => "2026-09-01"
|
|
762
|
+
}]
|
|
763
|
+
)
|
|
764
|
+
unless execution_violations(CONTRACT, valid_deferred, *valid_args).empty?
|
|
765
|
+
failures << "Valid seam-deferred result was rejected"
|
|
766
|
+
end
|
|
733
767
|
mutations.each do |expected, result|
|
|
734
768
|
blocker = expected.sub(/-(empty|command|contract-version|status|shape|work-unit|missing|implemented-with-seam|not-applicable-reason|identity|area)$/, "")
|
|
735
769
|
actual = execution_violations(CONTRACT, result, *valid_args)
|
|
@@ -870,15 +904,9 @@ end
|
|
|
870
904
|
|
|
871
905
|
scaffold_pom_template = ROOT.join(".agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template")
|
|
872
906
|
scaffold_pom_body = scaffold_pom_template.read
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
smart_doc_body = smart_doc_template.file? ? smart_doc_template.read : ""
|
|
877
|
-
%w[allInOne outPath packageFilters componentType].each do |marker|
|
|
878
|
-
failures << "smart-doc template missing #{marker}" unless smart_doc_body.include?("\"#{marker}\"")
|
|
879
|
-
end
|
|
880
|
-
failures << "smart-doc packageFilters must use the supported single-star regex" unless smart_doc_body.include?("\"packageFilters\": \"{{base_package}}.*\"")
|
|
881
|
-
failures << "smart-doc template contains environment secret settings" if smart_doc_body.match?(/appToken|openUrl|debugEnvUrl|serverUrl/)
|
|
907
|
+
failures << "后端脚手架不得保留旧 OpenAPI Maven 插件" if scaffold_pom_body.include?("smart-doc-maven-plugin")
|
|
908
|
+
legacy_openapi_template = ROOT.join(".agents/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template")
|
|
909
|
+
failures << "后端脚手架不得保留旧 OpenAPI 配置模板" if legacy_openapi_template.exist?
|
|
882
910
|
|
|
883
911
|
%w[
|
|
884
912
|
yss-domain/SKILL.md
|