create-yss-spec 2.2.3 → 2.2.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 +17 -1
- package/package.json +1 -1
- package/src/cli.js +15 -1
- package/src/self-update.js +277 -0
- package/template/.agents/skills/yss-domain/SKILL.md +5 -4
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.agents/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.agents/skills/yss-router/SKILL.md +4 -2
- package/template/.agents/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.agents/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.agents/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.agents/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.agents/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.agents/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.agents/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.agents/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.agents/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.agents/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.agents/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.agents/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.agents/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.agents/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.agents/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.agents/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.agents/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.claude/skills/yss-domain/SKILL.md +5 -4
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.claude/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.claude/skills/yss-router/SKILL.md +4 -2
- package/template/.claude/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.claude/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.claude/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.claude/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.claude/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.claude/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.claude/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.claude/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.claude/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.claude/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.claude/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.claude/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.claude/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.claude/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.claude/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.claude/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.claude/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.claude/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.claude/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.claude/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.claude/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.claude/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.claude/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.claude/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.claude/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.claude/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.claude/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.claude/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.claude/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.claude/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.codex/skills/yss-domain/SKILL.md +5 -4
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.codex/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.codex/skills/yss-router/SKILL.md +4 -2
- package/template/.codex/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.codex/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.codex/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.codex/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.codex/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.codex/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.codex/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.codex/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.codex/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.codex/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.codex/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.codex/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.codex/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.codex/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.codex/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.codex/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.codex/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.cursor/skills/yss-domain/SKILL.md +5 -4
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.cursor/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.cursor/skills/yss-router/SKILL.md +4 -2
- package/template/.cursor/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.cursor/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.cursor/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.cursor/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.cursor/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.cursor/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.cursor/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.cursor/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.cursor/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.cursor/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.cursor/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.cursor/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.cursor/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.cursor/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.cursor/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.cursor/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.cursor/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.hermes/skills/yss-domain/SKILL.md +5 -4
- package/template/.hermes/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.hermes/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.hermes/skills/yss-router/SKILL.md +4 -2
- package/template/.hermes/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.hermes/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.hermes/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.hermes/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.hermes/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.hermes/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.hermes/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.hermes/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.hermes/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.hermes/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.hermes/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.hermes/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.hermes/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.hermes/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.hermes/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.hermes/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.hermes/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.hermes/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.hermes/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.hermes/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.hermes/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.hermes/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.hermes/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.hermes/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.hermes/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.hermes/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.hermes/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.hermes/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.hermes/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.hermes/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.pi/skills/yss-domain/SKILL.md +5 -4
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.pi/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.pi/skills/yss-router/SKILL.md +4 -2
- package/template/.pi/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.pi/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.pi/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.pi/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.pi/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.pi/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.pi/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.pi/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.pi/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.pi/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.pi/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.pi/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.pi/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.pi/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.pi/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.pi/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.pi/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.qoder/skills/yss-domain/SKILL.md +5 -4
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.qoder/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.qoder/skills/yss-router/SKILL.md +4 -2
- package/template/.qoder/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.qoder/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.qoder/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.qoder/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.qoder/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.qoder/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.qoder/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.qoder/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.qoder/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.qoder/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.qoder/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.qoder/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.qoder/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.qoder/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.qoder/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.qoder/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.qoder/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.qoder/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.qoder/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.qoder/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.qoder/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.qoder/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.qoder/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.qoder/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.qoder/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.qoder/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.qoder/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.qoder/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.qoder/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.qoder/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/.trae/skills/yss-domain/SKILL.md +5 -4
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +15 -14
- package/template/.trae/skills/yss-product-lifecycle/references/artifact-dependencies.md +4 -0
- package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +139 -4
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +6 -3
- package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +7 -2
- package/template/.trae/skills/yss-router/SKILL.md +4 -2
- package/template/.trae/skills/yss-router/references/router-contract.yaml +18 -3
- package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +58 -1
- package/template/.trae/skills/yss-stage-decision/SKILL.md +50 -0
- package/template/.trae/skills/yss-stage-decision/references/domain-strategy-contract.md +26 -0
- package/template/.trae/skills/yss-stage-decision/references/domain-strategy.schema.json +128 -0
- package/template/.trae/skills/yss-stage-decision/references/stage-decision-package-contract.md +19 -0
- package/template/.trae/skills/yss-stage-decision/references/stage-decision-package.schema.json +86 -0
- package/template/.trae/skills/yss-stage-decision/references/validation-rules.md +18 -0
- package/template/.trae/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +169 -0
- package/template/.trae/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +100 -0
- package/template/.trae/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml +15 -0
- package/template/.trae/skills/yss-stage-decision/tests/fixtures/invalid-direction.yaml +58 -0
- package/template/.trae/skills/yss-stage-decision/tests/fixtures/invalid-stage-decision-blocker.yaml +44 -0
- package/template/.trae/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml +15 -0
- package/template/.trae/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +49 -0
- package/template/.trae/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +108 -0
- package/template/.trae/skills/yss-stage-decision/tests/run-scenarios.mjs +68 -0
- package/template/.trae/skills/yss-tactical-design/SKILL.md +33 -0
- package/template/.trae/skills/yss-tactical-design/agents/openai.yaml +4 -0
- package/template/.trae/skills/yss-tactical-design/references/output-template.md +28 -0
- package/template/.trae/skills/yss-tactical-design/references/tactical-design-contract.md +41 -0
- package/template/.trae/skills/yss-tactical-design/references/tactical-design.schema.json +31 -0
- package/template/.trae/skills/yss-tactical-design/references/validation-rules.md +13 -0
- package/template/.trae/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +213 -0
- package/template/.trae/skills/yss-tactical-design/tests/fixtures/invalid-api-leaks-aggregate.yaml +21 -0
- package/template/.trae/skills/yss-tactical-design/tests/fixtures/invalid-missing-consistency-policy.yaml +21 -0
- package/template/.trae/skills/yss-tactical-design/tests/fixtures/invalid-missing-invariant.yaml +21 -0
- package/template/.trae/skills/yss-tactical-design/tests/fixtures/invalid-stale-upstream-ref.yaml +21 -0
- package/template/.trae/skills/yss-tactical-design/tests/fixtures/invalid-strategic-as-tactical.yaml +21 -0
- package/template/.trae/skills/yss-tactical-design/tests/fixtures/valid-tactical-design.yaml +80 -0
- package/template/.trae/skills/yss-tactical-design/tests/run-scenarios.mjs +47 -0
- package/template/AGENTS.md +22 -5
- package/template/CONTEXT.md +1 -1
- package/template/docs/agents/README.md +1 -1
- package/template/docs/agents/digital-human-roles.md +2 -2
- package/template/docs/agents/digital-human-roles.yaml +7 -1
- package/template/docs/agents/yss-skill-registry.yaml +12 -0
- package/template/docs/architecture/templates/system-overview-design-template.md +1 -0
- package/template/docs/process/lifecycle-artifact-map.md +8 -0
- package/template/docs/process/lifecycle-registry-baseline.json +11 -1
- package/template/docs/process/lifecycle-registry.yaml +46 -2
- package/template/docs/process/schemas/digital-human-task-package.schema.json +107 -0
- package/template/docs/process/schemas/subagent-task-package.schema.json +6 -0
- package/template/docs/process/subagent-collaboration.md +2 -2
- package/template/docs/templates/subagent-task-package-template.md +39 -1
- package/template/scripts/lib/lifecycle-transition.mjs +259 -0
- package/template/scripts/lib/scenario-checks.mjs +96 -2
- package/template/scripts/lib/task-package.mjs +214 -0
- package/template/scripts/verify-digital-human-task-package +14 -0
- package/template/scripts/verify-digital-human-task-package-scenarios +8 -0
- package/template/scripts/verify-lifecycle-transition-scenarios +105 -0
- package/template/scripts/verify-subagent-task-package +8 -0
- package/template/scripts/verify-subagent-task-package-scenarios +196 -0
- package/template/scripts/verify-template +13 -2
- package/template/skills-lock.json +36 -4
- package/template.snapshot.json +4 -4
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { parseDocument } from "../vendor/yaml.mjs";
|
|
3
|
+
|
|
4
|
+
const IMPLEMENTATION_WORK_UNIT = "work-unit.slice-implementation";
|
|
5
|
+
const TICKET_DECOMPOSITION_WORK_UNIT = "work-unit.ticket-decomposition";
|
|
6
|
+
|
|
7
|
+
function deepFreeze(value) {
|
|
8
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
9
|
+
Object.freeze(value);
|
|
10
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NEXT_ROUTES = deepFreeze({
|
|
15
|
+
"work-unit.entry-triage": ["work-unit.discovery-opportunity", "work-unit.discovery-requirements"],
|
|
16
|
+
"work-unit.ssot-update": ["work-unit.skill-projection-sync", "work-unit.intensity-aware-verification"],
|
|
17
|
+
"work-unit.skill-projection-sync": ["work-unit.template-snapshot-build", "work-unit.intensity-aware-verification"],
|
|
18
|
+
"work-unit.template-snapshot-build": ["work-unit.attach-sync-integration", "work-unit.intensity-aware-verification"],
|
|
19
|
+
"work-unit.attach-sync-integration": ["work-unit.intensity-aware-verification"],
|
|
20
|
+
"work-unit.intensity-aware-verification": ["work-unit.intensity-aware-review"],
|
|
21
|
+
"work-unit.intensity-aware-review": ["work-unit.release-and-rollback"],
|
|
22
|
+
"work-unit.release-and-rollback": [],
|
|
23
|
+
"work-unit.discovery-opportunity": ["work-unit.discovery-requirements", "work-unit.domain-strategy-design", "work-unit.stage-decision", "work-unit.spec-synthesis"],
|
|
24
|
+
"work-unit.discovery-requirements": ["work-unit.domain-strategy-design", "work-unit.stage-decision", "work-unit.spec-synthesis"],
|
|
25
|
+
"work-unit.domain-strategy-design": ["work-unit.stage-decision", "work-unit.spec-synthesis"],
|
|
26
|
+
"work-unit.stage-decision": ["work-unit.spec-synthesis"],
|
|
27
|
+
"work-unit.spec-synthesis": ["work-unit.prototype-design", "work-unit.technical-analysis", TICKET_DECOMPOSITION_WORK_UNIT],
|
|
28
|
+
"work-unit.prototype-design": ["work-unit.technical-analysis", TICKET_DECOMPOSITION_WORK_UNIT],
|
|
29
|
+
"work-unit.technical-analysis": [TICKET_DECOMPOSITION_WORK_UNIT],
|
|
30
|
+
[TICKET_DECOMPOSITION_WORK_UNIT]: [IMPLEMENTATION_WORK_UNIT],
|
|
31
|
+
[IMPLEMENTATION_WORK_UNIT]: ["work-unit.frontend-implementation-verification", "work-unit.code-review"],
|
|
32
|
+
"work-unit.frontend-implementation-verification": ["work-unit.code-review"],
|
|
33
|
+
"work-unit.code-review": ["work-unit.release-and-retrospective", IMPLEMENTATION_WORK_UNIT],
|
|
34
|
+
"work-unit.release-and-retrospective": [],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const BLOCKING_SIGNALS = Object.freeze({
|
|
38
|
+
invalidRoute: "illegal-next-route",
|
|
39
|
+
implementationBeforeTickets: "ticket-formalization-required",
|
|
40
|
+
invalidPredecessor: "invalid-implementation-predecessor",
|
|
41
|
+
decompositionIncomplete: "ticket-decomposition-incomplete",
|
|
42
|
+
decompositionRefMissing: "ticket-decomposition-result-ref-missing",
|
|
43
|
+
missingSlice: "vertical-slice-ticket-required",
|
|
44
|
+
sliceRefMissing: "vertical-slice-ticket-ref-missing",
|
|
45
|
+
parentTicket: "parent-ticket-forbidden",
|
|
46
|
+
wrongKind: "vertical-slice-ticket-kind-invalid",
|
|
47
|
+
kindMissing: "vertical-slice-ticket-kind-missing",
|
|
48
|
+
unreadableSlice: "vertical-slice-ticket-unreadable",
|
|
49
|
+
wrongRole: "ticket-not-ready-for-agent",
|
|
50
|
+
roleMissing: "vertical-slice-ticket-role-missing",
|
|
51
|
+
contractMismatch: "slice-contract-ticket-mismatch",
|
|
52
|
+
contractNotApproved: "slice-contract-not-approved",
|
|
53
|
+
contractNotPersisted: "slice-contract-not-persisted",
|
|
54
|
+
contractNotCurrent: "slice-contract-not-current",
|
|
55
|
+
missingEvidence: "ticket-formalization-evidence-missing",
|
|
56
|
+
stale: "ticket-formalization-stale",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const allowedResult = (evidenceRefs = []) => ({
|
|
60
|
+
result: "allowed",
|
|
61
|
+
blocking_signals: [],
|
|
62
|
+
missing_requirements: [],
|
|
63
|
+
evidence_refs: evidenceRefs,
|
|
64
|
+
next_work_unit: null,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const blockedResult = (signals, missing = [], evidenceRefs = []) => ({
|
|
68
|
+
result: "blocked",
|
|
69
|
+
blocking_signals: [...new Set(signals)],
|
|
70
|
+
missing_requirements: [...new Set(missing)],
|
|
71
|
+
evidence_refs: evidenceRefs,
|
|
72
|
+
next_work_unit: null,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function hasText(value) {
|
|
76
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isReadable(ref, exists) {
|
|
80
|
+
return hasText(ref) && exists(ref);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readDecompositionResult(ref, read) {
|
|
84
|
+
if (!hasText(ref)) return null;
|
|
85
|
+
try {
|
|
86
|
+
const document = parseDocument(read(ref), { maxAliasCount: 0, uniqueKeys: true });
|
|
87
|
+
if (document.errors.length > 0) return null;
|
|
88
|
+
const value = document.toJS({ maxAliasCount: 0 });
|
|
89
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateTicketPath(ref) {
|
|
96
|
+
if (!hasText(ref)) return false;
|
|
97
|
+
return /^docs\/\.scratch\/[^/]+\/issues\/[^/]+\.md$/.test(ref);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateTicketReference(ref, trackerKind) {
|
|
101
|
+
if (trackerKind === "github" || trackerKind === "gitlab") {
|
|
102
|
+
return /^(https?:\/\/|(?:github|gitlab):)[^\s]+$/.test(ref);
|
|
103
|
+
}
|
|
104
|
+
return validateTicketPath(ref);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validate a lifecycle work-unit transition without mutating state.
|
|
109
|
+
* `nextRoute` is null only for a terminal work unit. A blocked or needs-human
|
|
110
|
+
* Workflow Execution Result may omit a route; completed results are checked
|
|
111
|
+
* by `validateWorkflowExecutionResult` before this function is called.
|
|
112
|
+
*/
|
|
113
|
+
export function validateNextRoute(currentWorkUnit, nextRoute) {
|
|
114
|
+
const routes = NEXT_ROUTES[currentWorkUnit];
|
|
115
|
+
if (!routes) return blockedResult([BLOCKING_SIGNALS.invalidRoute], ["known_current_work_unit"]);
|
|
116
|
+
if (nextRoute === null && routes.length === 0) return allowedResult();
|
|
117
|
+
if (!hasText(nextRoute) || !routes.includes(nextRoute)) {
|
|
118
|
+
const signals = [BLOCKING_SIGNALS.invalidRoute];
|
|
119
|
+
if (nextRoute === IMPLEMENTATION_WORK_UNIT && currentWorkUnit !== TICKET_DECOMPOSITION_WORK_UNIT) {
|
|
120
|
+
signals.push(BLOCKING_SIGNALS.implementationBeforeTickets);
|
|
121
|
+
}
|
|
122
|
+
return blockedResult(signals, ["allowed_next_route"]);
|
|
123
|
+
}
|
|
124
|
+
return allowedResult();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validate that Ticket formalization produced a real, implementable vertical slice.
|
|
129
|
+
* `exists` is injectable so external adapters can resolve their own tracker refs.
|
|
130
|
+
*/
|
|
131
|
+
export function validateTicketFormalization(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8") } = {}) {
|
|
132
|
+
const decomposition = state?.ticket_decomposition_result;
|
|
133
|
+
const ticket = state?.vertical_slice_ticket;
|
|
134
|
+
const contract = state?.slice_contract;
|
|
135
|
+
const decompositionRef = state?.ticket_decomposition_result_ref;
|
|
136
|
+
const sliceRef = state?.vertical_slice_ticket_ref;
|
|
137
|
+
const sliceRole = state?.vertical_slice_ticket_role;
|
|
138
|
+
const sliceKind = state?.vertical_slice_ticket_kind;
|
|
139
|
+
const trackerKind = state?.tracker_kind ?? "local-markdown";
|
|
140
|
+
const evidenceRefs = decomposition?.evidence_refs ?? [];
|
|
141
|
+
const persistedDecomposition = readDecompositionResult(decompositionRef, read);
|
|
142
|
+
const signals = [];
|
|
143
|
+
const missing = [];
|
|
144
|
+
|
|
145
|
+
if (decomposition?.result !== "completed") {
|
|
146
|
+
signals.push(BLOCKING_SIGNALS.decompositionIncomplete);
|
|
147
|
+
missing.push("ticket_decomposition_result.result=completed");
|
|
148
|
+
}
|
|
149
|
+
if (!persistedDecomposition || persistedDecomposition.result_schema !== "workflow-execution-result-v1" || persistedDecomposition.result !== "completed" || persistedDecomposition.work_unit !== TICKET_DECOMPOSITION_WORK_UNIT) {
|
|
150
|
+
signals.push(BLOCKING_SIGNALS.decompositionIncomplete);
|
|
151
|
+
missing.push("ticket_decomposition_result_ref must contain completed work-unit.ticket-decomposition result");
|
|
152
|
+
}
|
|
153
|
+
if (persistedDecomposition && Array.isArray(persistedDecomposition.evidence_refs)) {
|
|
154
|
+
if (persistedDecomposition.evidence_refs.some((ref) => !isReadable(ref, exists))) {
|
|
155
|
+
signals.push(BLOCKING_SIGNALS.missingEvidence);
|
|
156
|
+
missing.push("persisted decomposition result evidence_refs must be readable");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!isReadable(decompositionRef, exists) || !evidenceRefs.includes(decompositionRef)) {
|
|
160
|
+
signals.push(BLOCKING_SIGNALS.decompositionRefMissing);
|
|
161
|
+
missing.push("readable ticket_decomposition_result_ref included in evidence_refs");
|
|
162
|
+
}
|
|
163
|
+
if (!Array.isArray(evidenceRefs) || evidenceRefs.length === 0 || evidenceRefs.some((ref) => !isReadable(ref, exists))) {
|
|
164
|
+
signals.push(BLOCKING_SIGNALS.missingEvidence);
|
|
165
|
+
missing.push("readable ticket_decomposition_result.evidence_refs");
|
|
166
|
+
}
|
|
167
|
+
if (!hasText(sliceRef)) {
|
|
168
|
+
signals.push(BLOCKING_SIGNALS.sliceRefMissing);
|
|
169
|
+
missing.push("vertical_slice_ticket_ref");
|
|
170
|
+
}
|
|
171
|
+
if (!hasText(sliceKind)) {
|
|
172
|
+
signals.push(BLOCKING_SIGNALS.kindMissing);
|
|
173
|
+
missing.push("vertical_slice_ticket_kind");
|
|
174
|
+
}
|
|
175
|
+
if (!hasText(sliceRole)) {
|
|
176
|
+
signals.push(BLOCKING_SIGNALS.roleMissing);
|
|
177
|
+
missing.push("vertical_slice_ticket_role");
|
|
178
|
+
}
|
|
179
|
+
if (!ticket || !hasText(ticket.ref)) {
|
|
180
|
+
signals.push(BLOCKING_SIGNALS.missingSlice);
|
|
181
|
+
missing.push("vertical_slice_ticket.ref");
|
|
182
|
+
} else {
|
|
183
|
+
if (sliceRef !== ticket.ref) {
|
|
184
|
+
signals.push(BLOCKING_SIGNALS.contractMismatch);
|
|
185
|
+
missing.push("vertical_slice_ticket_ref=vertical_slice_ticket.ref");
|
|
186
|
+
}
|
|
187
|
+
if (sliceKind !== ticket.kind) {
|
|
188
|
+
signals.push(BLOCKING_SIGNALS.wrongKind);
|
|
189
|
+
missing.push("vertical_slice_ticket_kind=vertical_slice_ticket.kind");
|
|
190
|
+
}
|
|
191
|
+
if (sliceRole !== ticket.role) {
|
|
192
|
+
signals.push(BLOCKING_SIGNALS.wrongRole);
|
|
193
|
+
missing.push("vertical_slice_ticket_role=vertical_slice_ticket.role");
|
|
194
|
+
}
|
|
195
|
+
if (!validateTicketReference(ticket.ref, trackerKind)) {
|
|
196
|
+
signals.push(ticket.kind === "parent-ticket" || ticket.ref.endsWith("/parent-ticket.md") ? BLOCKING_SIGNALS.parentTicket : BLOCKING_SIGNALS.missingSlice);
|
|
197
|
+
missing.push(trackerKind === "local-markdown" ? "vertical_slice_ticket.ref under docs/.scratch/<feature>/issues/" : "remote tracker Ticket reference");
|
|
198
|
+
}
|
|
199
|
+
if (ticket.kind === "parent-ticket" || ticket.ref.endsWith("/parent-ticket.md")) {
|
|
200
|
+
signals.push(BLOCKING_SIGNALS.parentTicket);
|
|
201
|
+
missing.push("vertical_slice_ticket.kind=vertical-slice-ticket");
|
|
202
|
+
} else if (ticket.kind !== "vertical-slice-ticket") {
|
|
203
|
+
signals.push(BLOCKING_SIGNALS.wrongKind);
|
|
204
|
+
missing.push("vertical_slice_ticket.kind=vertical-slice-ticket");
|
|
205
|
+
}
|
|
206
|
+
if (ticket.role !== "ready-for-agent") {
|
|
207
|
+
signals.push(BLOCKING_SIGNALS.wrongRole);
|
|
208
|
+
missing.push("vertical_slice_ticket.role=ready-for-agent");
|
|
209
|
+
}
|
|
210
|
+
if (trackerKind === "local-markdown" && !isReadable(ticket.ref, exists)) {
|
|
211
|
+
signals.push(BLOCKING_SIGNALS.unreadableSlice);
|
|
212
|
+
missing.push("readable vertical_slice_ticket.ref");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!contract || !hasText(contract.ticket_ref) || contract.ticket_ref !== ticket?.ref) {
|
|
216
|
+
signals.push(BLOCKING_SIGNALS.contractMismatch);
|
|
217
|
+
missing.push("slice_contract.ticket_ref=vertical_slice_ticket.ref");
|
|
218
|
+
}
|
|
219
|
+
if (contract?.status !== "approved") {
|
|
220
|
+
signals.push(BLOCKING_SIGNALS.contractNotApproved);
|
|
221
|
+
missing.push("slice_contract.status=approved");
|
|
222
|
+
}
|
|
223
|
+
if (contract?.persisted !== true) {
|
|
224
|
+
signals.push(BLOCKING_SIGNALS.contractNotPersisted);
|
|
225
|
+
missing.push("slice_contract.persisted=true");
|
|
226
|
+
}
|
|
227
|
+
if (contract?.current_version !== true) {
|
|
228
|
+
signals.push(BLOCKING_SIGNALS.contractNotCurrent);
|
|
229
|
+
missing.push("slice_contract.current_version=true");
|
|
230
|
+
}
|
|
231
|
+
if (state?.stale === true || (Array.isArray(state?.stale_inputs) && state.stale_inputs.length > 0)) {
|
|
232
|
+
signals.push(BLOCKING_SIGNALS.stale);
|
|
233
|
+
missing.push("ticket formalization inputs are current");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return signals.length === 0 ? allowedResult(evidenceRefs) : blockedResult(signals, missing, evidenceRefs);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Validate the complete implementation entry seam after ready-for-agent promotion.
|
|
241
|
+
*/
|
|
242
|
+
export function validateImplementationEntry(state, options = {}) {
|
|
243
|
+
const ticketResult = validateTicketFormalization(state, options);
|
|
244
|
+
if (ticketResult.result === "blocked") return ticketResult;
|
|
245
|
+
if (state?.predecessor_work_unit !== TICKET_DECOMPOSITION_WORK_UNIT) {
|
|
246
|
+
return blockedResult([BLOCKING_SIGNALS.invalidPredecessor], ["predecessor_work_unit=work-unit.ticket-decomposition"], ticketResult.evidence_refs);
|
|
247
|
+
}
|
|
248
|
+
if (state?.ready_for_agent !== true) {
|
|
249
|
+
return blockedResult([BLOCKING_SIGNALS.wrongRole], ["ready_for_agent=true"], ticketResult.evidence_refs);
|
|
250
|
+
}
|
|
251
|
+
return ticketResult;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const lifecycleTransitionContract = Object.freeze({
|
|
255
|
+
implementation_work_unit: IMPLEMENTATION_WORK_UNIT,
|
|
256
|
+
ticket_decomposition_work_unit: TICKET_DECOMPOSITION_WORK_UNIT,
|
|
257
|
+
next_routes: NEXT_ROUTES,
|
|
258
|
+
blocking_signals: BLOCKING_SIGNALS,
|
|
259
|
+
});
|
|
@@ -3,12 +3,16 @@ import path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
6
|
+
import { lifecycleTransitionContract, validateImplementationEntry, validateNextRoute } from "./lifecycle-transition.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
8
9
|
const read = (relative) => readFileSync(path.join(root, relative), "utf8");
|
|
9
10
|
function ensure(condition, message) { if (!condition) throw new TypeError(message); }
|
|
10
11
|
function exists(relative) { return existsSync(path.join(root, relative)); }
|
|
11
12
|
function includesAll(actual, expected) { return Array.isArray(actual) && expected.every((item) => actual.includes(item)); }
|
|
13
|
+
function hasText(value) { return typeof value === "string" && value.trim().length > 0; }
|
|
14
|
+
const virtualTicketDecompositionRef = "docs/.scratch/demo/evidence/ticket-decomposition-result.yaml";
|
|
15
|
+
const virtualTicketDecomposition = "result_schema: workflow-execution-result-v1\nwork_unit: work-unit.ticket-decomposition\nresult: completed\nevidence_refs:\n - docs/.scratch/demo/evidence/ticket-decomposition-result.yaml\n";
|
|
12
16
|
|
|
13
17
|
function validateMattContract(data) {
|
|
14
18
|
const direct = data.entry_routing?.direct_matt_entry;
|
|
@@ -84,13 +88,52 @@ function validateWorkflowExecutionResult(payload, contract, workUnitRoutes) {
|
|
|
84
88
|
for (const field of contract.workflow_reference.required) ensure(typeof payload.workflow_reference?.[field] === "string" && payload.workflow_reference[field].trim(), `workflow_reference.${field} 无效`);
|
|
85
89
|
const workUnit = workUnitRoutes?.[payload.work_unit];
|
|
86
90
|
ensure(workUnit, `未知 Workflow Execution Result work_unit: ${payload.work_unit}`);
|
|
91
|
+
if (payload.result === "completed") {
|
|
92
|
+
const routeResult = validateNextRoute(payload.work_unit, payload.next_route);
|
|
93
|
+
ensure(routeResult.result === "allowed", `Workflow Execution Result next_route 非法: ${routeResult.blocking_signals.join(", ")}`);
|
|
94
|
+
}
|
|
87
95
|
const accepted = [workUnit.native, workUnit.compatibility].filter(Boolean);
|
|
88
96
|
ensure(contract.workflow_reference.allowed_sources.includes(payload.workflow_reference.source) && accepted.some((route) => route.source === payload.workflow_reference.source && route.skill === payload.workflow_reference.skill && route.invocation_mode === payload.workflow_reference.invocation_mode), "Workflow Execution Result workflow_reference 与 work_unit route 不匹配");
|
|
97
|
+
const workUnitRequirements = contract.work_unit_requirements?.[payload.work_unit];
|
|
98
|
+
if (workUnitRequirements && payload.result === "completed") {
|
|
99
|
+
for (const field of [...(workUnitRequirements.required ?? []), ...(workUnitRequirements.completed_requires ?? [])]) {
|
|
100
|
+
ensure(hasText(payload[field]), `Workflow Execution Result ${payload.work_unit} 缺少 ${field}`);
|
|
101
|
+
}
|
|
102
|
+
for (const field of workUnitRequirements.boolean_required ?? []) {
|
|
103
|
+
ensure(typeof payload[field] === "boolean", `Workflow Execution Result ${payload.work_unit} 缺少布尔字段 ${field}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (payload.work_unit === "work-unit.slice-implementation" && payload.result === "completed") {
|
|
107
|
+
const implementationState = {
|
|
108
|
+
tracker_kind: payload.tracker_kind,
|
|
109
|
+
predecessor_work_unit: payload.predecessor_work_unit,
|
|
110
|
+
ready_for_agent: payload.ready_for_agent,
|
|
111
|
+
ticket_decomposition_result_ref: payload.ticket_decomposition_result_ref,
|
|
112
|
+
vertical_slice_ticket_ref: payload.vertical_slice_ticket_ref,
|
|
113
|
+
vertical_slice_ticket_role: payload.vertical_slice_ticket_role,
|
|
114
|
+
vertical_slice_ticket_kind: payload.vertical_slice_ticket_kind,
|
|
115
|
+
ticket_decomposition_result: { result: payload.ticket_decomposition_result_status, evidence_refs: payload.evidence_refs },
|
|
116
|
+
vertical_slice_ticket: { ref: payload.vertical_slice_ticket_ref, role: payload.vertical_slice_ticket_role, kind: payload.vertical_slice_ticket_kind },
|
|
117
|
+
slice_contract: {
|
|
118
|
+
ticket_ref: payload.slice_contract_ticket_ref,
|
|
119
|
+
status: payload.slice_contract_status,
|
|
120
|
+
persisted: payload.slice_contract_persisted,
|
|
121
|
+
current_version: payload.slice_contract_current_version,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
const semantic = validateImplementationEntry(implementationState, {
|
|
125
|
+
// The scenario uses one explicit virtual fixture; arbitrary local refs
|
|
126
|
+
// must still pass the real readability check and cannot use a fallback.
|
|
127
|
+
exists: (ref) => exists(ref) || ref === "docs/.scratch/demo/issues/01-valid-slice.md" || ref === virtualTicketDecompositionRef,
|
|
128
|
+
read: (ref) => ref === virtualTicketDecompositionRef ? virtualTicketDecomposition : readFileSync(path.join(root, ref), "utf8"),
|
|
129
|
+
});
|
|
130
|
+
ensure(semantic.result === "allowed", `Workflow Execution Result implementation Ticket 语义非法: ${semantic.blocking_signals.join(", ")}`);
|
|
131
|
+
}
|
|
89
132
|
if (payload.result !== "completed") return;
|
|
90
133
|
for (const field of contract.completed_requires_empty) ensure(Array.isArray(payload[field]) && payload[field].length === 0, `completed 的 ${field} 必须为空`);
|
|
91
134
|
for (const field of contract.completed_requires_non_empty) ensure(Array.isArray(payload[field]) && payload[field].length > 0, `completed 的 ${field} 不能为空`);
|
|
92
135
|
if (contract.completed_requires_readable_evidence_refs) {
|
|
93
|
-
for (const reference of payload.evidence_refs) ensure(typeof reference === "string" && reference.trim() && exists(reference), `completed 证据不可读取: ${reference}`);
|
|
136
|
+
for (const reference of payload.evidence_refs) ensure(typeof reference === "string" && reference.trim() && (exists(reference) || reference === virtualTicketDecompositionRef), `completed 证据不可读取: ${reference}`);
|
|
94
137
|
}
|
|
95
138
|
if (contract.completed_requires_no_blocking_signals) ensure(Array.isArray(payload.blocking_signals) && payload.blocking_signals.length === 0, "completed 不得携带 blocking_signals");
|
|
96
139
|
}
|
|
@@ -177,6 +220,16 @@ export function runScenario(name) {
|
|
|
177
220
|
const releaseGate = registry.gates.find((gate) => gate.id === "gate.release-ready");
|
|
178
221
|
ensure(releaseGate?.requires_gates?.includes("gate.frontend-implementation-verified"), "发布就绪未依赖前端实现还原门禁");
|
|
179
222
|
const contract = parseDocument(read(".agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml"), { uniqueKeys: true }).toJS({ maxAliasCount: 0 });
|
|
223
|
+
ensure(contract.ready_for_agent?.requires_vertical_slice_ticket === true && contract.ready_for_agent?.parent_ticket_as_implementation_ref === "forbidden", "ready_for_agent 未强制垂直切片且禁止父 Ticket 实现引用");
|
|
224
|
+
ensure(includesAll(contract.ready_for_agent?.required, ["ticket_decomposition_result_ref", "vertical_slice_ticket_ref", "vertical_slice_ticket_role", "vertical_slice_ticket_kind"]), "ready_for_agent 缺少 Ticket 正式化必填字段");
|
|
225
|
+
ensure(contract.ticket_formalization?.implementation_predecessor === "work-unit.ticket-decomposition" && contract.ticket_formalization?.vertical_slice_ticket?.parent_ticket_ref_forbidden === true, "Ticket 正式化实现前置或父 Ticket 禁止规则缺失");
|
|
226
|
+
ensure(contract.transition_graph?.implementation_requires_predecessor === "work-unit.ticket-decomposition", "生命周期转换图未声明实现前置工作单元");
|
|
227
|
+
ensure(JSON.stringify(contract.transition_graph?.forbidden_shortcuts) === JSON.stringify([
|
|
228
|
+
{ from: "work-unit.spec-synthesis", to: "work-unit.slice-implementation" },
|
|
229
|
+
{ from: "work-unit.prototype-design", to: "work-unit.slice-implementation" },
|
|
230
|
+
{ from: "work-unit.technical-analysis", to: "work-unit.slice-implementation" },
|
|
231
|
+
]), "生命周期转换图缺少 Spec/原型/技术分析到实现的越级阻断");
|
|
232
|
+
ensure(lifecycleTransitionContract.next_routes["work-unit.ticket-decomposition"]?.includes("work-unit.slice-implementation"), "转换校验器未允许 Ticket 正式化后进入实现");
|
|
180
233
|
ensure(contract.release_readiness?.conditional?.ui_impact?.includes("gate.frontend-implementation-verified") && contract.frontend_implementation_plan?.acceptance?.includes("no_template_placeholders"), "发布公式或前端计划实质校验不完整");
|
|
181
234
|
const templateRejected = spawnSync("scripts/verify-frontend-implementation-evidence", ["docs/process/templates/frontend-implementation-plan-template.yaml"], { cwd: root, encoding: "utf8" });
|
|
182
235
|
ensure(templateRejected.status !== 0 && templateRejected.stderr.includes("template: false"), "前端实现计划占位模板可冒充正式批准证据");
|
|
@@ -200,6 +253,46 @@ export function runScenario(name) {
|
|
|
200
253
|
blocking_signals: []
|
|
201
254
|
};
|
|
202
255
|
validateWorkflowExecutionResult(validResult, data.workflow_execution_result, data.work_unit_routes);
|
|
256
|
+
const validTicketResult = {
|
|
257
|
+
...validResult,
|
|
258
|
+
work_unit: "work-unit.ticket-decomposition",
|
|
259
|
+
next_route: "work-unit.slice-implementation",
|
|
260
|
+
ticket_decomposition_result_ref: "docs/.scratch/demo/evidence/ticket-decomposition-result.yaml",
|
|
261
|
+
vertical_slice_ticket_ref: "docs/.scratch/demo/issues/01-valid-slice.md",
|
|
262
|
+
};
|
|
263
|
+
validateWorkflowExecutionResult(validTicketResult, data.workflow_execution_result, data.work_unit_routes);
|
|
264
|
+
const validImplementationResult = {
|
|
265
|
+
...validResult,
|
|
266
|
+
evidence_refs: [virtualTicketDecompositionRef, "docs/process/lifecycle-registry.yaml"],
|
|
267
|
+
work_unit: "work-unit.slice-implementation",
|
|
268
|
+
next_route: "work-unit.code-review",
|
|
269
|
+
predecessor_work_unit: "work-unit.ticket-decomposition",
|
|
270
|
+
ready_for_agent: true,
|
|
271
|
+
ticket_decomposition_result_ref: "docs/.scratch/demo/evidence/ticket-decomposition-result.yaml",
|
|
272
|
+
ticket_decomposition_result_status: "completed",
|
|
273
|
+
vertical_slice_ticket_ref: "docs/.scratch/demo/issues/01-valid-slice.md",
|
|
274
|
+
vertical_slice_ticket_role: "ready-for-agent",
|
|
275
|
+
vertical_slice_ticket_kind: "vertical-slice-ticket",
|
|
276
|
+
slice_contract_ticket_ref: "docs/.scratch/demo/issues/01-valid-slice.md",
|
|
277
|
+
slice_contract_status: "approved",
|
|
278
|
+
slice_contract_persisted: true,
|
|
279
|
+
slice_contract_current_version: true,
|
|
280
|
+
};
|
|
281
|
+
validateWorkflowExecutionResult(validImplementationResult, data.workflow_execution_result, data.work_unit_routes);
|
|
282
|
+
for (const mutate of [
|
|
283
|
+
(item) => { item.vertical_slice_ticket_role = "ready-for-human"; },
|
|
284
|
+
(item) => { item.vertical_slice_ticket_kind = "parent-ticket"; },
|
|
285
|
+
(item) => { item.vertical_slice_ticket_ref = "docs/.scratch/demo/parent-ticket.md"; },
|
|
286
|
+
(item) => { item.slice_contract_ticket_ref = "docs/.scratch/demo/issues/02-other.md"; },
|
|
287
|
+
(item) => { item.predecessor_work_unit = "work-unit.spec-synthesis"; },
|
|
288
|
+
(item) => { item.ticket_decomposition_result_status = "blocked"; },
|
|
289
|
+
(item) => { item.ticket_decomposition_result_status = "needs-human"; },
|
|
290
|
+
]) {
|
|
291
|
+
const invalid = structuredClone(validImplementationResult); mutate(invalid);
|
|
292
|
+
let rejected = false;
|
|
293
|
+
try { validateWorkflowExecutionResult(invalid, data.workflow_execution_result, data.work_unit_routes); } catch { rejected = true; }
|
|
294
|
+
ensure(rejected, "实现 Workflow Execution Result 的 Ticket 语义变异未被拒绝");
|
|
295
|
+
}
|
|
203
296
|
const unavailableResult = structuredClone(validResult);
|
|
204
297
|
unavailableResult.result = "blocked";
|
|
205
298
|
unavailableResult.unavailable_skill = { skill: "yss-ui", provider: "codex", fallback: "manual-review", resolution: "needs-human" };
|
|
@@ -221,7 +314,8 @@ export function runScenario(name) {
|
|
|
221
314
|
(item) => { item.new_impacts = ["new-api"]; },
|
|
222
315
|
(item) => { item.workflow_reference.source = "untrusted/source"; },
|
|
223
316
|
(item) => { item.workflow_reference.skill = "implement"; },
|
|
224
|
-
(item) => { item.workflow_reference.invocation_mode = "reference"; }
|
|
317
|
+
(item) => { item.workflow_reference.invocation_mode = "reference"; },
|
|
318
|
+
(item) => { item.next_route = "work-unit.slice-implementation"; }
|
|
225
319
|
]) {
|
|
226
320
|
const invalid = structuredClone(validResult); mutate(invalid);
|
|
227
321
|
let rejected = false;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseDocument } from "../vendor/yaml.mjs";
|
|
5
|
+
import { loadDigitalHumanRoles, taskPackageDefaults } from "./digital-human-roles.mjs";
|
|
6
|
+
import { loadRegistry, ROOT } from "./lifecycle-registry.mjs";
|
|
7
|
+
import { loadMaintenanceCheckpoint, validateMaintenanceCheckpoint } from "./maintenance-intensity.mjs";
|
|
8
|
+
import { validateNextRoute } from "./lifecycle-transition.mjs";
|
|
9
|
+
|
|
10
|
+
export const TASK_PACKAGE_SCHEMA = path.join(ROOT, "docs/process/schemas/digital-human-task-package.schema.json");
|
|
11
|
+
export const LEGACY_TASK_PACKAGE_SCHEMA = path.join(ROOT, "docs/process/schemas/subagent-task-package.schema.json");
|
|
12
|
+
export const TASK_PACKAGE_REGISTRY_REF = "docs/agents/digital-human-roles.yaml";
|
|
13
|
+
export const CONTRACT_KINDS = new Set(["lifecycle-work-unit", "slice-implementation", "template-maintenance"]);
|
|
14
|
+
export const EXECUTION_STATES = new Set(["Explorer", "Drafter", "Worker", "Reviewer", "Verifier"]);
|
|
15
|
+
export const WORKFLOW_STATUSES = new Set(["not-started", "active", "paused", "resolved", "failed"]);
|
|
16
|
+
|
|
17
|
+
function fail(message) { throw new TypeError(message); }
|
|
18
|
+
function requireString(value, field) { if (typeof value !== "string" || !value.trim()) fail(`${field} 不能为空`); }
|
|
19
|
+
function equalArrays(actual, expected) { return Array.isArray(actual) && actual.length === expected.length && actual.every((value, index) => value === expected[index]); }
|
|
20
|
+
|
|
21
|
+
function parseYaml(source, label) {
|
|
22
|
+
const document = parseDocument(source, { maxAliasCount: 0, uniqueKeys: true });
|
|
23
|
+
if (document.errors.length > 0) fail(`无法解析${label}: ${document.errors[0].message}`);
|
|
24
|
+
return document.toJS({ maxAliasCount: 0 });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertSafeRelativePath(value, field) {
|
|
28
|
+
requireString(value, field);
|
|
29
|
+
if (path.isAbsolute(value)) fail(`${field} 必须是仓库相对路径`);
|
|
30
|
+
const resolved = path.resolve(ROOT, value);
|
|
31
|
+
const relative = path.relative(ROOT, resolved);
|
|
32
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`)) fail(`${field} 不得越出仓库根目录`);
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assertReadableEvidenceRef(value, field) {
|
|
37
|
+
requireString(value, field);
|
|
38
|
+
if (/^https?:\/\//.test(value)) return;
|
|
39
|
+
const evidencePath = assertSafeRelativePath(value, field);
|
|
40
|
+
if (!existsSync(evidencePath)) fail(`${field} 不可读: ${value}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function loadTaskPackage(filePath) {
|
|
44
|
+
const sourcePath = path.resolve(filePath);
|
|
45
|
+
if (!existsSync(sourcePath)) fail(`任务包不存在: ${filePath}`);
|
|
46
|
+
const value = parseYaml(readFileSync(sourcePath, "utf8"), "任务包");
|
|
47
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail("任务包必须是对象");
|
|
48
|
+
return { value, sourcePath };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function validateTaskPackageSchema(value, schemaPath = TASK_PACKAGE_SCHEMA) {
|
|
52
|
+
const effectiveSchema = path.resolve(schemaPath) === path.resolve(LEGACY_TASK_PACKAGE_SCHEMA) ? TASK_PACKAGE_SCHEMA : schemaPath;
|
|
53
|
+
if (!existsSync(effectiveSchema)) fail(`缺少任务包 schema: ${effectiveSchema}`);
|
|
54
|
+
const validator = String.raw`
|
|
55
|
+
import json
|
|
56
|
+
import sys
|
|
57
|
+
from jsonschema import Draft202012Validator
|
|
58
|
+
schema = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
59
|
+
value = json.load(sys.stdin)
|
|
60
|
+
errors = sorted(Draft202012Validator(schema).iter_errors(value), key=lambda error: list(error.absolute_path))
|
|
61
|
+
for error in errors:
|
|
62
|
+
location = ".".join(str(part) for part in error.absolute_path) or "<root>"
|
|
63
|
+
print(f"{location}: {error.message}", file=sys.stderr)
|
|
64
|
+
sys.exit(1 if errors else 0)
|
|
65
|
+
`;
|
|
66
|
+
const result = spawnSync("python3", ["-c", validator, effectiveSchema], { cwd: ROOT, encoding: "utf8", input: JSON.stringify(value) });
|
|
67
|
+
if (result.status !== 0) fail(`${result.stdout}${result.stderr}`.trim());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateSkillSource(value, registry) {
|
|
71
|
+
const defaults = taskPackageDefaults(value.role_id, registry);
|
|
72
|
+
if (value.skill_source.registry_ref !== TASK_PACKAGE_REGISTRY_REF) fail(`skill_source.registry_ref 必须为 ${TASK_PACKAGE_REGISTRY_REF}`);
|
|
73
|
+
if (value.skill_source.defaults_ref !== `taskPackageDefaults(${value.role_id})`) fail(`skill_source.defaults_ref 必须为 taskPackageDefaults(${value.role_id})`);
|
|
74
|
+
if (!equalArrays(value.skill_source.core_skills, defaults.core_skills)) fail("skill_source.core_skills 必须与角色注册表完全一致");
|
|
75
|
+
if (!equalArrays(value.skill_source.forbidden_skills, defaults.forbidden_skills)) fail("skill_source.forbidden_skills 必须与角色注册表完全一致");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateCommon(value, registry, lifecycle) {
|
|
79
|
+
const workUnit = lifecycle.work_units.find((item) => item.id === value.work_unit_id);
|
|
80
|
+
if (!workUnit && value.contract.kind !== "slice-implementation") fail(`未知 work_unit_id: ${value.work_unit_id}`);
|
|
81
|
+
const roleDefaults = taskPackageDefaults(value.role_id, registry);
|
|
82
|
+
const runtimeIds = new Set((registry.runtimes || []).map((runtime) => runtime.id));
|
|
83
|
+
if (!runtimeIds.has(value.runtime_id)) fail(`未知 runtime_id: ${value.runtime_id}`);
|
|
84
|
+
if (!EXECUTION_STATES.has(value.execution_state)) fail(`execution_state 无效: ${value.execution_state}`);
|
|
85
|
+
if (!WORKFLOW_STATUSES.has(value.workflow_status)) fail(`workflow_status 无效: ${value.workflow_status}`);
|
|
86
|
+
if (value.stage_id) {
|
|
87
|
+
if (!lifecycle.stages.some((stage) => stage.id === value.stage_id)) fail(`未知 stage_id: ${value.stage_id}`);
|
|
88
|
+
if (!roleDefaults.stages.includes(value.stage_id)) fail(`role_id 未覆盖 stage_id: ${value.role_id} -> ${value.stage_id}`);
|
|
89
|
+
}
|
|
90
|
+
validateSkillSource(value, registry);
|
|
91
|
+
for (const allowed of value.allowed_write_paths) assertSafeRelativePath(allowed, "allowed_write_paths 条目");
|
|
92
|
+
if (value.execution_state === "Reviewer" && value.review_context.implementation_actor_id === value.actor_id) fail("Reviewer 必须与实现者使用不同 actor_id");
|
|
93
|
+
const commands = new Set(value.verification_commands);
|
|
94
|
+
for (const result of value.verification_results) {
|
|
95
|
+
if (!commands.has(result.command)) fail(`verification_results 命令未声明: ${result.command}`);
|
|
96
|
+
assertReadableEvidenceRef(result.evidence_ref, "verification_results.evidence_ref");
|
|
97
|
+
}
|
|
98
|
+
const completed = value.workflow_status === "resolved" || value.result?.result === "completed";
|
|
99
|
+
if (completed) {
|
|
100
|
+
if (!value.result || value.result.result !== "completed") fail("resolved/完成任务必须返回 result=completed 的 Workflow Execution Result");
|
|
101
|
+
if (value.workflow_status !== "resolved") fail("result=completed 的任务包必须处于 resolved 状态");
|
|
102
|
+
for (const field of ["result_schema", "work_unit", "workflow_reference", "skill", "changed_files", "evidence_refs", "deferred_seams", "drift", "violation", "new_impacts", "stale_candidates", "next_route", "blocking_signals"]) {
|
|
103
|
+
if (value.result[field] === undefined) fail(`result=completed 时 ${field} 不能为空`);
|
|
104
|
+
}
|
|
105
|
+
if (value.result.result_schema !== "workflow-execution-result-v1") fail("result_schema 必须为 workflow-execution-result-v1");
|
|
106
|
+
if (value.result.work_unit !== value.work_unit_id) fail("Workflow Execution Result.work_unit 必须与任务包 work_unit_id 一致");
|
|
107
|
+
const routeResult = validateNextRoute(value.result.work_unit, value.result.next_route);
|
|
108
|
+
if (routeResult.result !== "allowed") fail(`Workflow Execution Result next_route 非法: ${routeResult.blocking_signals.join(", ")}`);
|
|
109
|
+
value.expected_evidence_files.forEach((ref) => assertReadableEvidenceRef(ref, "expected_evidence_files"));
|
|
110
|
+
if (value.verification_results.length === 0) fail("已完成任务必须包含 verification_results");
|
|
111
|
+
if (value.verification_results.some((result) => result.exit_code !== 0)) fail("已完成任务的验证命令必须全部成功");
|
|
112
|
+
const resultCommands = new Set(value.verification_results.map((result) => result.command));
|
|
113
|
+
if (value.verification_commands.some((command) => !resultCommands.has(command))) fail("已完成任务必须覆盖全部 verification_commands");
|
|
114
|
+
}
|
|
115
|
+
if (value.result) {
|
|
116
|
+
for (const field of ["new_impacts", "stale_candidates", "blocking_signals", "drift", "violation"]) {
|
|
117
|
+
if (value.result.result === "completed" && Array.isArray(value.result[field]) && value.result[field].length > 0) fail(`result=completed 时 ${field} 必须为空`);
|
|
118
|
+
}
|
|
119
|
+
if (value.result.result === "completed") {
|
|
120
|
+
if (!Array.isArray(value.result.evidence_refs) || value.result.evidence_refs.length === 0) fail("result=completed 时 evidence_refs 不能为空");
|
|
121
|
+
value.result.evidence_refs.forEach((ref) => assertReadableEvidenceRef(ref, "result.evidence_refs"));
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(value.result.changed_files)) {
|
|
124
|
+
for (const changed of value.result.changed_files) {
|
|
125
|
+
const changedPath = assertSafeRelativePath(changed, "changed_files 条目");
|
|
126
|
+
if (!value.allowed_write_paths.some((allowed) => {
|
|
127
|
+
const allowedPath = assertSafeRelativePath(allowed, "allowed_write_paths 条目");
|
|
128
|
+
const relative = path.relative(allowedPath, changedPath);
|
|
129
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`));
|
|
130
|
+
})) fail(`changed_files 超出 allowed_write_paths: ${changed}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return workUnit;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function validateContract(value, registry, lifecycle) {
|
|
138
|
+
const contract = value.contract;
|
|
139
|
+
const implementationWorkUnit = "work-unit.slice-implementation";
|
|
140
|
+
const workUnit = lifecycle.work_units.find((item) => item.id === value.work_unit_id);
|
|
141
|
+
if (!CONTRACT_KINDS.has(contract.kind)) fail(`未知 contract.kind: ${contract.kind}`);
|
|
142
|
+
if (!new Set(["issued", "stale", "blocked"]).has(contract.status)) fail(`contract.status 无效: ${contract.status}`);
|
|
143
|
+
if (contract.status === "stale" && value.workflow_status !== "paused") fail("stale 任务包必须暂停,待主控重新路由");
|
|
144
|
+
assertReadableEvidenceRef(contract.contract_ref, "contract.contract_ref");
|
|
145
|
+
if (contract.kind === "lifecycle-work-unit") {
|
|
146
|
+
if (value.work_unit_id === implementationWorkUnit || value.convergence.parent_work_unit === implementationWorkUnit) {
|
|
147
|
+
fail("lifecycle-work-unit 不得冒充垂直切片实现任务;实现任务必须使用 slice-implementation");
|
|
148
|
+
}
|
|
149
|
+
if (workUnit?.scope === "template-source") fail("template-source 工作单元必须使用 template-maintenance");
|
|
150
|
+
if (contract.slice_contract_ref || contract.maintenance_ref) fail("lifecycle-work-unit 不得携带专项合同引用");
|
|
151
|
+
assertReadableEvidenceRef(contract.lifecycle_ref, "contract.lifecycle_ref");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (contract.kind === "template-maintenance") {
|
|
155
|
+
if (contract.slice_contract_ref || contract.lifecycle_ref) fail("template-maintenance 不得携带其他合同引用");
|
|
156
|
+
const checkpointPath = assertSafeRelativePath(contract.maintenance_ref, "contract.maintenance_ref");
|
|
157
|
+
if (!existsSync(checkpointPath)) fail(`维护 checkpoint 不存在: ${contract.maintenance_ref}`);
|
|
158
|
+
validateMaintenanceCheckpoint(loadMaintenanceCheckpoint(contract.maintenance_ref));
|
|
159
|
+
if (lifecycle.work_units.find((item) => item.id === value.work_unit_id)?.scope !== "template-source") fail("template-maintenance 必须绑定 template-source work unit");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (contract.lifecycle_ref || contract.maintenance_ref) fail("slice-implementation 不得携带其他合同引用");
|
|
163
|
+
if (!/^stage\.vertical-slice-implementation$/.test(value.stage_id)) fail("slice-implementation 必须绑定垂直切片实现阶段");
|
|
164
|
+
if (value.convergence.parent_work_unit !== implementationWorkUnit) fail("slice-implementation 必须汇合到 work-unit.slice-implementation");
|
|
165
|
+
if (/^https?:\/\//.test(contract.slice_contract_ref)) fail("slice_contract_ref 必须引用本地已持久化的 Slice Implementation Contract");
|
|
166
|
+
const contractPath = assertSafeRelativePath(contract.slice_contract_ref, "contract.slice_contract_ref");
|
|
167
|
+
if (!existsSync(contractPath)) fail(`Slice Implementation Contract 不存在: ${contract.slice_contract_ref}`);
|
|
168
|
+
const document = parseYaml(readFileSync(contractPath, "utf8"), "Slice Implementation Contract");
|
|
169
|
+
const slice = document.slice_contract || document;
|
|
170
|
+
if (slice.schema_version !== 1 || typeof slice.slice_id !== "string" || !slice.lifecycle_refs || !slice.readiness || !Array.isArray(slice.work_units)) fail("Slice Implementation Contract 缺少 schema_version/slice_id/lifecycle_refs/readiness/work_units");
|
|
171
|
+
if (slice.contract_id !== contract.contract_id || slice.contract_version !== contract.contract_version || slice.status !== "approved") fail("Slice Contract 必须是当前 approved 版本");
|
|
172
|
+
const sliceUnit = slice.work_units.find((item) => item && item.id === value.work_unit_id);
|
|
173
|
+
if (!sliceUnit) fail(`Slice Implementation Contract 缺少 work_unit_id: ${value.work_unit_id}`);
|
|
174
|
+
for (const field of ["role_id", "runtime_id", "task_package_ref", "contract_id", "contract_version", "allowed_write_paths"]) if (sliceUnit[field] === undefined) fail(`Slice Contract work_unit 缺少 ${field}`);
|
|
175
|
+
if (sliceUnit.role_id !== value.role_id || sliceUnit.runtime_id !== value.runtime_id || sliceUnit.contract_id !== contract.contract_id || sliceUnit.contract_version !== contract.contract_version || sliceUnit.task_package_ref !== value.work_unit_id) fail("任务包与 Slice Contract work_unit 不一致");
|
|
176
|
+
for (const allowed of value.allowed_write_paths) if (!sliceUnit.allowed_write_paths.includes(allowed)) fail(`allowed_write_paths 未获 Slice Contract 授权: ${allowed}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function validateTaskPackage(value, { rolesDoc, lifecycleDoc } = {}) {
|
|
180
|
+
validateTaskPackageSchema(value);
|
|
181
|
+
const registry = rolesDoc || loadDigitalHumanRoles();
|
|
182
|
+
const lifecycle = lifecycleDoc || loadRegistry();
|
|
183
|
+
validateCommon(value, registry, lifecycle);
|
|
184
|
+
validateContract(value, registry, lifecycle);
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function generateTaskPackageDefaults(roleId, overrides = {}, { rolesDoc } = {}) {
|
|
189
|
+
const defaults = taskPackageDefaults(roleId, rolesDoc || loadDigitalHumanRoles());
|
|
190
|
+
return {
|
|
191
|
+
schema_version: 1,
|
|
192
|
+
role_id: defaults.role_id,
|
|
193
|
+
skill_source: {
|
|
194
|
+
registry_ref: TASK_PACKAGE_REGISTRY_REF,
|
|
195
|
+
defaults_ref: `taskPackageDefaults(${roleId})`,
|
|
196
|
+
core_skills: defaults.core_skills,
|
|
197
|
+
forbidden_skills: defaults.forbidden_skills
|
|
198
|
+
},
|
|
199
|
+
...overrides
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function validateTaskPackageSet(packages, options = {}) {
|
|
204
|
+
if (!Array.isArray(packages) || packages.length === 0) fail("任务包集合不能为空");
|
|
205
|
+
packages.forEach((pkg) => validateTaskPackage(pkg, options));
|
|
206
|
+
const slicePackages = packages.filter((pkg) => pkg.contract.kind === "slice-implementation");
|
|
207
|
+
if (slicePackages.length > 0) {
|
|
208
|
+
const contracts = new Set(slicePackages.map((pkg) => `${pkg.contract.contract_id}@${pkg.contract.contract_version}`));
|
|
209
|
+
if (contracts.size !== 1) fail("同一切片的任务包必须消费同一 contract_id/contract_version");
|
|
210
|
+
const workers = new Set(slicePackages.filter((pkg) => pkg.execution_state === "Worker").map((pkg) => pkg.actor_id));
|
|
211
|
+
slicePackages.filter((pkg) => pkg.execution_state === "Reviewer").forEach((pkg) => { if (workers.has(pkg.actor_id)) fail("Reviewer 不得与实现者使用同一 actor_id"); });
|
|
212
|
+
}
|
|
213
|
+
return packages;
|
|
214
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { loadTaskPackage, validateTaskPackage } from "./lib/task-package.mjs";
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const { positionals } = parseArgs({ allowPositionals: true, strict: true });
|
|
7
|
+
if (positionals.length !== 1) throw new TypeError("用法: scripts/verify-digital-human-task-package <task-package.yaml|task-package.json>");
|
|
8
|
+
const { value } = loadTaskPackage(positionals[0]);
|
|
9
|
+
validateTaskPackage(value);
|
|
10
|
+
process.stdout.write("数字人任务包验证通过\n");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
process.stderr.write(`数字人任务包验证失败: ${error.message}\n`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const result = spawnSync(process.execPath, [path.join(root, "scripts/verify-subagent-task-package-scenarios"), ...process.argv.slice(2)], { stdio: "inherit" });
|
|
8
|
+
process.exitCode = result.status ?? 1;
|