create-yss-spec 3.4.9 → 3.5.0
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 +2 -2
- package/package.json +1 -1
- package/src/api/_sync-service.js +111 -18
- package/src/cli/args.js +1 -0
- package/src/cli/help.js +10 -1
- package/src/cli/index.js +3 -0
- package/src/cli/prompts.js +5 -1
- package/src/cli/router.js +2 -0
- package/src/commands/attach.js +12 -2
- package/src/commands/init.js +2 -0
- package/src/commands/skills.js +253 -0
- package/src/commands/sync.js +3 -0
- package/src/family-identity.js +1 -1
- package/src/template/asset-runtime.js +220 -0
- package/src/template/attach-planner.js +5 -0
- package/src/template/distribution-runtime.js +165 -0
- package/src/template/instance-runtime.js +76 -29
- package/src/template/ownership-policy.js +17 -1
- package/src/template/prune-planner.js +6 -1
- package/src/template/sync-planner.js +8 -0
- package/src/template/verification-runtime.js +56 -1
- package/src/validation/metadata.js +24 -1
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-cache/SKILL.md +6 -5
- package/template/.agents/skills/yss-cache/references/annotations.md +3 -1
- package/template/.agents/skills/yss-cache/references/architecture.md +4 -2
- package/template/.agents/skills/yss-cache/references/configuration.md +6 -3
- package/template/.agents/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.agents/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.agents/skills/yss-cache/references/verification.md +10 -4
- package/template/.agents/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.agents/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.agents/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.agents/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.agents/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.agents/skills/yss-repository/SKILL.md +1 -0
- package/template/.agents/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.agents/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.agents/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.agents/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.codex/skills/yss-cache/SKILL.md +6 -5
- package/template/.codex/skills/yss-cache/references/annotations.md +3 -1
- package/template/.codex/skills/yss-cache/references/architecture.md +4 -2
- package/template/.codex/skills/yss-cache/references/configuration.md +6 -3
- package/template/.codex/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.codex/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.codex/skills/yss-cache/references/verification.md +10 -4
- package/template/.codex/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.codex/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.codex/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.codex/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.codex/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.codex/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.codex/skills/yss-repository/SKILL.md +1 -0
- package/template/.codex/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.codex/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.codex/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.codex/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.cursor/skills/yss-cache/SKILL.md +6 -5
- package/template/.cursor/skills/yss-cache/references/annotations.md +3 -1
- package/template/.cursor/skills/yss-cache/references/architecture.md +4 -2
- package/template/.cursor/skills/yss-cache/references/configuration.md +6 -3
- package/template/.cursor/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.cursor/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.cursor/skills/yss-cache/references/verification.md +10 -4
- package/template/.cursor/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.cursor/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.cursor/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.cursor/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.cursor/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.cursor/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.cursor/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.cursor/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.cursor/skills/yss-repository/SKILL.md +1 -0
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.cursor/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.cursor/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.cursor/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.pi/skills/yss-cache/SKILL.md +6 -5
- package/template/.pi/skills/yss-cache/references/annotations.md +3 -1
- package/template/.pi/skills/yss-cache/references/architecture.md +4 -2
- package/template/.pi/skills/yss-cache/references/configuration.md +6 -3
- package/template/.pi/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.pi/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.pi/skills/yss-cache/references/verification.md +10 -4
- package/template/.pi/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.pi/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.pi/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.pi/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.pi/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +4 -2
- package/template/.pi/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.pi/skills/yss-repository/SKILL.md +1 -0
- package/template/.pi/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.pi/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.pi/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.pi/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/AGENTS.md +2 -0
- package/template/CONTEXT.md +2 -1
- package/template/README.md +3 -5
- package/template/docs/agents/issue-tracker.md +7 -0
- package/template/docs/agents/skill-migrations.md +4 -0
- package/template/docs/agents/yss-skill-registry.yaml +7 -6
- package/template/docs/design/README.md +0 -1
- package/template/docs/design/design.md +2 -2
- package/template/docs/design/templates/interaction-spec-template.md +1 -1
- package/template/docs/engineering/evidence/aliyun-artifact-resolution.json +446 -0
- package/template/docs/process/contract-reading.md +46 -0
- package/template/docs/process/lifecycle-artifact-map.md +2 -2
- package/template/docs/process/lifecycle-registry-baseline.json +2 -1
- package/template/docs/process/lifecycle-registry.yaml +10 -4
- package/template/docs/process/schemas/api-contract-decision-v2.schema.json +198 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +259 -0
- package/template/docs/process/schemas/stage-tracking.schema.json +259 -0
- package/template/docs/process/stage-tracking.md +62 -0
- package/template/docs/process/strategic-handoff-package.md +8 -0
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +2 -0
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +1 -0
- package/template/docs/user-guide//351/230/266/346/256/265/346/216/245/345/205/245/344/270/216/344/272/247/347/211/251/345/244/215/347/224/250/344/275/277/347/224/250/346/211/213/345/206/214.md +243 -0
- package/template/scripts/api-contract-decision +16 -0
- package/template/scripts/complete-backend-delivery +17 -0
- package/template/scripts/contract +13 -0
- package/template/scripts/design-md +2 -0
- package/template/scripts/dispatch-slice-task +13 -0
- package/template/scripts/lib/api-contract-decision.mjs +45 -4
- package/template/scripts/lib/approval-record.mjs +1 -1
- package/template/scripts/lib/approved-execution-context.mjs +35 -7
- package/template/scripts/lib/backend-architecture.mjs +2 -2
- package/template/scripts/lib/backend-delivery-terminal.mjs +51 -0
- package/template/scripts/lib/backend-platform-provenance.mjs +1 -1
- package/template/scripts/lib/backend-platform.mjs +1 -1
- package/template/scripts/lib/backend-scaffold-prerequisites.mjs +1 -1
- package/template/scripts/lib/context-contract.mjs +1 -1
- package/template/scripts/lib/context-reconciliation.mjs +1 -1
- package/template/scripts/lib/contract-source.mjs +22 -0
- package/template/scripts/lib/contract-views.mjs +109 -0
- package/template/scripts/lib/delivery-preflight.mjs +3 -2
- package/template/scripts/lib/design-md.mjs +230 -0
- package/template/scripts/lib/digital-human-roles.mjs +1 -1
- package/template/scripts/lib/existing-backend-architecture.mjs +4 -3
- package/template/scripts/lib/existing-ui-baseline.mjs +4 -2
- package/template/scripts/lib/frontend-delivery-boundary.mjs +1 -1
- package/template/scripts/lib/frontend-delivery.mjs +1 -1
- package/template/scripts/lib/harness-execution-scope.mjs +15 -1
- package/template/scripts/lib/implementation-contract-compiler.mjs +4 -4
- package/template/scripts/lib/json-schema.mjs +19 -4
- package/template/scripts/lib/lifecycle-context-query.mjs +5 -1
- package/template/scripts/lib/lifecycle-controls.mjs +1 -1
- package/template/scripts/lib/lifecycle-execution-scope.mjs +92 -0
- package/template/scripts/lib/lifecycle-registry.mjs +2 -1
- package/template/scripts/lib/lifecycle-transition.mjs +21 -3
- package/template/scripts/lib/openapi-draft-validation.mjs +2 -1
- package/template/scripts/lib/skill-registry.mjs +10 -3
- package/template/scripts/lib/skill-supply-chain.mjs +33 -14
- package/template/scripts/lib/slice-contract-preparation.mjs +16 -14
- package/template/scripts/lib/slice-contract-views.mjs +1 -1
- package/template/scripts/lib/slice-contract.mjs +18 -3
- package/template/scripts/lib/slice-execution-preflight.mjs +7 -3
- package/template/scripts/lib/slice-repositories.mjs +1 -1
- package/template/scripts/lib/slice-task-package.mjs +3 -2
- package/template/scripts/lib/stage-tracking-migration.mjs +176 -0
- package/template/scripts/lib/stage-tracking.mjs +184 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -4
- package/template/scripts/lib/strategic-handoff-io.mjs +5 -2
- package/template/scripts/lib/strategic-handoff-routing.mjs +31 -0
- package/template/scripts/lib/strategic-handoff.mjs +4 -2
- package/template/scripts/lib/task-package.mjs +2 -0
- package/template/scripts/lib/technical-design-boundary.mjs +4 -2
- package/template/scripts/lib/ui-baseline.mjs +1 -1
- package/template/scripts/lib/user-decision.mjs +1 -1
- package/template/scripts/lib/validation-phase.mjs +72 -0
- package/template/scripts/stage-tracking +25 -0
- package/template/scripts/strategic-consumer-entry +47 -0
- package/template/scripts/verify-context-reconciliation +4 -3
- package/template/scripts/verify-lifecycle-checkpoint +10 -0
- package/template/scripts/verify-project-instance +11 -10
- package/template/skills-lock.json +17 -32
- package/template.manifest.json +18 -3
- package/template.snapshot.json +5 -5
- package/template/.agents/skills/grill-me/SKILL.md +0 -7
- package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.agents/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.agents/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.agents/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.codex/skills/grill-me/SKILL.md +0 -7
- package/template/.codex/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.codex/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.codex/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.codex/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.cursor/skills/grill-me/SKILL.md +0 -7
- package/template/.cursor/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.cursor/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.cursor/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.cursor/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.pi/skills/grill-me/SKILL.md +0 -7
- package/template/.pi/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.pi/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.pi/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.pi/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/docs/agents/yss-plugin-dependency-contract.md +0 -45
- package/template/docs/api/.gitkeep +0 -0
- package/template/docs/api/specs/.gitkeep +0 -0
- package/template/docs/architecture/.gitkeep +0 -0
- package/template/docs/design/facts/antdv-next/1.5.2/cli-help.txt +0 -38
- package/template/docs/design/facts/antdv-next/1.5.2/component-list.json +0 -434
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/info.json +0 -246
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/semantic.json +0 -40
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/token.json +0 -32
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/info.json +0 -190
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/semantic.json +0 -20
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/token.json +0 -256
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/info.json +0 -460
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/token.json +0 -123
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/info.json +0 -646
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/semantic.json +0 -50
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/token.json +0 -130
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/info.json +0 -640
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/token.json +0 -172
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/info.json +0 -1010
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/token.json +0 -263
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/info.json +0 -321
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/semantic.json +0 -25
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/token.json +0 -25
- package/template/docs/design/facts/antdv-next/1.5.2/design-md.json +0 -3
- package/template/docs/design/facts/antdv-next/1.5.2/manifest.json +0 -215
- package/template/docs/design/facts/antdv-next/1.5.2/resolution-probe.json +0 -5
- package/template/docs/design/prototypes/.gitkeep +0 -1
- package/template/docs/implementation/.gitkeep +0 -1
- package/template/docs/plan/IDEATION.md +0 -96
- package/template/docs/process/PDCA-SCRUM.md +0 -10
- package/template/docs/process/harness-executive-blueprint.md +0 -10
- package/template/docs/releases/.gitkeep +0 -1
- package/template/docs/requirements/README.md +0 -85
- package/template/docs/requirements/tickets/.gitkeep +0 -1
- package/template/docs/templates/agent-brief-template.md +0 -46
- package/template/docs/templates/architecture-proposal-template.md +0 -18
- package/template/docs/templates/implementation-plan-template.md +0 -16
- package/template/docs/templates/implementation-routing-template.md +0 -350
- package/template/docs/templates/requirement-freeze-template.md +0 -63
- package/template/docs/templates/risk-register-template.md +0 -11
- package/template/docs/templates/user-story-template.md +0 -20
- package/template/docs/testing/README.md +0 -110
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { parseDocument } from "../vendor/yaml.mjs";
|
|
8
|
+
|
|
9
|
+
const repositoryRoot = path.resolve(new URL("../..", import.meta.url).pathname);
|
|
10
|
+
const designPath = path.join(repositoryRoot, "DESIGN.md");
|
|
11
|
+
const projectionDir = path.join(repositoryRoot, "docs/design/tokens");
|
|
12
|
+
const manifestPath = path.join(projectionDir, ".design-md-projection.json");
|
|
13
|
+
const syncMetadataPath = path.join(repositoryRoot, "docs/design/design-system-sync.yaml");
|
|
14
|
+
const expectedSections = ["Overview", "Colors", "Typography", "Layout", "Elevation & Depth", "Shapes", "Components", "Do's and Don'ts"];
|
|
15
|
+
const requiredFrontmatter = ["version", "name", "description", "colors", "typography", "rounded", "spacing", "components"];
|
|
16
|
+
const componentProperties = new Set(["backgroundColor", "textColor", "typography", "rounded", "padding", "size", "height", "width"]);
|
|
17
|
+
|
|
18
|
+
function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
|
|
19
|
+
function fail(message) { throw new Error(message); }
|
|
20
|
+
function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
|
|
21
|
+
|
|
22
|
+
function resolveValue(value, frontmatter) {
|
|
23
|
+
const match = typeof value === "string" ? value.match(/^\{(colors|typography|rounded|spacing)\.([\w-]+)\}$/) : null;
|
|
24
|
+
return match ? frontmatter[match[1]]?.[match[2]] : value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function projectedCssVariables(frontmatter) {
|
|
28
|
+
const components = frontmatter.components;
|
|
29
|
+
return {
|
|
30
|
+
"yss-color-primary-control": resolveValue(components["button-primary"].backgroundColor, frontmatter),
|
|
31
|
+
"yss-color-primary-control-hover": resolveValue(components["button-primary-hover"].backgroundColor, frontmatter),
|
|
32
|
+
"yss-color-on-primary": resolveValue(components["button-primary"].textColor, frontmatter),
|
|
33
|
+
"yss-control-height": resolveValue(components["button-primary"].height, frontmatter),
|
|
34
|
+
"yss-card-padding": resolveValue(components["card-default"].padding, frontmatter),
|
|
35
|
+
"yss-control-height-compact": resolveValue(components["button-compact"].height, frontmatter),
|
|
36
|
+
"yss-card-compact-padding": resolveValue(components["card-compact"].padding, frontmatter)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeCssProjection(frontmatter) {
|
|
41
|
+
const cssPath = path.join(projectionDir, "variables.css");
|
|
42
|
+
let source = readFileSync(cssPath, "utf8");
|
|
43
|
+
for (const [name, value] of Object.entries(projectedCssVariables(frontmatter))) {
|
|
44
|
+
const declaration = ` --${name}: ${value};`;
|
|
45
|
+
const pattern = new RegExp(`^\\s*--${escapeRegExp(name)}:.*;$`, "m");
|
|
46
|
+
source = pattern.test(source) ? source.replace(pattern, declaration) : source.replace(":root {\n", `:root {\n${declaration}\n`);
|
|
47
|
+
}
|
|
48
|
+
writeFileSync(cssPath, source);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateCssProjection(frontmatter) {
|
|
52
|
+
const source = readFileSync(path.join(projectionDir, "variables.css"), "utf8");
|
|
53
|
+
for (const [name, value] of Object.entries(projectedCssVariables(frontmatter))) {
|
|
54
|
+
if (!new RegExp(`^\\s*--${escapeRegExp(name)}: ${escapeRegExp(String(value))};$`, "m").test(source)) fail(`variables.css 的 --${name} 与 DESIGN.md 不一致`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readDesign(file = designPath) {
|
|
59
|
+
const source = readFileSync(file, "utf8");
|
|
60
|
+
if (!source.startsWith("---\n")) fail(`${path.relative(repositoryRoot, file)} 缺少 YAML frontmatter`);
|
|
61
|
+
const end = source.indexOf("\n---", 4);
|
|
62
|
+
if (end < 0) fail(`${path.relative(repositoryRoot, file)} frontmatter 未闭合`);
|
|
63
|
+
const document = parseDocument(source.slice(4, end), { uniqueKeys: true, maxAliasCount: 0 });
|
|
64
|
+
if (document.errors.length) fail(document.errors[0].message);
|
|
65
|
+
const frontmatter = document.toJS({ maxAliasCount: 0 });
|
|
66
|
+
for (const field of requiredFrontmatter) if (!frontmatter?.[field]) fail(`frontmatter 缺少 ${field}`);
|
|
67
|
+
const body = source.slice(end + 4);
|
|
68
|
+
const headings = [...body.matchAll(/^## ([^\n]+)$/gm)].map((match) => match[1].trim());
|
|
69
|
+
const actual = headings.slice(0, expectedSections.length);
|
|
70
|
+
if (JSON.stringify(actual) !== JSON.stringify(expectedSections)) fail(`canonical H2 章节顺序必须为: ${expectedSections.join(" → ")}`);
|
|
71
|
+
for (const section of expectedSections) {
|
|
72
|
+
if (headings.filter((heading) => heading === section).length !== 1) fail(`canonical H2 章节必须且只能出现一次: ${section}`);
|
|
73
|
+
}
|
|
74
|
+
const canonicalH1 = [...body.matchAll(/^# ([^\n]+)$/gm)].map((match) => match[1].trim()).filter((heading) => expectedSections.includes(heading));
|
|
75
|
+
if (canonicalH1.length > 0) fail(`canonical 正文章节必须使用 H2,不能使用 H1: ${canonicalH1.join(", ")}`);
|
|
76
|
+
for (const [name, component] of Object.entries(frontmatter.components)) {
|
|
77
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) fail(`组件变体名称非法: ${name}`);
|
|
78
|
+
for (const key of Object.keys(component)) if (!componentProperties.has(key)) fail(`组件 ${name} 使用不支持的属性: ${key}`);
|
|
79
|
+
}
|
|
80
|
+
const references = [...source.matchAll(/\{(colors|typography|rounded|spacing)\.([\w-]+)\}/g)].map((match) => match[0]);
|
|
81
|
+
for (const reference of references) {
|
|
82
|
+
const [, group, token] = reference.match(/^\{([^}]+)\.([^}]+)\}$/);
|
|
83
|
+
if (frontmatter[group]?.[token] === undefined) fail(`悬空 token 引用: ${reference}`);
|
|
84
|
+
}
|
|
85
|
+
if (file === designPath && existsSync(syncMetadataPath)) {
|
|
86
|
+
const syncDocument = parseDocument(readFileSync(syncMetadataPath, "utf8"), { uniqueKeys: true, maxAliasCount: 0 });
|
|
87
|
+
if (syncDocument.errors.length) fail(syncDocument.errors[0].message);
|
|
88
|
+
const sync = syncDocument.toJS({ maxAliasCount: 0 })?.design_system_sync;
|
|
89
|
+
if (!sync?.baseline_sha256 || sync.baseline_sha256 !== sha256(source)) {
|
|
90
|
+
fail("design-system-sync.yaml 的 baseline_sha256 与 DESIGN.md 不一致,请更新跨仓同步摘要");
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(sync.synchronized_sections) || sync.synchronized_sections.length === 0) {
|
|
93
|
+
fail("design-system-sync.yaml 缺少 synchronized_sections");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { source, frontmatter };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function runUpstream(args) {
|
|
100
|
+
const result = spawnSync("npx", ["--yes", "@google/design.md@0.4.0", ...args], { cwd: repositoryRoot, encoding: "utf8" });
|
|
101
|
+
if (result.error || result.status !== 0) fail(result.stderr?.trim() || result.stdout?.trim() || "design.md CLI 执行失败");
|
|
102
|
+
return result.stdout;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function projectionFiles() {
|
|
106
|
+
return ["theme.json", "tokens.default.json", "tokens.dark.json", "tokens.compact.json", "variables.css", "variables.dark.css"].map((file) => path.join(projectionDir, file));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeProjectionManifest() {
|
|
110
|
+
const { source } = readDesign();
|
|
111
|
+
const files = Object.fromEntries(projectionFiles().map((file) => [path.relative(repositoryRoot, file), sha256(readFileSync(file))]));
|
|
112
|
+
writeFileSync(manifestPath, `${JSON.stringify({ schema_version: 1, source: "DESIGN.md", source_sha256: sha256(source), files }, null, 2)}\n`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function writeThemeProjection(frontmatter) {
|
|
116
|
+
const themeFile = path.join(projectionDir, "theme.json");
|
|
117
|
+
const theme = { token: {}, algorithm: "default" };
|
|
118
|
+
const token = theme.token || (theme.token = {});
|
|
119
|
+
const colors = frontmatter.colors;
|
|
120
|
+
const typography = frontmatter.typography;
|
|
121
|
+
const rounded = frontmatter.rounded;
|
|
122
|
+
const spacing = frontmatter.spacing;
|
|
123
|
+
Object.assign(token, {
|
|
124
|
+
colorPrimary: colors.primary,
|
|
125
|
+
colorSuccess: colors.success,
|
|
126
|
+
colorWarning: colors.warning,
|
|
127
|
+
colorError: colors.error,
|
|
128
|
+
colorInfo: colors.info ?? colors.primary,
|
|
129
|
+
colorTextBase: "#000000",
|
|
130
|
+
colorBgBase: colors.surface ?? "#ffffff",
|
|
131
|
+
colorBgLayout: colors["canvas-layout"] ?? "#f0f2f5",
|
|
132
|
+
colorText: colors.text,
|
|
133
|
+
colorTextSecondary: colors["text-secondary"],
|
|
134
|
+
colorBorder: colors.border ?? "#d9d9d9",
|
|
135
|
+
fontFamily: typography.body.fontFamily,
|
|
136
|
+
fontSize: Number.parseInt(typography.body.fontSize, 10),
|
|
137
|
+
borderRadius: Number.parseInt(rounded.md, 10),
|
|
138
|
+
sizeUnit: Number.parseInt(spacing.xxs, 10),
|
|
139
|
+
sizeStep: Number.parseInt(spacing.xxs, 10),
|
|
140
|
+
controlHeight: Number.parseInt(frontmatter.components["button-primary"].height, 10),
|
|
141
|
+
controlHeightSM: Number.parseInt(frontmatter.components["button-small"].height, 10),
|
|
142
|
+
controlHeightLG: Number.parseInt(frontmatter.components["button-large"].height, 10),
|
|
143
|
+
fontSizeSM: Number.parseInt(typography.caption.fontSize, 10),
|
|
144
|
+
borderRadiusSM: Number.parseInt(rounded.sm, 10),
|
|
145
|
+
borderRadiusLG: Number.parseInt(rounded.lg, 10),
|
|
146
|
+
padding: Number.parseInt(spacing.md, 10),
|
|
147
|
+
paddingSM: Number.parseInt(spacing.sm, 10),
|
|
148
|
+
paddingXS: Number.parseInt(spacing.xs, 10),
|
|
149
|
+
paddingLG: Number.parseInt(spacing.card, 10)
|
|
150
|
+
});
|
|
151
|
+
theme.components = { Card: { borderRadiusLG: token.borderRadiusLG, paddingLG: token.paddingLG } };
|
|
152
|
+
writeFileSync(themeFile, `${JSON.stringify(theme, null, 2)}\n`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function writeAlgorithmProjections(toolchain) {
|
|
156
|
+
if (!toolchain) fail("export --write 需要 --antd-toolchain <独立作者工具目录>,固定 antd 6.6.4");
|
|
157
|
+
const require = createRequire(path.join(path.resolve(toolchain), "package.json"));
|
|
158
|
+
if (require("antd/package.json").version !== "6.6.4") fail("主题算法需要 antd 6.6.4");
|
|
159
|
+
const { theme } = require("antd");
|
|
160
|
+
const config = JSON.parse(readFileSync(path.join(projectionDir, "theme.json")));
|
|
161
|
+
const kebab = key => key.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
162
|
+
const snapshots = {};
|
|
163
|
+
for (const [mode, algorithm] of Object.entries({ default: theme.defaultAlgorithm, dark: theme.darkAlgorithm, compact: theme.compactAlgorithm })) {
|
|
164
|
+
const token = { ...config.token };
|
|
165
|
+
// Let the dark algorithm derive neutral colors instead of pinning light aliases.
|
|
166
|
+
if (mode === "dark") for (const key of ["colorBgBase", "colorTextBase", "colorBgLayout", "colorText", "colorTextSecondary", "colorBorder"]) delete token[key];
|
|
167
|
+
snapshots[mode] = theme.getDesignToken({ token, algorithm });
|
|
168
|
+
writeFileSync(path.join(projectionDir, `tokens.${mode}.json`), JSON.stringify(snapshots[mode], null, 2) + "\n");
|
|
169
|
+
}
|
|
170
|
+
const rewrite = (css, token) => {
|
|
171
|
+
const values = Object.fromEntries(Object.entries(token).map(([key,value]) => [kebab(key),value]));
|
|
172
|
+
return css.replace(/(--brand-([\w-]+):\s*)([^;]+);/g, (all, prefix, key, old) => {
|
|
173
|
+
const value = values[key];
|
|
174
|
+
if (value === undefined || typeof value === "object") return all;
|
|
175
|
+
const unit = typeof value === "number" ? old.trim().match(/(?:px|ms|s)$/)?.[0] || "" : "";
|
|
176
|
+
return `${prefix}${value}${unit};`;
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
const cssPath = path.join(projectionDir, "variables.css");
|
|
180
|
+
const css = readFileSync(cssPath, "utf8");
|
|
181
|
+
const darkStart = css.indexOf('[data-theme="dark"]');
|
|
182
|
+
writeFileSync(cssPath, darkStart < 0 ? rewrite(css, snapshots.default) : rewrite(css.slice(0, darkStart), snapshots.default) + rewrite(css.slice(darkStart), snapshots.dark));
|
|
183
|
+
const darkPath = path.join(projectionDir, "variables.dark.css");
|
|
184
|
+
writeFileSync(darkPath, rewrite(readFileSync(darkPath, "utf8"), snapshots.dark));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function driftCheck() {
|
|
188
|
+
const { source, frontmatter } = readDesign();
|
|
189
|
+
validateCssProjection(frontmatter);
|
|
190
|
+
if (!existsSync(manifestPath)) fail(`缺少 ${path.relative(repositoryRoot, manifestPath)},请先执行 export --write-manifest`);
|
|
191
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
192
|
+
if (manifest.source_sha256 !== sha256(source)) fail("DESIGN.md 已变化但投影未重新生成(source_sha256 漂移)");
|
|
193
|
+
for (const [relative, digest] of Object.entries(manifest.files || {})) {
|
|
194
|
+
const file = path.join(repositoryRoot, relative);
|
|
195
|
+
if (!existsSync(file) || sha256(readFileSync(file)) !== digest) fail(`派生文件漂移: ${relative}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function main() {
|
|
200
|
+
const [command, ...args] = process.argv.slice(2);
|
|
201
|
+
if (command === "lint") {
|
|
202
|
+
const file = args.find((arg) => !arg.startsWith("-")) || designPath;
|
|
203
|
+
readDesign(path.resolve(repositoryRoot, file));
|
|
204
|
+
process.stdout.write(`${runUpstream(["lint", file, "--format", "json"])}`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (command === "diff") {
|
|
208
|
+
if (args.length < 2) fail("用法: design-md diff <before> <after>");
|
|
209
|
+
process.stdout.write(runUpstream(["diff", ...args.slice(0, 2), "--format", "json"]));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (command === "export") {
|
|
213
|
+
const format = args[0] || "dtcg";
|
|
214
|
+
const { frontmatter } = readDesign();
|
|
215
|
+
process.stdout.write(runUpstream(["export", "DESIGN.md", "--format", format]));
|
|
216
|
+
if (args.includes("--write")) {
|
|
217
|
+
const toolchain = args[args.indexOf("--antd-toolchain") + 1];
|
|
218
|
+
if (!args.includes("--antd-toolchain") || !toolchain) fail("缺少 --antd-toolchain");
|
|
219
|
+
writeThemeProjection(frontmatter);
|
|
220
|
+
writeAlgorithmProjections(toolchain);
|
|
221
|
+
writeCssProjection(frontmatter);
|
|
222
|
+
}
|
|
223
|
+
if (args.includes("--write-manifest")) writeProjectionManifest();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (command === "drift") { driftCheck(); process.stdout.write("DESIGN.md 投影无漂移\n"); return; }
|
|
227
|
+
fail("用法: design-md lint|diff|export|drift");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try { main(); } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {assertApprovedExecutionContext} from './approved-execution-context.mjs';
|
|
2
2
|
import {createHash} from 'node:crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
4
|
+
import { readFileSync, lstatSync, readdirSync } from './validation-phase.mjs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import {execFileSync} from 'node:child_process';
|
|
6
7
|
import {parseDocument} from '../vendor/yaml.mjs';
|
|
@@ -35,7 +36,7 @@ function inspectMaven(root,units){
|
|
|
35
36
|
const script=`import json,sys,xml.etree.ElementTree as ET\nout=[]\nfor p in json.load(sys.stdin):\n r=ET.parse(p).getroot(); ns={'m':'http://maven.apache.org/POM/4.0.0'}; q=lambda s:r.find(s,ns)\n aid=q('m:artifactId'); aid=aid if aid is not None else q('artifactId')\n deps=[e.text.strip() for e in r.findall('m:dependencies/m:dependency/m:artifactId',ns)+r.findall('m:profiles/m:profile/m:dependencies/m:dependency/m:artifactId',ns)+r.findall('dependencies/dependency/artifactId') if e.text]\n modules=[e.text.strip() for e in r.findall('.//m:modules/m:module',ns)+r.findall('.//modules/module') if e.text]\n out.append({'artifact_id':aid.text.strip() if aid is not None else '', 'dependencies':deps, 'modules':modules})\nprint(json.dumps(out))`;
|
|
36
37
|
try{return JSON.parse(execFileSync('python3',['-c',script],{input:JSON.stringify(units.map(u=>file(root,u.pom_ref))),encoding:'utf8',timeout:15000,maxBuffer:2*1024*1024}));}catch(error){requireThat(false,'ARCH_BUILD_INVALID',`无法读取实际 Maven 构建单元: ${error.message}`);}
|
|
37
38
|
}
|
|
38
|
-
export function verifyExistingArchitecture(identity,bindings,{root,registry,execution}={}){
|
|
39
|
+
export function verifyExistingArchitecture(identity,bindings,{root,registry,execution,readOnly=false}={}){
|
|
39
40
|
const profile=validateExistingArchitecture(identity,registry);
|
|
40
41
|
requireThat(text(root),'ARCH_EVIDENCE_REF','需要治理 root');
|
|
41
42
|
const baseline=bound(root,bindings?.engineering_baseline),registration=bound(root,bindings?.repository_registration),manifest=bound(root,bindings?.manifest);
|
|
@@ -132,7 +133,7 @@ export function verifyExistingArchitecture(identity,bindings,{root,registry,exec
|
|
|
132
133
|
}
|
|
133
134
|
if(changes.length){
|
|
134
135
|
requireThat(execution,'ARCH_SOURCE_STALE',`固定源码改变: ${changes.join(', ')}`);
|
|
135
|
-
const permission=assertApprovedExecutionContext(execution,{root,architectureIdentity:identity,architectureEvidence:bindings});
|
|
136
|
+
const permission=assertApprovedExecutionContext(execution,{root,architectureIdentity:identity,architectureEvidence:bindings,readOnly});
|
|
136
137
|
const allowed=permission.allowed_write_paths.map(x=>{if(path.isAbsolute(x)){const rel=path.relative(projectRoot,x).split(path.sep).join('/');return rel==='.'||rel===''?'.':relative(rel);}return x==='.'?'.':relative(x);});
|
|
137
138
|
requireThat(changes.every(ref=>allowed.some(scope=>beneath(ref,scope))&®istration.allowed_write_paths.some(scope=>beneath(ref,scope))),'ARCH_SOURCE_OUT_OF_SCOPE','输出增量超出批准写范围');
|
|
138
139
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, withValidationPhase, inValidationPhase } from './validation-phase.mjs';
|
|
2
3
|
import { ensure, hash, digest, read, safe, files, schema, treeDigest } from './strategic-handoff-io.mjs';
|
|
3
4
|
|
|
4
5
|
// This is an observation bundle, not a prototype or an approval generator.
|
|
5
|
-
export function validateExistingUiBaseline(data,
|
|
6
|
+
export function validateExistingUiBaseline(data,options={}) {return inValidationPhase()?validate(data,options):withValidationPhase({root:options.bundleRoot,purpose:'existing-ui-baseline',readOnly:true},()=>validate(data,options));}
|
|
7
|
+
function validate(data, { bundleRoot } = {}) {
|
|
6
8
|
const errors=[];
|
|
7
9
|
try {
|
|
8
10
|
schema(data,'docs/process/schemas/existing-ui-baseline.schema.json');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
|
|
2
2
|
import { loadDeliveryProfile } from './harness-execution-scope.mjs';
|
|
3
|
-
import { existsSync } from '
|
|
3
|
+
import { existsSync } from './validation-phase.mjs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import { ROOT, read, safe, ensure, digest } from './strategic-handoff-io.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { uiBaselineRef, uiBaselineCaseIds, uiBaselineKind, hasConsumerRoutes } from './ui-baseline.mjs';
|
|
2
|
-
import { existsSync, readdirSync, readFileSync } from '
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from './validation-phase.mjs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { openBackendDelivery } from './backend-delivery.mjs';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadExecutionScope, assertScopeWorkUnit } from './lifecycle-execution-scope.mjs';
|
|
2
|
+
import { existsSync } from './validation-phase.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { ROOT, read, safe, ensure } from './strategic-handoff-io.mjs';
|
|
4
5
|
|
|
@@ -30,6 +31,12 @@ function activeProfile(root) {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export function enforceHarnessTaskScope(task,{root=ROOT}={}) {
|
|
34
|
+
const scope=loadExecutionScope(root);
|
|
35
|
+
if(scope) {
|
|
36
|
+
assertScopeWorkUnit(task.contract?.kind==='slice-implementation'?'work-unit.slice-implementation':task.work_unit_id,{root});
|
|
37
|
+
ensure(task.role_id!=='role.frontend-engineer','后端职责不允许派发前端实现');
|
|
38
|
+
ensure(task.allowed_write_paths.every(ref=>!/^apps\/frontend(?:\/|$)/.test(ref)),'禁止写入前端工程');
|
|
39
|
+
}
|
|
33
40
|
const profile=activeProfile(root);
|
|
34
41
|
if (!profile) return;
|
|
35
42
|
ensure(task.contract?.kind!=='template-maintenance','专职产品项目不得派发模板维护任务');
|
|
@@ -41,6 +48,13 @@ export function enforceHarnessTaskScope(task,{root=ROOT}={}) {
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export function enforceHarnessSkillScope(skills,registry,{root=ROOT}={}) {
|
|
51
|
+
if(loadExecutionScope(root)) {
|
|
52
|
+
assertScopeWorkUnit('work-unit.slice-implementation',{root});
|
|
53
|
+
for(const id of skills) {
|
|
54
|
+
const impacts=registry.skills.find(skill=>skill.id===id)?.impacts||[];
|
|
55
|
+
ensure(!impacts.includes('frontend')||impacts.includes('backend')||impacts.includes('lifecycle'),`后端职责禁止编译生产前端技能: ${id}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
44
58
|
const profile=activeProfile(root);
|
|
45
59
|
if (!profile) return;
|
|
46
60
|
const side=profile.profile_id==='harness.frontend-delivery'?'frontend':'backend';
|
|
@@ -5,7 +5,7 @@ import { createApprovedExecutionContext, createApprovedRecompilationContext, ass
|
|
|
5
5
|
import { enforceTechnicalDesign } from './technical-design-boundary.mjs';
|
|
6
6
|
import { enforceHarnessSkillScope } from "./harness-execution-scope.mjs";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { readFileSync } from
|
|
8
|
+
import { readFileSync } from './validation-phase.mjs';
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
11
11
|
import { DEFAULT_REGISTRY, loadSkillRegistry, resolveSkillForNewUse } from "./skill-registry.mjs";
|
|
@@ -276,8 +276,8 @@ export function evaluateContractFreshness(contract, { registry, compilerContract
|
|
|
276
276
|
const reasons = [];
|
|
277
277
|
let trustedExecution;
|
|
278
278
|
try {
|
|
279
|
-
trustedExecution=approved_slice?createApprovedExecutionContext(approved_slice,{root,contract:all,work_unit_id}):execution;
|
|
280
|
-
if(trustedExecution)assertApprovedExecutionContext(trustedExecution,{root,contract});
|
|
279
|
+
trustedExecution=approved_slice?createApprovedExecutionContext(approved_slice,{root,contract:all,work_unit_id,readOnly}):execution;
|
|
280
|
+
if(trustedExecution)assertApprovedExecutionContext(trustedExecution,{root,contract,readOnly});
|
|
281
281
|
} catch(error) { reasons.push(error.code||'EXECUTION_APPROVAL_INVALID');trustedExecution=undefined; }
|
|
282
282
|
try { enforceTechnicalDesign({ ...resolution, slice_id: contract.slice_id ?? resolution.slice_id }, { root,execution:trustedExecution,readOnly }); }
|
|
283
283
|
catch { reasons.push("technical-design-stale-or-unavailable"); }
|
|
@@ -289,7 +289,7 @@ export function evaluateContractFreshness(contract, { registry, compilerContract
|
|
|
289
289
|
try { validateArchitectureIdentity(resolution.architecture_identity, registry); }
|
|
290
290
|
catch { reasons.push("architecture-identity-invalid"); }
|
|
291
291
|
if (resolution.architecture_identity.schema_version === 2) {
|
|
292
|
-
try { verifyArchitectureEvidence(resolution.architecture_identity, resolution.architecture_evidence, { root, registry, execution:trustedExecution }); }
|
|
292
|
+
try { verifyArchitectureEvidence(resolution.architecture_identity, resolution.architecture_evidence, { root, registry, execution:trustedExecution, readOnly }); }
|
|
293
293
|
catch (error) { reasons.push(error.code ?? "architecture-evidence-stale-or-unavailable"); }
|
|
294
294
|
}
|
|
295
295
|
if (resolution.architecture_identity_digest !== architectureDigest(resolution.architecture_identity)) reasons.push("architecture-identity-digest-changed");
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { existsSync } from
|
|
1
|
+
import { existsSync, readFileSync, validationCache } from './validation-phase.mjs';
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
const schemaCache=Symbol('schema-results');
|
|
5
6
|
const validator = String.raw`
|
|
6
7
|
import json
|
|
7
8
|
import sys
|
|
@@ -21,8 +22,7 @@ for item in jobs:
|
|
|
21
22
|
try:
|
|
22
23
|
key = (item['schemaPath'], item['formatChecker'])
|
|
23
24
|
if key not in validators:
|
|
24
|
-
|
|
25
|
-
schema = json.load(source)
|
|
25
|
+
schema = json.loads(item['schemaText'])
|
|
26
26
|
validators[key] = Draft202012Validator(schema, format_checker=FormatChecker() if key[1] else None)
|
|
27
27
|
errors = list(validators[key].iter_errors(item['value']))
|
|
28
28
|
if item['errorStyle'] == 'verbose':
|
|
@@ -45,10 +45,11 @@ export function validateJsonSchemas(items, { cwd = process.cwd(), timeoutMs = 30
|
|
|
45
45
|
const effectiveSchema = path.resolve(schemaPath);
|
|
46
46
|
if (!existsSync(effectiveSchema)) throw new TypeError(`缺少 ${label}: ${effectiveSchema}`);
|
|
47
47
|
if (!["compact", "verbose"].includes(errorStyle)) throw new TypeError(`未知 schema errorStyle: ${errorStyle}`);
|
|
48
|
-
return { value, schemaPath: effectiveSchema, formatChecker, errorStyle };
|
|
48
|
+
return { value, schemaPath: effectiveSchema, schemaText: readFileSync(effectiveSchema, 'utf8'), formatChecker, errorStyle };
|
|
49
49
|
});
|
|
50
50
|
// readall waits for EOF even after the complete JSON has arrived. A known UTF-8
|
|
51
51
|
// byte frame lets the validator finish when a transport keeps its write end open.
|
|
52
|
+
const run = jobs => {
|
|
52
53
|
const input = Buffer.from(JSON.stringify(jobs), "utf8");
|
|
53
54
|
const result = spawnSync("python3", ["-c", validator, String(input.length)], { cwd, encoding: "utf8", input, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
|
|
54
55
|
if (result.error?.code === "ETIMEDOUT") throw new TypeError(`JSON_SCHEMA_TIMEOUT: schema validation exceeded ${timeoutMs}ms`);
|
|
@@ -58,6 +59,20 @@ export function validateJsonSchemas(items, { cwd = process.cwd(), timeoutMs = 30
|
|
|
58
59
|
if (!Array.isArray(results) || results.some(item => !item || typeof item.valid !== "boolean" || typeof item.error !== "string")) throw new TypeError("JSON_SCHEMA_PROTOCOL: validator returned invalid result records");
|
|
59
60
|
if (results.length !== jobs.length) throw new TypeError("schema batch 返回数量不匹配");
|
|
60
61
|
return results;
|
|
62
|
+
};
|
|
63
|
+
// Batch identical jobs once and reuse only deterministic checks within this operation.
|
|
64
|
+
// External references retain the uncached path: their dependency closure is not local here.
|
|
65
|
+
const keys=jobs.map(job=>JSON.stringify([job.schemaPath,job.schemaText,job.value,job.formatChecker,job.errorStyle,path.resolve(cwd),timeoutMs]));
|
|
66
|
+
const pending=new Map(),cached=new Map();
|
|
67
|
+
for(let i=0;i<jobs.length;i++){
|
|
68
|
+
if(/"\$(?:ref|dynamicRef)"\s*:\s*"(?!#)/.test(jobs[i].schemaText)){pending.set(`${i}:${keys[i]}`,{job:jobs[i],indices:[i],cache:false});continue;}
|
|
69
|
+
const existing=validationCache(schemaCache,keys[i]);
|
|
70
|
+
if(existing!==undefined)cached.set(i,existing);
|
|
71
|
+
else {const item=pending.get(keys[i])||{job:jobs[i],indices:[],key:keys[i],cache:true};item.indices.push(i);pending.set(keys[i],item);}
|
|
72
|
+
}
|
|
73
|
+
const entries=[...pending.values()],computed=entries.length?run(entries.map(x=>x.job)):[];
|
|
74
|
+
entries.forEach((entry,index)=>{const result=computed[index];if(entry.cache&&!result.error.includes('Traceback (most recent call last)'))validationCache(schemaCache,entry.key,result);for(const i of entry.indices)cached.set(i,result);});
|
|
75
|
+
return jobs.map((_,i)=>structuredClone(cached.get(i)));
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
export function validateJsonSchema(value, schemaPath, options = {}) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadExecutionScope, assertScopeWorkUnit, scopedNextRoutes } from './lifecycle-execution-scope.mjs';
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { readFileSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -67,6 +68,8 @@ export function queryLifecycleContext({ mode, stageId, workUnitId, include = []
|
|
|
67
68
|
throw new TypeError("至少提供 --mode、--stage、--work-unit 或 --include 之一");
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
const scope = loadExecutionScope();
|
|
72
|
+
if (workUnitId) assertScopeWorkUnit(workUnitId, { readOnly: true });
|
|
70
73
|
const lifecycle = validateRegistry(loadRegistry());
|
|
71
74
|
const orchestration = loadYaml(ORCHESTRATION_CONTRACT_REF, "生命周期编排合同");
|
|
72
75
|
const skillRegistry = loadSkillRegistry();
|
|
@@ -91,7 +94,7 @@ export function queryLifecycleContext({ mode, stageId, workUnitId, include = []
|
|
|
91
94
|
const evidenceIds = new Set([...gates, ...checks].flatMap((gate) => gate.evidence ?? []));
|
|
92
95
|
const selected = Object.fromEntries(normalizedIncludes.map((key) => [key, orchestration[key]]));
|
|
93
96
|
const transition = workUnitId ? {
|
|
94
|
-
next: orchestration.transition_graph?.routes?.[workUnitId] ?? [],
|
|
97
|
+
next: scopedNextRoutes(workUnitId, orchestration.transition_graph?.routes?.[workUnitId] ?? []),
|
|
95
98
|
forbidden_shortcuts: (orchestration.transition_graph?.forbidden_shortcuts ?? [])
|
|
96
99
|
.filter((edge) => edge.from === workUnitId || edge.to === workUnitId),
|
|
97
100
|
} : null;
|
|
@@ -129,6 +132,7 @@ export function queryLifecycleContext({ mode, stageId, workUnitId, include = []
|
|
|
129
132
|
work_unit: workUnit,
|
|
130
133
|
},
|
|
131
134
|
execution: {
|
|
135
|
+
...(scope ? { responsibility_scope: scope } : {}),
|
|
132
136
|
mode: mode ? orchestration.modes[mode] : null,
|
|
133
137
|
selected,
|
|
134
138
|
...(loadPlan ? { plan_checks: lifecycle.stages.find(stage => stage.id === 'stage.plan').spec_entry.required_checks.map(id => ({ id, status: 'pending', evidence_refs: [] })) } : {}),
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { safe } from './strategic-handoff-io.mjs';
|
|
5
|
+
import { ROOT } from './lifecycle-registry.mjs';
|
|
6
|
+
import { parseDocument } from '../vendor/yaml.mjs';
|
|
7
|
+
|
|
8
|
+
export const SCOPE_REF = '.yss-execution-scope.yaml';
|
|
9
|
+
export const TERMINAL_REF = '.yss-backend-delivery.json';
|
|
10
|
+
const read = (root, ref) => {
|
|
11
|
+
const file = safe(root, ref);
|
|
12
|
+
const doc = parseDocument(readFileSync(file, 'utf8'), { uniqueKeys: true });
|
|
13
|
+
if (doc.errors.length) throw new TypeError(doc.errors[0].message);
|
|
14
|
+
return doc.toJS({ maxAliasCount: 0 });
|
|
15
|
+
};
|
|
16
|
+
const check = (ok, message) => { if (!ok) throw new TypeError(`execution-scope-blocked: ${message}`); };
|
|
17
|
+
|
|
18
|
+
/** The project opt-in selects a policy; callers cannot supply a replacement policy. */
|
|
19
|
+
export function loadExecutionScope(root = ROOT) {
|
|
20
|
+
const marker = path.join(root, '.yss-plugin.json');
|
|
21
|
+
const plugin = existsSync(marker) ? read(root, '.yss-plugin.json') : null;
|
|
22
|
+
const file = path.join(root, SCOPE_REF);
|
|
23
|
+
if (!existsSync(file)) {
|
|
24
|
+
check(!['yss-plan-to-backend', 'yss-backend-delivery'].includes(plugin?.plugin), '插件项目缺少职责范围;迁移后才能继续');
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const config = read(root, SCOPE_REF), identity = read(root, 'yss-project.yaml');
|
|
28
|
+
check(identity.schema_version === 1 && identity.repository_mode === 'project-instance', '职责范围仅适用于项目实例');
|
|
29
|
+
check(config.schema_version === 1 && config.scope_id === 'plan-to-backend'
|
|
30
|
+
&& Object.keys(config).every(key => ['schema_version', 'scope_id'].includes(key)), '未知或被扩大的职责配置');
|
|
31
|
+
if (plugin) check(['yss-plan-to-backend', 'yss-backend-delivery'].includes(plugin.plugin) && plugin.execution_scope === config.scope_id, '插件与项目职责绑定不一致');
|
|
32
|
+
const contract = read(root, '.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml');
|
|
33
|
+
const policy = contract.execution_scopes?.[config.scope_id];
|
|
34
|
+
check(policy?.terminal_work_unit === 'work-unit.backend-delivery' && Array.isArray(policy.allowed_work_units), '匹配版本的主控合同缺少职责策略');
|
|
35
|
+
return { ...policy, scope_id: config.scope_id };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function verifyDeliveryTerminal(root = ROOT) {
|
|
39
|
+
check(existsSync(path.join(root, TERMINAL_REF)), '缺少正式后端交付终点记录');
|
|
40
|
+
const result = spawnSync(process.execPath, [path.join(root, 'scripts/complete-backend-delivery'), 'verify', '--root', root], {
|
|
41
|
+
cwd: root, encoding: 'utf8', input: '', timeout: 120000, maxBuffer: 8 * 1024 * 1024,
|
|
42
|
+
env: { ...process.env, NODE_OPTIONS: '', NODE_PATH: '' },
|
|
43
|
+
});
|
|
44
|
+
check(!result.error && result.status === 0, result.error?.message || result.stderr || '后端交付终点复验失败');
|
|
45
|
+
return JSON.parse(result.stdout);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function assertScopeWorkUnit(workUnit, { root = ROOT, readOnly = false } = {}) {
|
|
49
|
+
const scope = loadExecutionScope(root);
|
|
50
|
+
if (!scope) { check(workUnit !== 'work-unit.backend-delivery', '后端终点需要显式职责范围'); return null; }
|
|
51
|
+
check(scope.allowed_work_units.includes(workUnit), `职责范围不允许 ${workUnit}`);
|
|
52
|
+
if (existsSync(path.join(root, TERMINAL_REF))) {
|
|
53
|
+
verifyDeliveryTerminal(root);
|
|
54
|
+
check(readOnly, '后端交付已完成;不能恢复实现或自动发布');
|
|
55
|
+
}
|
|
56
|
+
return scope;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function scopedNextRoutes(workUnit, routes, { root = ROOT } = {}) {
|
|
60
|
+
const scope = loadExecutionScope(root);
|
|
61
|
+
if (!scope) return routes.filter(id => id !== 'work-unit.backend-delivery');
|
|
62
|
+
assertScopeWorkUnit(workUnit, { root, readOnly: true });
|
|
63
|
+
if (existsSync(path.join(root, TERMINAL_REF))) return [];
|
|
64
|
+
return (scope.route_overrides[workUnit] || routes).filter(id => scope.allowed_work_units.includes(id));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function assertScopeTransition(current, next, state, { root = ROOT } = {}) {
|
|
68
|
+
const scope = loadExecutionScope(root);
|
|
69
|
+
if (!scope) { check(current !== 'work-unit.backend-delivery' && next !== 'work-unit.backend-delivery', '后端终点需要显式职责范围'); return; }
|
|
70
|
+
if (current === scope.terminal_work_unit && next === null) { verifyDeliveryTerminal(root); return; }
|
|
71
|
+
assertScopeWorkUnit(current, { root });
|
|
72
|
+
if (next) assertScopeWorkUnit(next, { root });
|
|
73
|
+
check(next !== null, '不能把中间阶段声明为后端交付完成');
|
|
74
|
+
assertScopeImpacts(state, { root });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function assertScopeImpacts(state, { root = ROOT } = {}) {
|
|
78
|
+
if (!loadExecutionScope(root)) return;
|
|
79
|
+
check(state?.delivery_impacts?.frontend !== true, '产品 UI 设计保留,生产前端实现必须交给下游');
|
|
80
|
+
if (state?.ui_impact === true) {
|
|
81
|
+
const deferred = state.downstream_frontend;
|
|
82
|
+
check(deferred && ['owner', 'ticket_ref', 'verification_plan', 'target_version'].every(key => typeof deferred[key] === 'string' && deferred[key].trim()), 'UI 产品须明确下游前端责任、Ticket、验证计划和目标版本');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function assertScopeSlice(contract, { root = ROOT } = {}) {
|
|
87
|
+
if (!loadExecutionScope(root)) return;
|
|
88
|
+
check(contract.backend?.status === 'required' && contract.frontend?.status === 'not-applicable', '本职责仅允许后端 Slice;产品 UI 设计不能混作前端实现批准');
|
|
89
|
+
check(!(contract.common?.impacted_areas || []).some(x => ['ui', 'frontend'].includes(x)), 'Slice 仍包含生产前端实现影响');
|
|
90
|
+
const roles = (contract.work_units || []).map(unit => unit.role_id);
|
|
91
|
+
check(!roles.includes('role.frontend-engineer'), '禁止派发前端实现');
|
|
92
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { readFileSync } from './validation-phase.mjs';
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assertTrackingTransition } from './stage-tracking.mjs';
|
|
2
|
+
import { assertScopeTransition, assertScopeImpacts, assertScopeWorkUnit, scopedNextRoutes } from './lifecycle-execution-scope.mjs';
|
|
1
3
|
// Wire capability marker: profile-local readiness rules are not interchangeable.
|
|
2
4
|
export const SLICE_REPOSITORY_PREPARATION_PROTOCOL = 1;
|
|
3
5
|
import { createHash } from "node:crypto";
|
|
@@ -43,7 +45,8 @@ const NEXT_ROUTES = deepFreeze({
|
|
|
43
45
|
[TICKET_DECOMPOSITION_WORK_UNIT]: [IMPLEMENTATION_WORK_UNIT],
|
|
44
46
|
[IMPLEMENTATION_WORK_UNIT]: ["work-unit.frontend-implementation-verification", "work-unit.code-review"],
|
|
45
47
|
"work-unit.frontend-implementation-verification": ["work-unit.code-review"],
|
|
46
|
-
"work-unit.code-review": ["work-unit.release-and-retrospective", IMPLEMENTATION_WORK_UNIT],
|
|
48
|
+
"work-unit.code-review": ["work-unit.release-and-retrospective", IMPLEMENTATION_WORK_UNIT, "work-unit.backend-delivery"],
|
|
49
|
+
"work-unit.backend-delivery": [],
|
|
47
50
|
"work-unit.release-and-retrospective": [],
|
|
48
51
|
});
|
|
49
52
|
|
|
@@ -249,7 +252,11 @@ function validateTicketReference(ref, trackerKind) {
|
|
|
249
252
|
* by `validateWorkflowExecutionResult` before this function is called.
|
|
250
253
|
*/
|
|
251
254
|
export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, options = {}) {
|
|
252
|
-
|
|
255
|
+
try { assertTrackingTransition(currentWorkUnit, nextRoute, decisionState, { root: options.root || ROOT }); }
|
|
256
|
+
catch (error) { return blockedResult(['stage-tracking-blocked'], [error.message]); }
|
|
257
|
+
try { assertScopeTransition(currentWorkUnit, nextRoute, decisionState, options); }
|
|
258
|
+
catch (error) { return blockedResult(['execution-scope-blocked'], [error.message]); }
|
|
259
|
+
if (currentWorkUnit === "work-unit.code-review" && ["work-unit.release-and-retrospective", "work-unit.backend-delivery"].includes(nextRoute)) {
|
|
253
260
|
try {
|
|
254
261
|
if (decisionState?.review_input?.scope_kind !== "change") throw new Error("只读基线审计不能关闭实现审查");
|
|
255
262
|
validateBackendReview(decisionState, { root: options.root || ROOT });
|
|
@@ -262,7 +269,7 @@ export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, opt
|
|
|
262
269
|
try { enforceFrontendDelivery(decisionState, { root: options.root, phase: nextRoute === IMPLEMENTATION_WORK_UNIT ? "implementation" : "inputs" }); }
|
|
263
270
|
catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
|
|
264
271
|
}
|
|
265
|
-
const routes = NEXT_ROUTES[currentWorkUnit];
|
|
272
|
+
const routes = NEXT_ROUTES[currentWorkUnit] && scopedNextRoutes(currentWorkUnit, NEXT_ROUTES[currentWorkUnit], options);
|
|
266
273
|
if (!routes) return blockedResult([BLOCKING_SIGNALS.invalidRoute], ["known_current_work_unit"]);
|
|
267
274
|
if (nextRoute === REPOSITORY_PREPARATION_WORK_UNIT && currentWorkUnit !== "work-unit.technical-analysis") {
|
|
268
275
|
return blockedResult([BLOCKING_SIGNALS.technicalAnalysisRequired], ["work-unit.technical-analysis predecessor"]);
|
|
@@ -299,6 +306,8 @@ export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, opt
|
|
|
299
306
|
* generated and verified scaffold. Unaffected roles need an explicit reason.
|
|
300
307
|
*/
|
|
301
308
|
export function validateImplementationRepositoriesReady(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8"), root = ROOT } = {}) {
|
|
309
|
+
try { assertScopeImpacts(state, { root }); }
|
|
310
|
+
catch (error) { return blockedResult(['execution-scope-blocked'], [error.message]); }
|
|
302
311
|
const preparation = state?.implementation_repository_preparation;
|
|
303
312
|
const impacts = state?.delivery_impacts;
|
|
304
313
|
const signals = [];
|
|
@@ -411,6 +420,13 @@ function validateDecisionBoundary(workUnit, state, options) {
|
|
|
411
420
|
* `exists` is injectable so external adapters can resolve their own tracker refs.
|
|
412
421
|
*/
|
|
413
422
|
export function validateTicketFormalization(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8"), ...decisionOptions } = {}) {
|
|
423
|
+
if ((state?.tracker_kind ?? "local-markdown") === "local-markdown" && state?.vertical_slice_ticket?.ref && isReadable(state.vertical_slice_ticket.ref, exists)) {
|
|
424
|
+
try {
|
|
425
|
+
const header = read(state.vertical_slice_ticket.ref).match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
426
|
+
const kind = header ? parseDocument(header[1], { uniqueKeys: true }).toJS({ maxAliasCount: 0 })?.kind : null;
|
|
427
|
+
if (kind === "stage-work-item") return blockedResult(["stage-work-item-not-implementable"], ["vertical-slice-ticket required"]);
|
|
428
|
+
} catch { return blockedResult(["ticket-content-unreadable"], ["readable vertical slice content"]); }
|
|
429
|
+
}
|
|
414
430
|
try { enforceFrontendDelivery(state, { root: decisionOptions.root, phase: "implementation" }); }
|
|
415
431
|
catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
|
|
416
432
|
const repositoryResult = validateImplementationRepositoriesReady(state, { exists, read, ...decisionOptions });
|
|
@@ -532,6 +548,8 @@ export function validateTicketFormalization(state, { exists = existsSync, read =
|
|
|
532
548
|
* Validate the complete implementation entry seam after ready-for-agent promotion.
|
|
533
549
|
*/
|
|
534
550
|
export function validateImplementationEntry(state, options = {}) {
|
|
551
|
+
try { assertScopeWorkUnit(IMPLEMENTATION_WORK_UNIT, options); assertScopeImpacts(state, options); }
|
|
552
|
+
catch (error) { return blockedResult(['execution-scope-blocked'], [error.message]); }
|
|
535
553
|
const ticketResult = validateTicketFormalization(state, options);
|
|
536
554
|
if (ticketResult.result === "blocked") return ticketResult;
|
|
537
555
|
if (state?.predecessor_work_unit !== TICKET_DECOMPOSITION_WORK_UNIT) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { existsSync, lstatSync, readFileSync } from "./validation-phase.mjs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { parseAllDocuments, parseDocument } from "../vendor/yaml.mjs";
|