create-yss-spec 2.1.9 → 2.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli.js +82 -15
- package/template/.agents/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.agents/skills/domain-modeling/SKILL.md +1 -1
- package/template/.agents/skills/grilling/SKILL.md +7 -1
- package/template/.agents/skills/triage/SKILL.md +2 -2
- package/template/.agents/skills/wait-what/SKILL.md +2 -2
- package/template/.agents/skills/wayfinder/SKILL.md +1 -1
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/.claude/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.claude/skills/domain-modeling/SKILL.md +1 -1
- package/template/.claude/skills/grilling/SKILL.md +7 -1
- package/template/.claude/skills/triage/SKILL.md +2 -2
- package/template/.claude/skills/wait-what/SKILL.md +2 -2
- package/template/.claude/skills/wayfinder/SKILL.md +1 -1
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/.codex/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.codex/skills/domain-modeling/SKILL.md +1 -1
- package/template/.codex/skills/grilling/SKILL.md +7 -1
- package/template/.codex/skills/triage/SKILL.md +2 -2
- package/template/.codex/skills/wait-what/SKILL.md +2 -2
- package/template/.codex/skills/wayfinder/SKILL.md +1 -1
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/.hermes/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.hermes/skills/domain-modeling/SKILL.md +1 -1
- package/template/.hermes/skills/grilling/SKILL.md +7 -1
- package/template/.hermes/skills/triage/SKILL.md +2 -2
- package/template/.hermes/skills/wait-what/SKILL.md +2 -2
- package/template/.hermes/skills/wayfinder/SKILL.md +1 -1
- package/template/.hermes/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/.pi/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.pi/skills/domain-modeling/SKILL.md +1 -1
- package/template/.pi/skills/grilling/SKILL.md +7 -1
- package/template/.pi/skills/triage/SKILL.md +2 -2
- package/template/.pi/skills/wait-what/SKILL.md +2 -2
- package/template/.pi/skills/wayfinder/SKILL.md +1 -1
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/.qoder/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.qoder/skills/domain-modeling/SKILL.md +1 -1
- package/template/.qoder/skills/grilling/SKILL.md +7 -1
- package/template/.qoder/skills/triage/SKILL.md +2 -2
- package/template/.qoder/skills/wait-what/SKILL.md +2 -2
- package/template/.qoder/skills/wayfinder/SKILL.md +1 -1
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/.trae/skills/diagnosing-bugs/SKILL.md +2 -4
- package/template/.trae/skills/domain-modeling/SKILL.md +1 -1
- package/template/.trae/skills/grilling/SKILL.md +7 -1
- package/template/.trae/skills/triage/SKILL.md +2 -2
- package/template/.trae/skills/wait-what/SKILL.md +2 -2
- package/template/.trae/skills/wayfinder/SKILL.md +1 -1
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +43 -91
- package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +7 -5
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +88 -22
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +5 -4
- package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +4 -1
- package/template/AGENTS.md +2 -2
- package/template/CONTEXT.md +7 -1
- package/template/docs/adr/0007-lifecycle-native-work-units.md +5 -0
- package/template/docs/agents/skills-maintenance.md +11 -3
- package/template/docs/process/MATT-POCOCK-ENGINEERING-SKILLS.md +1 -1
- package/template/docs/process/lifecycle-artifact-map.md +19 -15
- package/template/docs/process/lifecycle-registry-baseline.json +15 -1
- package/template/docs/process/lifecycle-registry.yaml +77 -0
- package/template/docs/process/schemas/frontend-implementation-evidence.schema.json +39 -0
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +56 -0
- package/template/docs/process/schemas/lifecycle-registry.schema.json +2 -2
- package/template/docs/process/templates/frontend-implementation-plan-template.yaml +16 -0
- package/template/docs/process/templates/frontend-implementation-verification-template.yaml +21 -0
- package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +30 -0
- package/template/docs/process/templates/stage-checkpoint-template.md +9 -5
- package/template/scripts/lib/lifecycle-registry.mjs +9 -4
- package/template/scripts/lib/scenario-checks.mjs +37 -15
- package/template/scripts/lib/skill-supply-chain.mjs +33 -8
- package/template/scripts/verify-frontend-implementation-evidence +51 -0
- package/template/scripts/verify-lifecycle-checkpoint +37 -0
- package/template/scripts/verify-template +5 -2
- package/template/scripts/verify-upstream-skill-source +32 -0
- package/template/skills-lock.json +105 -55
- package/template.manifest.json +12 -0
- package/template.snapshot.json +4 -4
- package/template/.agents/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.agents/skills/batch-grill-me/agents/openai.yaml +0 -5
- package/template/.claude/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.claude/skills/batch-grill-me/agents/openai.yaml +0 -5
- package/template/.codex/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.codex/skills/batch-grill-me/agents/openai.yaml +0 -5
- package/template/.hermes/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.hermes/skills/batch-grill-me/agents/openai.yaml +0 -5
- package/template/.pi/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.pi/skills/batch-grill-me/agents/openai.yaml +0 -5
- package/template/.qoder/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.qoder/skills/batch-grill-me/agents/openai.yaml +0 -5
- package/template/.trae/skills/batch-grill-me/SKILL.md +0 -15
- package/template/.trae/skills/batch-grill-me/agents/openai.yaml +0 -5
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://yss.dev/schemas/lifecycle-checkpoint.schema.json",
|
|
4
|
+
"title": "YSS 生命周期 Checkpoint",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "mode", "status", "stage", "artifacts", "gates", "next_work_unit", "ticket_sync", "verification", "human_review", "git_checkpoint", "blockers", "rollback"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 1 },
|
|
10
|
+
"mode": { "enum": ["route", "orchestrate", "resume", "audit"] },
|
|
11
|
+
"status": { "enum": ["routing", "running", "paused-human-gate", "blocked", "completed"] },
|
|
12
|
+
"stage": { "type": "string", "pattern": "^stage\\.[a-z0-9][a-z0-9-]*$" },
|
|
13
|
+
"artifacts": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"patternProperties": {
|
|
16
|
+
"^artifact\\.[a-z0-9][a-z0-9-]*$": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"required": ["status", "ref", "evidence_refs"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"status": { "enum": ["missing", "draft", "ready-for-human", "approved", "stale", "not-applicable"] },
|
|
22
|
+
"ref": { "type": ["string", "null"] },
|
|
23
|
+
"evidence_refs": { "type": "array", "items": { "type": "string" } },
|
|
24
|
+
"stale_by": { "type": "array", "items": { "type": "string" } }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"additionalProperties": false
|
|
29
|
+
},
|
|
30
|
+
"gates": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"patternProperties": {
|
|
33
|
+
"^gate\\.[a-z0-9][a-z0-9-]*$": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"required": ["status", "reason", "evidence_refs"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"status": { "enum": ["not-evaluated", "blocked", "ready-for-human", "approved", "stale", "not-applicable"] },
|
|
39
|
+
"reason": { "type": "string" },
|
|
40
|
+
"evidence_refs": { "type": "array", "items": { "type": "string" } }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"additionalProperties": false
|
|
45
|
+
},
|
|
46
|
+
"next_work_unit": { "type": ["string", "null"], "pattern": "^work-unit\\.[a-z0-9][a-z0-9-]*$" },
|
|
47
|
+
"phase_boundary": { "type": "object" },
|
|
48
|
+
"stage_trace": { "type": "object" },
|
|
49
|
+
"ticket_sync": { "type": "object" },
|
|
50
|
+
"verification": { "type": "object" },
|
|
51
|
+
"human_review": { "type": "object" },
|
|
52
|
+
"git_checkpoint": { "type": "object" },
|
|
53
|
+
"blockers": { "type": "array" },
|
|
54
|
+
"rollback": { "type": "array" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"$defs": {
|
|
29
29
|
"id": { "type": "string", "pattern": "^(stage|gate|artifact|work-unit|evidence)\\.[a-z0-9][a-z0-9-]*$" },
|
|
30
30
|
"stages": { "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": ["id", "name", "goal", "exit_criteria"], "properties": { "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1 }, "goal": { "type": "string", "minLength": 1 }, "exit_criteria": { "type": "string", "minLength": 1 } } } },
|
|
31
|
-
"objects": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "name", "stage", "trigger"], "properties": { "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1 }, "stage": { "type": "string", "pattern": "^stage\\." }, "trigger": { "type": "string", "minLength": 1 }, "evidence": { "type": "array", "minItems": 1, "items": { "type": "string", "pattern": "^evidence\\." } } } } },
|
|
32
|
-
"workUnits": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "name", "scope", "input", "output", "completion"], "properties": { "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1 }, "scope": { "
|
|
31
|
+
"objects": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "name", "stage", "trigger"], "properties": { "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1 }, "stage": { "type": "string", "pattern": "^stage\\." }, "trigger": { "type": "string", "minLength": 1 }, "evidence": { "type": "array", "minItems": 1, "items": { "type": "string", "pattern": "^evidence\\." } }, "requires_gates": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^gate\\." } } } } },
|
|
32
|
+
"workUnits": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "name", "scope", "input", "output", "completion"], "properties": { "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1 }, "scope": { "enum": ["template-source", "project-instance"] }, "input": { "type": "string", "minLength": 1 }, "output": { "type": "string", "minLength": 1 }, "completion": { "type": "string", "minLength": 1 } } } },
|
|
33
33
|
"evidence": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["id", "name", "description"], "properties": { "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1 }, "description": { "type": "string", "minLength": 1 } } } }
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
kind: plan
|
|
3
|
+
template: true
|
|
4
|
+
status: draft
|
|
5
|
+
prototype_ref: docs/.scratch/example/design/prototype.html
|
|
6
|
+
spec_ref: docs/.scratch/example/spec.md
|
|
7
|
+
route_and_page_inventory:
|
|
8
|
+
- route: /example
|
|
9
|
+
page: ExamplePage
|
|
10
|
+
visual_acceptance_cases:
|
|
11
|
+
- primary desktop flow
|
|
12
|
+
- narrow viewport flow
|
|
13
|
+
desktop_viewport: 1440x900
|
|
14
|
+
narrow_viewport: 390x844
|
|
15
|
+
state_cases: [loading, empty, error, permission, selected, key-interaction]
|
|
16
|
+
pnpm_commands: [pnpm lint, pnpm type-check, pnpm test]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
kind: verification
|
|
3
|
+
template: true
|
|
4
|
+
status: draft
|
|
5
|
+
prototype_ref: docs/.scratch/example/design/prototype.html
|
|
6
|
+
spec_ref: docs/.scratch/example/spec.md
|
|
7
|
+
route_and_page_inventory:
|
|
8
|
+
- route: /example
|
|
9
|
+
page: ExamplePage
|
|
10
|
+
desktop_visual_evidence: [docs/.scratch/example/verification/desktop.png]
|
|
11
|
+
narrow_view_visual_evidence: [docs/.scratch/example/verification/narrow.png]
|
|
12
|
+
interaction_results: [loading, empty, error, permission, selected, key-interaction]
|
|
13
|
+
console_warning_check: blocked
|
|
14
|
+
pnpm_commands: [pnpm lint, pnpm type-check, pnpm test]
|
|
15
|
+
uncovered_differences: []
|
|
16
|
+
difference_owner: null
|
|
17
|
+
independent_review:
|
|
18
|
+
reviewer: non-implementer
|
|
19
|
+
candidate_digest: pending
|
|
20
|
+
status: pending
|
|
21
|
+
findings_ref: docs/.scratch/example/verification/ui-fidelity-review.md
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
mode: route
|
|
3
|
+
status: routing
|
|
4
|
+
stage: stage.entry-triage
|
|
5
|
+
artifacts: {}
|
|
6
|
+
gates: {}
|
|
7
|
+
next_work_unit: work-unit.discovery-opportunity
|
|
8
|
+
phase_boundary:
|
|
9
|
+
decision: continue
|
|
10
|
+
reason: initial routing
|
|
11
|
+
stage_trace:
|
|
12
|
+
stage: stage.entry-triage
|
|
13
|
+
upstream_refs: []
|
|
14
|
+
artifact_refs: []
|
|
15
|
+
gate_decisions: []
|
|
16
|
+
downstream_impacts: []
|
|
17
|
+
ticket_sync:
|
|
18
|
+
status: pending
|
|
19
|
+
refs: []
|
|
20
|
+
verification:
|
|
21
|
+
commands: []
|
|
22
|
+
evidence_refs: []
|
|
23
|
+
human_review:
|
|
24
|
+
required: false
|
|
25
|
+
status: not-applicable
|
|
26
|
+
git_checkpoint:
|
|
27
|
+
required: false
|
|
28
|
+
status: not-applicable
|
|
29
|
+
blockers: []
|
|
30
|
+
rollback: []
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
```yaml
|
|
4
4
|
schema_version: 1
|
|
5
|
-
mode:
|
|
5
|
+
mode: <route|orchestrate|resume|audit>
|
|
6
|
+
status: <routing|running|paused-human-gate|blocked|completed>
|
|
6
7
|
stage: <lifecycle stage>
|
|
7
8
|
artifacts:
|
|
8
|
-
-
|
|
9
|
-
status: <draft|ready-for-human|
|
|
9
|
+
<stable-artifact-id>:
|
|
10
|
+
status: <missing|draft|ready-for-human|approved|stale|not-applicable>
|
|
11
|
+
ref: <readable artifact reference>
|
|
12
|
+
evidence_refs: []
|
|
10
13
|
gates:
|
|
11
|
-
-
|
|
12
|
-
status: <
|
|
14
|
+
<stable-gate-id>:
|
|
15
|
+
status: <not-evaluated|blocked|ready-for-human|approved|stale|not-applicable>
|
|
13
16
|
reason: <why this status applies>
|
|
14
17
|
phase_boundary:
|
|
15
18
|
decision: <continue|clear|handoff|subagent|compact>
|
|
@@ -24,6 +27,7 @@ stage_trace:
|
|
|
24
27
|
ticket_sync:
|
|
25
28
|
status: <pending|synced|not-applicable>
|
|
26
29
|
refs: []
|
|
30
|
+
next_work_unit: <stable work-unit id>
|
|
27
31
|
verification:
|
|
28
32
|
commands: []
|
|
29
33
|
evidence_refs: []
|
|
@@ -128,6 +128,11 @@ export function validateRegistry(registry, { baseline = DEFAULT_BASELINE } = {})
|
|
|
128
128
|
requireReference(ids, reference, `${gate.id}.evidence`);
|
|
129
129
|
if (ids.get(reference) !== "evidence") fail(`${gate.id}.evidence 必须引用 evidence.*`);
|
|
130
130
|
}
|
|
131
|
+
for (const reference of gate.requires_gates ?? []) {
|
|
132
|
+
requireReference(ids, reference, `${gate.id}.requires_gates`);
|
|
133
|
+
if (ids.get(reference) !== "gates") fail(`${gate.id}.requires_gates 必须引用 gate.*`);
|
|
134
|
+
if (reference === gate.id) fail(`${gate.id}.requires_gates 不得引用自身`);
|
|
135
|
+
}
|
|
131
136
|
}
|
|
132
137
|
for (const artifact of registry.artifacts) {
|
|
133
138
|
requireReference(ids, artifact.stage, `${artifact.id}.stage`);
|
|
@@ -144,8 +149,8 @@ export function renderLifecycleStructure(registry) {
|
|
|
144
149
|
"## 1. 主阶段", "", "| 稳定 ID | 阶段 | 目标 | 退出标准 |", "|---|---|---|---|"
|
|
145
150
|
];
|
|
146
151
|
for (const stage of registry.stages) lines.push(`| \`${stage.id}\` | ${stage.name} | ${stage.goal} | ${stage.exit_criteria} |`);
|
|
147
|
-
lines.push("", "## 2. 生命周期对象", "", "门禁是需要裁决的审查点;产物、工作单元和证据不是门禁的同义词。未命中条件的门禁记录 \`not-applicable\` 及原因,不生成空文档。", "", "### 2.1 条件门禁", "", "| 稳定 ID | 门禁 | 所属阶段 | 触发条件 | 必须留下的证据 |", "
|
|
148
|
-
for (const gate of registry.gates) lines.push(`| \`${gate.id}\` | ${gate.name} | \`${gate.stage}\` | ${gate.trigger} | ${gate.evidence.map((id) => `\`${id}\``).join("、")} |`);
|
|
152
|
+
lines.push("", "## 2. 生命周期对象", "", "门禁是需要裁决的审查点;产物、工作单元和证据不是门禁的同义词。未命中条件的门禁记录 \`not-applicable\` 及原因,不生成空文档。", "", "### 2.1 条件门禁", "", "| 稳定 ID | 门禁 | 所属阶段 | 触发条件 | 前置门禁 | 必须留下的证据 |", "|---|---|---|---|---|---|");
|
|
153
|
+
for (const gate of registry.gates) lines.push(`| \`${gate.id}\` | ${gate.name} | \`${gate.stage}\` | ${gate.trigger} | ${(gate.requires_gates ?? []).map((id) => `\`${id}\``).join("、") || "无"} | ${gate.evidence.map((id) => `\`${id}\``).join("、")} |`);
|
|
149
154
|
lines.push("", "### 2.2 生命周期产物", "", "| 稳定 ID | 产物 | 所属阶段 | 触发条件 |", "|---|---|---|---|");
|
|
150
155
|
for (const artifact of registry.artifacts) lines.push(`| \`${artifact.id}\` | ${artifact.name} | \`${artifact.stage}\` | ${artifact.trigger} |`);
|
|
151
156
|
lines.push("", "### 2.3 执行证据", "", "| 稳定 ID | 证据 | 说明 |", "|---|---|---|");
|
|
@@ -155,8 +160,8 @@ export function renderLifecycleStructure(registry) {
|
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
export function renderWorkUnits(registry) {
|
|
158
|
-
const lines = ["<!-- lifecycle-registry:work-units:start -->", "> 此表由 `docs/process/lifecycle-registry.yaml`
|
|
159
|
-
for (const unit of registry.work_units) lines.push(`| \`${unit.id}\` | ${unit.name} | ${unit.input} | ${unit.output} | ${unit.completion} |`);
|
|
163
|
+
const lines = ["<!-- lifecycle-registry:work-units:start -->", "> 此表由 `docs/process/lifecycle-registry.yaml` 生成;工作单元按 `scope` 区分模板维护与项目实例流程。", "", "| 稳定 ID | 范围 | 工作单元 | 输入 | 输出 | 完成条件 |", "|---|---|---|---|---|---|"];
|
|
164
|
+
for (const unit of registry.work_units) lines.push(`| \`${unit.id}\` | ${unit.scope} | ${unit.name} | ${unit.input} | ${unit.output} | ${unit.completion} |`);
|
|
160
165
|
lines.push("<!-- lifecycle-registry:work-units:end -->");
|
|
161
166
|
return `${lines.join("\n")}\n`;
|
|
162
167
|
}
|
|
@@ -32,11 +32,14 @@ function validateMattContract(data) {
|
|
|
32
32
|
|
|
33
33
|
function validateInvocationBoundary(data) {
|
|
34
34
|
const boundary = data.matt_invocation_boundary;
|
|
35
|
-
const expectedUserInvoked = ["ask-matt", "
|
|
35
|
+
const expectedUserInvoked = ["ask-matt", "grill-me", "grill-with-docs", "handoff", "implement", "improve-codebase-architecture", "loop-me", "setup-matt-pocock-skills", "setup-ts-deep-modules", "teach", "to-questionnaire", "to-spec", "to-tickets", "triage", "wait-what", "wayfinder", "writing-beats", "writing-fragments", "writing-shape"];
|
|
36
|
+
const expectedModelInvoked = ["code-review", "codebase-design", "diagnosing-bugs", "domain-modeling", "grilling", "migrate-to-shoehorn", "prototype", "research", "resolving-merge-conflicts", "scaffold-exercises", "setup-pre-commit", "tdd", "writing-for-agents"];
|
|
37
|
+
const expectedLifecycleModelInvoked = ["code-review", "codebase-design", "diagnosing-bugs", "domain-modeling", "grilling", "prototype", "research", "tdd"];
|
|
36
38
|
ensure(JSON.stringify(boundary?.user_invoked_skills) === JSON.stringify(expectedUserInvoked), "Matt user-invoked skills 清单不完整或已漂移");
|
|
37
39
|
ensure(JSON.stringify(boundary?.lifecycle_managed_user_entries) === JSON.stringify(["setup-matt-pocock-skills", "grill-with-docs", "to-spec", "to-tickets", "implement"]), "生命周期管理的显式用户入口清单不完整");
|
|
38
40
|
ensure(boundary?.lifecycle_may_invoke_user_invoked === false && boundary?.formal_artifact_owner === "explicit-user-entry", "生命周期仍可能自动调用 user-invoked skill 或产出其正式资产");
|
|
39
|
-
ensure(JSON.stringify(boundary?.model_invoked_skills) === JSON.stringify(
|
|
41
|
+
ensure(JSON.stringify(boundary?.model_invoked_skills) === JSON.stringify(expectedModelInvoked) && JSON.stringify(boundary?.lifecycle_allowed_model_invoked_skills) === JSON.stringify(expectedLifecycleModelInvoked) && boundary?.continuous_orchestration === "compatibility-prepare-and-validate-only", "Matt invocation inventory 或生命周期 model-invoked 白名单不完整");
|
|
42
|
+
ensure(JSON.stringify(data.skill_source_contract?.source_revisions_required) === JSON.stringify(["mattpocock/skills"]) && data.skill_source_contract?.adaptation_ref_required_when_effective_diff === true && data.skill_source_contract?.retired_shared_skills?.includes("batch-grill-me"), "上游来源或退役 skill 供应链契约不完整");
|
|
40
43
|
validateInvocationMetadata(boundary, (skill) => read(`.agents/skills/${skill}/SKILL.md`));
|
|
41
44
|
const setup = data.setup_readiness;
|
|
42
45
|
ensure(setup?.missing_action === "needs-human" && setup?.requested_skill === "setup-matt-pocock-skills" && setup?.resume_route === "setup-readiness" && setup?.preserves?.includes("lifecycle.status"), "readiness=missing 未形成显式用户 setup 的结构化暂停");
|
|
@@ -44,22 +47,31 @@ function validateInvocationBoundary(data) {
|
|
|
44
47
|
ensure(data.matt_skill_result?.status === "compatibility-read-only" && data.matt_skill_result?.may_influence_routing === false && data.matt_skill_result?.normalize_to === "workflow-execution-result-v1", "旧 Matt Skill Result 未限制为只读兼容 adapter");
|
|
45
48
|
ensure(result?.canonical_output_schema === "workflow-execution-result-v1" && JSON.stringify(result?.accepted_input_schemas) === JSON.stringify(["workflow-execution-result-v1"]) && result?.legacy?.["matt-skill-result-v1"]?.status === "compatibility-read-only" && result.legacy["matt-skill-result-v1"].may_influence_routing === false && result.legacy["matt-skill-result-v1"].normalize_to === "workflow-execution-result-v1", "Workflow Execution Result 未将旧 Matt Skill Result 限制为只读兼容");
|
|
46
49
|
ensure(includesAll(result?.required, ["result_schema", "work_unit", "workflow_reference", "result", "evidence_refs", "changed_artifacts", "new_impacts", "stale_candidates", "next_route", "blocking_signals"]) && includesAll(result?.result_values, ["completed", "blocked", "needs-human", "failed"]) && includesAll(result?.blocking_signals, ["drift", "new_impacts", "violation", "missing_evidence", "stale_candidates"]) && includesAll(result?.completed_requires_empty, ["new_impacts", "stale_candidates"]) && includesAll(result?.completed_requires_non_empty, ["evidence_refs"]) && result?.completed_requires_readable_evidence_refs === true && result?.evidence_ref_validation === "readable-or-resolvable" && result?.completed_requires_no_blocking_signals === true && includesAll(result?.workflow_reference?.required, ["source", "skill", "invocation_mode"]), "Workflow Execution Result 的完成态证据、阻断信号或 workflow_reference 契约不完整");
|
|
47
|
-
const
|
|
48
|
-
ensure(
|
|
49
|
-
ensure(
|
|
50
|
+
const native = data.lifecycle_native_entries;
|
|
51
|
+
ensure(native?.default_entry === "yss-product-lifecycle" && native?.formal_artifact_owner === "yss-product-lifecycle", "生命周期原生入口未持有默认正式资产所有权");
|
|
52
|
+
ensure(JSON.stringify(native?.user_confirmation_required_at) === JSON.stringify(["spec-baseline", "prototype-confirmation", "openapi-freeze", "merge-or-release"]), "生命周期人工门禁集合已漂移");
|
|
53
|
+
const routes = data.work_unit_routes;
|
|
54
|
+
ensure(routes?.["work-unit.discovery-requirements"]?.skills?.includes("grilling") && routes?.["work-unit.discovery-requirements"]?.skills?.includes("domain-modeling"), "需求分析工作单元缺少 grilling/domain-modeling");
|
|
55
|
+
ensure(routes?.["work-unit.discovery-opportunity"]?.route_by?.market_or_competitor_fact === "competitive-intelligence" && routes["work-unit.discovery-opportunity"].route_by.technical_or_standard_fact === "research", "机会调研事实路由不准确");
|
|
56
|
+
ensure(routes?.["work-unit.slice-implementation"]?.skills?.includes("tdd") && routes?.["work-unit.slice-implementation"]?.skills?.includes("yss-ui"), "原生实现工作单元缺少 TDD 或 UI 路由");
|
|
57
|
+
ensure(routes?.["work-unit.frontend-implementation-verification"]?.skills?.includes("code-review") && routes?.["work-unit.frontend-implementation-verification"]?.applies_when === "ui_impact", "前端还原验证未绑定 UI fidelity 审查轴");
|
|
50
58
|
for (const id of ["work-unit.spec-synthesis", "work-unit.ticket-decomposition", "work-unit.slice-implementation"]) {
|
|
51
|
-
ensure(
|
|
59
|
+
ensure(routes?.[id]?.native?.source === "yss-product-lifecycle" && routes[id].compatibility?.source === "mattpocock/skills" && routes[id].compatibility.formal_artifact_owner === "explicit-user-entry", `${id} 未分离原生执行定义与 Matt 兼容输入`);
|
|
60
|
+
}
|
|
61
|
+
for (const route of Object.values(routes ?? {})) {
|
|
62
|
+
for (const skill of route.skills ?? []) ensure(!boundary.user_invoked_skills.includes(skill), `工作单元不能自动调用 user-invoked skill: ${skill}`);
|
|
63
|
+
for (const skill of route.skills ?? []) if (boundary.model_invoked_skills.includes(skill)) ensure(boundary.lifecycle_allowed_model_invoked_skills.includes(skill), `工作单元调用了未进入生命周期白名单的 model-invoked skill: ${skill}`);
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
|
|
55
|
-
function validateWorkflowExecutionResult(payload, contract,
|
|
67
|
+
function validateWorkflowExecutionResult(payload, contract, workUnitRoutes) {
|
|
56
68
|
for (const field of contract.required) ensure(Object.hasOwn(payload, field), `Workflow Execution Result 缺少 ${field}`);
|
|
57
69
|
ensure(contract.result_values.includes(payload.result), "Workflow Execution Result result 无效");
|
|
58
70
|
for (const field of contract.workflow_reference.required) ensure(typeof payload.workflow_reference?.[field] === "string" && payload.workflow_reference[field].trim(), `workflow_reference.${field} 无效`);
|
|
59
|
-
const workUnit =
|
|
71
|
+
const workUnit = workUnitRoutes?.[payload.work_unit];
|
|
60
72
|
ensure(workUnit, `未知 Workflow Execution Result work_unit: ${payload.work_unit}`);
|
|
61
|
-
const
|
|
62
|
-
ensure(payload.workflow_reference.source ===
|
|
73
|
+
const accepted = [workUnit.native, workUnit.compatibility].filter(Boolean);
|
|
74
|
+
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 不匹配");
|
|
63
75
|
if (payload.result !== "completed") return;
|
|
64
76
|
for (const field of contract.completed_requires_empty) ensure(Array.isArray(payload[field]) && payload[field].length === 0, `completed 的 ${field} 必须为空`);
|
|
65
77
|
for (const field of contract.completed_requires_non_empty) ensure(Array.isArray(payload[field]) && payload[field].length > 0, `completed 的 ${field} 不能为空`);
|
|
@@ -96,7 +108,7 @@ const profiles = {
|
|
|
96
108
|
},
|
|
97
109
|
matt: {
|
|
98
110
|
message: "Matt/YSS 集成压力场景验证通过",
|
|
99
|
-
files: [".agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md", ".agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml"],
|
|
111
|
+
files: [".agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md", ".agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml", "docs/process/templates/lifecycle-checkpoint-template.yaml", "docs/process/templates/frontend-implementation-plan-template.yaml", "docs/process/templates/frontend-implementation-verification-template.yaml"],
|
|
100
112
|
markers: [[".agents/skills/yss-product-lifecycle/SKILL.md", "Workflow Execution Result"]]
|
|
101
113
|
},
|
|
102
114
|
prototype: {
|
|
@@ -146,6 +158,13 @@ export function runScenario(name) {
|
|
|
146
158
|
if (name === "lifecycle") {
|
|
147
159
|
const result = spawnSync("scripts/verify-lifecycle-registry", [], { cwd: root, encoding: "utf8" });
|
|
148
160
|
ensure(result.status === 0, result.stderr || result.stdout);
|
|
161
|
+
const registry = parseDocument(read("docs/process/lifecycle-registry.yaml"), { uniqueKeys: true }).toJS({ maxAliasCount: 0 });
|
|
162
|
+
const releaseGate = registry.gates.find((gate) => gate.id === "gate.release-ready");
|
|
163
|
+
ensure(releaseGate?.requires_gates?.includes("gate.frontend-implementation-verified"), "发布就绪未依赖前端实现还原门禁");
|
|
164
|
+
const contract = parseDocument(read(".agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml"), { uniqueKeys: true }).toJS({ maxAliasCount: 0 });
|
|
165
|
+
ensure(contract.release_readiness?.conditional?.ui_impact?.includes("gate.frontend-implementation-verified") && contract.frontend_implementation_plan?.acceptance?.includes("no_template_placeholders"), "发布公式或前端计划实质校验不完整");
|
|
166
|
+
const templateRejected = spawnSync("scripts/verify-frontend-implementation-evidence", ["docs/process/templates/frontend-implementation-plan-template.yaml"], { cwd: root, encoding: "utf8" });
|
|
167
|
+
ensure(templateRejected.status !== 0 && templateRejected.stderr.includes("template: false"), "前端实现计划占位模板可冒充正式批准证据");
|
|
149
168
|
}
|
|
150
169
|
if (name === "matt") {
|
|
151
170
|
const contract = parseDocument(read(".agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml"), { uniqueKeys: true });
|
|
@@ -156,7 +175,7 @@ export function runScenario(name) {
|
|
|
156
175
|
const validResult = {
|
|
157
176
|
result_schema: "workflow-execution-result-v1",
|
|
158
177
|
work_unit: "work-unit.spec-synthesis",
|
|
159
|
-
workflow_reference: { source: "
|
|
178
|
+
workflow_reference: { source: "yss-product-lifecycle", skill: "yss-product-lifecycle", invocation_mode: "model-invoked" },
|
|
160
179
|
result: "completed",
|
|
161
180
|
evidence_refs: ["docs/process/lifecycle-registry.yaml"],
|
|
162
181
|
changed_artifacts: [],
|
|
@@ -165,7 +184,10 @@ export function runScenario(name) {
|
|
|
165
184
|
next_route: "work-unit.ticket-decomposition",
|
|
166
185
|
blocking_signals: []
|
|
167
186
|
};
|
|
168
|
-
validateWorkflowExecutionResult(validResult, data.workflow_execution_result, data.
|
|
187
|
+
validateWorkflowExecutionResult(validResult, data.workflow_execution_result, data.work_unit_routes);
|
|
188
|
+
const compatibleResult = structuredClone(validResult);
|
|
189
|
+
compatibleResult.workflow_reference = { source: "mattpocock/skills", skill: "to-spec", invocation_mode: "reference" };
|
|
190
|
+
validateWorkflowExecutionResult(compatibleResult, data.workflow_execution_result, data.work_unit_routes);
|
|
169
191
|
for (const mutate of [
|
|
170
192
|
(item) => { delete item.workflow_reference; },
|
|
171
193
|
(item) => { item.evidence_refs = []; },
|
|
@@ -174,11 +196,11 @@ export function runScenario(name) {
|
|
|
174
196
|
(item) => { item.new_impacts = ["new-api"]; },
|
|
175
197
|
(item) => { item.workflow_reference.source = "untrusted/source"; },
|
|
176
198
|
(item) => { item.workflow_reference.skill = "implement"; },
|
|
177
|
-
(item) => { item.workflow_reference.invocation_mode = "
|
|
199
|
+
(item) => { item.workflow_reference.invocation_mode = "reference"; }
|
|
178
200
|
]) {
|
|
179
201
|
const invalid = structuredClone(validResult); mutate(invalid);
|
|
180
202
|
let rejected = false;
|
|
181
|
-
try { validateWorkflowExecutionResult(invalid, data.workflow_execution_result, data.
|
|
203
|
+
try { validateWorkflowExecutionResult(invalid, data.workflow_execution_result, data.work_unit_routes); } catch { rejected = true; }
|
|
182
204
|
ensure(rejected, "Workflow Execution Result 完成态变异未被拒绝");
|
|
183
205
|
}
|
|
184
206
|
let metadataRejected = false;
|
|
@@ -8,7 +8,7 @@ export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "
|
|
|
8
8
|
const SOURCE_ROOT = path.join(ROOT, ".agents/skills");
|
|
9
9
|
const LOCK_PATH = path.join(ROOT, "skills-lock.json");
|
|
10
10
|
const PROJECTION_ROOTS = [".claude/skills", ".codex/skills", ".hermes/skills", ".pi/skills", ".qoder/skills", ".trae/skills"];
|
|
11
|
-
const OBSOLETE = new Set(["to-" + "prd", "to-" + "issues", "design-an-interface", "qa", "request-refactor-plan", "ubiquitous-language", "edit-article", "obsidian-vault", "writing-great-skills", "code-review-process", "yss-domain-modeling", "yss-dir", "yss-duckdb", "yss-file", "yss-filerunner", "yss-db2mybatis", "yss-mail", "yss-mapper-dynamic", "yss-quality", "yss-sql-condition", "yss-sql-tpl", "yss-valuation", "yss-variable", "yss-openapi", "web-design-engineer", "web-video-presentation", "wireframe-prototype", "wizard", "git-guardrails-claude-code", "claude-handoff"]);
|
|
11
|
+
const OBSOLETE = new Set(["to-" + "prd", "to-" + "issues", "design-an-interface", "qa", "request-refactor-plan", "ubiquitous-language", "edit-article", "obsidian-vault", "writing-great-skills", "code-review-process", "yss-domain-modeling", "yss-dir", "yss-duckdb", "yss-file", "yss-filerunner", "yss-db2mybatis", "yss-mail", "yss-mapper-dynamic", "yss-quality", "yss-sql-condition", "yss-sql-tpl", "yss-valuation", "yss-variable", "yss-openapi", "web-design-engineer", "web-video-presentation", "wireframe-prototype", "wizard", "git-guardrails-claude-code", "claude-handoff", "batch-grill-me"]);
|
|
12
12
|
|
|
13
13
|
function relative(target) { return path.relative(ROOT, target).replaceAll(path.sep, "/"); }
|
|
14
14
|
function entries(directory) { return existsSync(directory) ? readdirSync(directory, { withFileTypes: true }) : []; }
|
|
@@ -59,7 +59,7 @@ export function syncSkills({ check = false } = {}) {
|
|
|
59
59
|
ensureSafeProjection(projection);
|
|
60
60
|
if (!check) mkdirSync(projection, { recursive: true });
|
|
61
61
|
const allowed = [...shared, ...Object.keys(lock.skills?.platform?.[root] ?? {})];
|
|
62
|
-
for (const entry of unlockedProjectionEntries(entries(projection), allowed, (name) => tracked(relative(path.join(projection, name))))) {
|
|
62
|
+
for (const entry of unlockedProjectionEntries(entries(projection), allowed, (name) => tracked(relative(path.join(projection, name)))).filter((entry) => !OBSOLETE.has(entry.name))) {
|
|
63
63
|
const target = path.join(projection, entry.name);
|
|
64
64
|
if (!check && entry.isSymbolicLink() && !existsSync(target)) rmSync(target, { force: true });
|
|
65
65
|
else drift.push(`unlocked projection: ${relative(target)}`);
|
|
@@ -96,16 +96,41 @@ function priorMetadata(lock) {
|
|
|
96
96
|
if (!["shared", "platform"].some((key) => key in skills)) return skills;
|
|
97
97
|
return { ...skills.shared, ...Object.values(skills.platform ?? {}).reduce((all, group) => ({ ...all, ...group }), {}), ...Object.fromEntries(Object.entries(skills).filter(([key]) => !["shared", "platform"].includes(key))) };
|
|
98
98
|
}
|
|
99
|
-
function
|
|
99
|
+
function option(arguments_, name) {
|
|
100
|
+
const prefix = `--${name}=`;
|
|
101
|
+
const value = arguments_.find((argument) => argument.startsWith(prefix));
|
|
102
|
+
return value?.slice(prefix.length) || null;
|
|
103
|
+
}
|
|
104
|
+
function sourceRevisionMap(oldLock, arguments_) {
|
|
105
|
+
const sources = structuredClone(oldLock?.sources ?? {});
|
|
106
|
+
const revision = option(arguments_, "source-revision");
|
|
107
|
+
if (revision) {
|
|
108
|
+
const separator = revision.lastIndexOf(":");
|
|
109
|
+
if (separator <= 0 || separator === revision.length - 1) throw new TypeError("--source-revision 必须使用 source:revision 格式");
|
|
110
|
+
const source = revision.slice(0, separator);
|
|
111
|
+
sources[source] = { ...(sources[source] ?? {}), revision: revision.slice(separator + 1) };
|
|
112
|
+
}
|
|
113
|
+
return sources;
|
|
114
|
+
}
|
|
115
|
+
function metadata(name, skillPath, directory, previous, canonical = false, sources = {}, upstreamRoot = null) {
|
|
100
116
|
const old = previous[name] ?? {};
|
|
101
117
|
let recordedPath = old.skillPath ?? skillPath;
|
|
102
118
|
if (canonical && /^\.(claude|codex|hermes|pi|qoder|trae)\/skills\//.test(recordedPath)) recordedPath = skillPath;
|
|
103
119
|
const result = { source: old.source ?? "project", sourceType: old.sourceType ?? "local", skillPath: recordedPath, effectiveHash: treeHash(directory) };
|
|
104
|
-
|
|
120
|
+
const sourceInfo = sources[result.source];
|
|
121
|
+
if (sourceInfo?.revision) result.sourceRevision = sourceInfo.revision;
|
|
122
|
+
let upstreamDirectory = null;
|
|
123
|
+
if (upstreamRoot && result.source === "mattpocock/skills") {
|
|
124
|
+
upstreamDirectory = path.join(upstreamRoot, recordedPath).replace(/\/SKILL\.md$/, "");
|
|
125
|
+
if (!existsSync(upstreamDirectory)) throw new TypeError(`${name} 的上游路径不存在: ${recordedPath}`);
|
|
126
|
+
result.upstreamHash = treeHash(upstreamDirectory);
|
|
127
|
+
} else if (old.upstreamHash ?? old.computedHash) result.upstreamHash = old.upstreamHash ?? old.computedHash;
|
|
128
|
+
if (result.source === "mattpocock/skills" && result.upstreamHash && result.effectiveHash !== result.upstreamHash) result.adaptationRef = "docs/process/MATT-POCOCK-ENGINEERING-SKILLS.md";
|
|
105
129
|
return result;
|
|
106
130
|
}
|
|
107
131
|
export function updateSkillLock(arguments_ = process.argv.slice(2)) {
|
|
108
|
-
const oldLock = parseLock(); const previous = priorMetadata(oldLock);
|
|
132
|
+
const oldLock = parseLock(); const previous = priorMetadata(oldLock); const sources = sourceRevisionMap(oldLock, arguments_); const upstreamRoot = option(arguments_, "upstream-root");
|
|
133
|
+
if (upstreamRoot && !existsSync(upstreamRoot)) throw new TypeError(`--upstream-root 不存在: ${upstreamRoot}`);
|
|
109
134
|
const additions = arguments_.filter((arg) => arg.startsWith("--add=")).map((arg) => arg.slice(6));
|
|
110
135
|
const removals = new Set(arguments_.filter((arg) => arg.startsWith("--remove=")).map((arg) => arg.slice(9)));
|
|
111
136
|
const platformAdds = arguments_.filter((arg) => arg.startsWith("--add-platform=")).map((arg) => arg.slice(15).split(":", 2));
|
|
@@ -117,7 +142,7 @@ export function updateSkillLock(arguments_ = process.argv.slice(2)) {
|
|
|
117
142
|
if (unlocked.length) throw new TypeError(`发现未登记到 skills-lock.json 的已跟踪共享 skills: ${unlocked.join(", ")}\n确认新增后运行 scripts/update-skill-lock --add=<skill-name>`);
|
|
118
143
|
const targets = [".agents/skills", ...PROJECTION_ROOTS];
|
|
119
144
|
const shared = Object.fromEntries(sharedNames.map((name) => {
|
|
120
|
-
const item = metadata(name, `.agents/skills/${name}/SKILL.md`, path.join(SOURCE_ROOT, name), previous, true); item.targets = targets; return [name, item];
|
|
145
|
+
const item = metadata(name, `.agents/skills/${name}/SKILL.md`, path.join(SOURCE_ROOT, name), previous, true, sources, upstreamRoot); item.targets = targets; return [name, item];
|
|
121
146
|
}));
|
|
122
147
|
const platform = {};
|
|
123
148
|
for (const root of PROJECTION_ROOTS) {
|
|
@@ -126,9 +151,9 @@ export function updateSkillLock(arguments_ = process.argv.slice(2)) {
|
|
|
126
151
|
if (!names.length) continue;
|
|
127
152
|
const absentPlatform = names.filter((name) => !lstatSafe(path.join(location, name))?.isDirectory());
|
|
128
153
|
if (absentPlatform.length) throw new TypeError(`锁文件声明的平台 skills 缺少内容 (${root}): ${absentPlatform.join(", ")}`);
|
|
129
|
-
platform[root] = Object.fromEntries(names.map((name) => { const item = metadata(name, `${root}/${name}/SKILL.md`, path.join(location, name), previous); item.targets = [root]; return [name, item]; }));
|
|
154
|
+
platform[root] = Object.fromEntries(names.map((name) => { const item = metadata(name, `${root}/${name}/SKILL.md`, path.join(location, name), previous, false, sources, upstreamRoot); item.targets = [root]; return [name, item]; }));
|
|
130
155
|
}
|
|
131
|
-
const manifest = { version: 3, generatedBy: "scripts/update-skill-lock", canonicalRoot: ".agents/skills", projectionRoots: PROJECTION_ROOTS, skills: { shared, platform } };
|
|
156
|
+
const manifest = { version: 3, generatedBy: "scripts/update-skill-lock", canonicalRoot: ".agents/skills", projectionRoots: PROJECTION_ROOTS, sources, skills: { shared, platform } };
|
|
132
157
|
const rendered = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
133
158
|
if (arguments_.includes("--check")) { if (!existsSync(LOCK_PATH) || readFileSync(LOCK_PATH, "utf8") !== rendered) throw new TypeError("skills-lock.json is stale; run scripts/update-skill-lock"); return "skills-lock.json matches distributed skills"; }
|
|
134
159
|
writeFileSync(LOCK_PATH, rendered); return `updated skills-lock.json with ${sharedNames.length} shared skills and ${Object.values(platform).reduce((sum, group) => sum + Object.keys(group).length, 0)} platform skills`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseDocument } from "./vendor/yaml.mjs";
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const schemaPath = path.join(root, "docs/process/schemas/frontend-implementation-evidence.schema.json");
|
|
11
|
+
const validator = String.raw`
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from jsonschema import Draft202012Validator
|
|
15
|
+
schema = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
16
|
+
value = json.load(sys.stdin)
|
|
17
|
+
errors = sorted(Draft202012Validator(schema).iter_errors(value), key=lambda error: list(error.absolute_path))
|
|
18
|
+
for error in errors:
|
|
19
|
+
location = ".".join(str(part) for part in error.absolute_path) or "<root>"
|
|
20
|
+
print(f"{location}: {error.message}", file=sys.stderr)
|
|
21
|
+
sys.exit(1 if errors else 0)
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const { values, positionals } = parseArgs({ options: { "allow-template": { type: "boolean" } }, allowPositionals: true, strict: true });
|
|
26
|
+
if (positionals.length !== 1) throw new TypeError("用法: scripts/verify-frontend-implementation-evidence [--allow-template] <file.yaml>");
|
|
27
|
+
const document = parseDocument(readFileSync(path.resolve(positionals[0]), "utf8"), { maxAliasCount: 0, uniqueKeys: true });
|
|
28
|
+
if (document.errors.length) throw new TypeError(document.errors[0].message);
|
|
29
|
+
const value = document.toJS({ maxAliasCount: 0 });
|
|
30
|
+
const result = spawnSync("python3", ["-c", validator, schemaPath], { cwd: root, encoding: "utf8", input: JSON.stringify(value) });
|
|
31
|
+
if (result.status !== 0) throw new TypeError(`${result.stdout}${result.stderr}`.trim());
|
|
32
|
+
if (!values["allow-template"] && value.template !== false) throw new TypeError("正式证据必须设置 template: false");
|
|
33
|
+
if (!values["allow-template"] && value.status !== "approved") throw new TypeError("正式证据必须达到 status: approved");
|
|
34
|
+
const serialized = JSON.stringify(value);
|
|
35
|
+
if (!values["allow-template"] && /REPLACE_ME|<[^>]+>|docs\/\.scratch\/example/.test(serialized)) throw new TypeError("正式证据不得保留模板占位值");
|
|
36
|
+
if (!values["allow-template"]) {
|
|
37
|
+
for (const field of ["prototype_ref", "spec_ref"]) {
|
|
38
|
+
const reference = value[field];
|
|
39
|
+
if (!/^https?:\/\//.test(reference) && !existsSync(path.resolve(root, reference))) throw new TypeError(`${field} 不可读取: ${reference}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!values["allow-template"] && value.kind === "verification") {
|
|
43
|
+
if (value.console_warning_check !== "pass") throw new TypeError("前端验证必须通过 console warning 检查");
|
|
44
|
+
if (value.uncovered_differences.length > 0 && !value.difference_owner) throw new TypeError("存在未覆盖差异时必须填写 difference_owner");
|
|
45
|
+
if (value.independent_review?.status !== "approved") throw new TypeError("前端验证必须通过独立 UI fidelity review");
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write(`前端实现还原 ${value.kind} 验证通过\n`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
process.stderr.write(`前端实现还原证据验证失败: ${error.message}\n`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseDocument } from "./vendor/yaml.mjs";
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const schemaPath = path.join(root, "docs/process/schemas/lifecycle-checkpoint.schema.json");
|
|
11
|
+
const validator = String.raw`
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from jsonschema import Draft202012Validator
|
|
15
|
+
schema = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
16
|
+
value = json.load(sys.stdin)
|
|
17
|
+
errors = sorted(Draft202012Validator(schema).iter_errors(value), key=lambda error: list(error.absolute_path))
|
|
18
|
+
for error in errors:
|
|
19
|
+
location = ".".join(str(part) for part in error.absolute_path) or "<root>"
|
|
20
|
+
print(f"{location}: {error.message}", file=sys.stderr)
|
|
21
|
+
sys.exit(1 if errors else 0)
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const { positionals } = parseArgs({ allowPositionals: true, strict: true });
|
|
26
|
+
if (positionals.length !== 1) throw new TypeError("用法: scripts/verify-lifecycle-checkpoint <checkpoint.yaml|checkpoint.json>");
|
|
27
|
+
const sourcePath = path.resolve(positionals[0]);
|
|
28
|
+
const document = parseDocument(readFileSync(sourcePath, "utf8"), { maxAliasCount: 0, uniqueKeys: true });
|
|
29
|
+
if (document.errors.length) throw new TypeError(document.errors[0].message);
|
|
30
|
+
const value = document.toJS({ maxAliasCount: 0 });
|
|
31
|
+
const result = spawnSync("python3", ["-c", validator, schemaPath], { cwd: root, encoding: "utf8", input: JSON.stringify(value) });
|
|
32
|
+
if (result.status !== 0) throw new TypeError(`${result.stdout}${result.stderr}`.trim());
|
|
33
|
+
process.stdout.write("生命周期 checkpoint 验证通过\n");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
process.stderr.write(`生命周期 checkpoint 验证失败: ${error.message}\n`);
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
}
|
|
@@ -5,7 +5,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
5
5
|
cd "$ROOT_DIR"
|
|
6
6
|
node -e 'const [major]=process.versions.node.split(".").map(Number); if (major < 22 || major >= 27) { console.error("模板工具需要 Node >=22 <27"); process.exit(1); }'
|
|
7
7
|
|
|
8
|
-
required_files=(yss-project.yaml AGENTS.md CONTEXT.md skills-lock.json docs/process/lifecycle-registry.yaml docs/process/lifecycle-registry-baseline.json docs/process/maintenance-intensity.yaml scripts/repository-mode scripts/implementation-path-policy scripts/sync-skills scripts/update-skill-lock scripts/generate-lifecycle-artifacts scripts/verify-lifecycle-registry scripts/verify-maintenance-checkpoint scripts/verify-maintenance-intensity-scenarios scripts/verify-yss-dto-openapi-profile scripts/verify-yss-dto-openapi-scenarios scripts/export-yss-skills)
|
|
8
|
+
required_files=(yss-project.yaml AGENTS.md CONTEXT.md skills-lock.json docs/process/lifecycle-registry.yaml docs/process/lifecycle-registry-baseline.json docs/process/maintenance-intensity.yaml docs/process/schemas/lifecycle-checkpoint.schema.json docs/process/schemas/frontend-implementation-evidence.schema.json docs/process/templates/lifecycle-checkpoint-template.yaml docs/process/templates/frontend-implementation-plan-template.yaml docs/process/templates/frontend-implementation-verification-template.yaml scripts/repository-mode scripts/implementation-path-policy scripts/sync-skills scripts/update-skill-lock scripts/generate-lifecycle-artifacts scripts/verify-lifecycle-registry scripts/verify-lifecycle-checkpoint scripts/verify-frontend-implementation-evidence scripts/verify-maintenance-checkpoint scripts/verify-maintenance-intensity-scenarios scripts/verify-yss-dto-openapi-profile scripts/verify-yss-dto-openapi-scenarios scripts/export-yss-skills)
|
|
9
9
|
for required in "${required_files[@]}"; do test -f "$required" || { echo "缺少模板必需文件: $required" >&2; exit 1; }; done
|
|
10
10
|
|
|
11
11
|
repository_mode="$(scripts/repository-mode)"
|
|
@@ -21,6 +21,9 @@ scripts/update-skill-lock --check
|
|
|
21
21
|
scripts/verify-yss-dto-openapi-profile
|
|
22
22
|
scripts/verify-yss-dto-openapi-scenarios
|
|
23
23
|
scripts/verify-lifecycle-registry
|
|
24
|
+
scripts/verify-lifecycle-checkpoint docs/process/templates/lifecycle-checkpoint-template.yaml
|
|
25
|
+
scripts/verify-frontend-implementation-evidence --allow-template docs/process/templates/frontend-implementation-plan-template.yaml
|
|
26
|
+
scripts/verify-frontend-implementation-evidence --allow-template docs/process/templates/frontend-implementation-verification-template.yaml
|
|
24
27
|
scripts/verify-lifecycle-scenarios
|
|
25
28
|
scripts/verify-maintenance-intensity-scenarios
|
|
26
29
|
scripts/verify-governance-release
|
|
@@ -33,7 +36,7 @@ scripts/verify-yss-ui-scenarios
|
|
|
33
36
|
scripts/verify-openapi-yaml-first-scenarios
|
|
34
37
|
scripts/verify-openapi-json-handoff-scenarios
|
|
35
38
|
|
|
36
|
-
for entry in scripts/sync-skills scripts/update-skill-lock scripts/generate-lifecycle-artifacts scripts/verify-lifecycle-registry scripts/verify-maintenance-checkpoint scripts/verify-maintenance-intensity-scenarios scripts/verify-yss-dto-openapi-profile scripts/verify-yss-dto-openapi-scenarios scripts/verify-lifecycle-scenarios scripts/verify-matt-yss-integration-scenarios scripts/verify-prototype-backend-scaffold-scenarios scripts/verify-yss-router-scenarios scripts/verify-openapi-yaml-first-scenarios scripts/verify-openapi-json-handoff-scenarios scripts/export-yss-skills; do node --check "$entry"; done
|
|
39
|
+
for entry in scripts/sync-skills scripts/update-skill-lock scripts/generate-lifecycle-artifacts scripts/verify-lifecycle-registry scripts/verify-lifecycle-checkpoint scripts/verify-frontend-implementation-evidence scripts/verify-maintenance-checkpoint scripts/verify-maintenance-intensity-scenarios scripts/verify-yss-dto-openapi-profile scripts/verify-yss-dto-openapi-scenarios scripts/verify-lifecycle-scenarios scripts/verify-matt-yss-integration-scenarios scripts/verify-prototype-backend-scaffold-scenarios scripts/verify-yss-router-scenarios scripts/verify-openapi-yaml-first-scenarios scripts/verify-openapi-json-handoff-scenarios scripts/export-yss-skills; do node --check "$entry"; done
|
|
37
40
|
|
|
38
41
|
legacy_runtime="ru""by"
|
|
39
42
|
script_roots=(scripts)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { treeHash } from "./lib/skill-supply-chain.mjs";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const sourceRootArg = args.find((arg) => arg.startsWith("--source-root="))?.slice("--source-root=".length);
|
|
10
|
+
if (!sourceRootArg) {
|
|
11
|
+
process.stderr.write("用法: scripts/verify-upstream-skill-source --source-root=<上游 checkout>\n");
|
|
12
|
+
process.exit(2);
|
|
13
|
+
}
|
|
14
|
+
const sourceRoot = path.resolve(sourceRootArg);
|
|
15
|
+
const lock = JSON.parse(readFileSync(path.join(root, "skills-lock.json"), "utf8"));
|
|
16
|
+
const source = "mattpocock/skills";
|
|
17
|
+
const revision = lock.sources?.[source]?.revision;
|
|
18
|
+
if (!revision) throw new TypeError(`锁文件缺少 ${source} source revision`);
|
|
19
|
+
const git = spawnSync("git", ["-C", sourceRoot, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
20
|
+
if (git.status !== 0) throw new TypeError(`无法读取上游 checkout revision: ${sourceRoot}`);
|
|
21
|
+
if (git.stdout.trim() !== revision) throw new TypeError(`上游 revision 不匹配: expected=${revision} actual=${git.stdout.trim()}`);
|
|
22
|
+
|
|
23
|
+
const shared = lock.skills?.shared ?? {};
|
|
24
|
+
for (const [name, item] of Object.entries(shared)) {
|
|
25
|
+
if (item.source !== source) continue;
|
|
26
|
+
if (item.sourceRevision !== revision) throw new TypeError(`${name} 缺少一致的 sourceRevision`);
|
|
27
|
+
const directory = path.dirname(path.join(sourceRoot, item.skillPath));
|
|
28
|
+
if (!existsSync(directory)) throw new TypeError(`${name} 的上游路径不存在: ${item.skillPath}`);
|
|
29
|
+
const actualHash = treeHash(directory);
|
|
30
|
+
if (actualHash !== item.upstreamHash) throw new TypeError(`${name} upstreamHash 不匹配: expected=${item.upstreamHash} actual=${actualHash}`);
|
|
31
|
+
}
|
|
32
|
+
process.stdout.write(`上游 ${source}@${revision} 及其 skill hashes 验证通过\n`);
|