create-yss-spec 3.3.0 → 3.3.1

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.
Files changed (197) hide show
  1. package/package.json +3 -2
  2. package/src/command-runner.mjs +107 -0
  3. package/src/self-update.js +7 -4
  4. package/src/template/instance-runtime.js +1 -1
  5. package/template/.agents/skills/.strategic-design-skills-manifest.json +2 -2
  6. package/template/.agents/skills/competitive-intelligence/SKILL.md +4 -4
  7. package/template/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  8. package/template/.agents/skills/grill-with-docs/SKILL.md +1 -1
  9. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  10. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  11. package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  12. package/template/.agents/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  13. package/template/.agents/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  14. package/template/.agents/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  15. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +7 -4
  16. package/template/.agents/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  17. package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  18. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  19. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  20. package/template/.agents/skills/yss-research/SKILL.md +1 -1
  21. package/template/.agents/skills/yss-stage-decision/SKILL.md +5 -3
  22. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  23. package/template/.claude/skills/competitive-intelligence/SKILL.md +4 -4
  24. package/template/.claude/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  25. package/template/.claude/skills/grill-with-docs/SKILL.md +1 -1
  26. package/template/.claude/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  27. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  28. package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  29. package/template/.claude/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  30. package/template/.claude/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  31. package/template/.claude/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  32. package/template/.claude/skills/yss-product-lifecycle/SKILL.md +7 -4
  33. package/template/.claude/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  34. package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  35. package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  36. package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  37. package/template/.claude/skills/yss-research/SKILL.md +1 -1
  38. package/template/.claude/skills/yss-stage-decision/SKILL.md +5 -3
  39. package/template/.claude/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  40. package/template/.codex/skills/competitive-intelligence/SKILL.md +4 -4
  41. package/template/.codex/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  42. package/template/.codex/skills/grill-with-docs/SKILL.md +1 -1
  43. package/template/.codex/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  44. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  45. package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  46. package/template/.codex/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  47. package/template/.codex/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  48. package/template/.codex/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  49. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +7 -4
  50. package/template/.codex/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  51. package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  52. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  53. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  54. package/template/.codex/skills/yss-research/SKILL.md +1 -1
  55. package/template/.codex/skills/yss-stage-decision/SKILL.md +5 -3
  56. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  57. package/template/.cursor/skills/competitive-intelligence/SKILL.md +4 -4
  58. package/template/.cursor/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  59. package/template/.cursor/skills/grill-with-docs/SKILL.md +1 -1
  60. package/template/.cursor/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  61. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  62. package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  63. package/template/.cursor/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  64. package/template/.cursor/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  65. package/template/.cursor/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  66. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +7 -4
  67. package/template/.cursor/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  68. package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  69. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  70. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  71. package/template/.cursor/skills/yss-research/SKILL.md +1 -1
  72. package/template/.cursor/skills/yss-stage-decision/SKILL.md +5 -3
  73. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  74. package/template/.pi/skills/competitive-intelligence/SKILL.md +4 -4
  75. package/template/.pi/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  76. package/template/.pi/skills/grill-with-docs/SKILL.md +1 -1
  77. package/template/.pi/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  78. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  79. package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  80. package/template/.pi/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  81. package/template/.pi/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  82. package/template/.pi/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  83. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +7 -4
  84. package/template/.pi/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  85. package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  86. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  87. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  88. package/template/.pi/skills/yss-research/SKILL.md +1 -1
  89. package/template/.pi/skills/yss-stage-decision/SKILL.md +5 -3
  90. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  91. package/template/.qoder/skills/competitive-intelligence/SKILL.md +4 -4
  92. package/template/.qoder/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  93. package/template/.qoder/skills/grill-with-docs/SKILL.md +1 -1
  94. package/template/.qoder/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  95. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  96. package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  97. package/template/.qoder/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  98. package/template/.qoder/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  99. package/template/.qoder/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  100. package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +7 -4
  101. package/template/.qoder/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  102. package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  103. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  104. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  105. package/template/.qoder/skills/yss-research/SKILL.md +1 -1
  106. package/template/.qoder/skills/yss-stage-decision/SKILL.md +5 -3
  107. package/template/.qoder/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  108. package/template/.trae/skills/competitive-intelligence/SKILL.md +4 -4
  109. package/template/.trae/skills/domain-modeling/CONTEXT-FORMAT.md +1 -1
  110. package/template/.trae/skills/grill-with-docs/SKILL.md +1 -1
  111. package/template/.trae/skills/setup-matt-pocock-skills/issue-tracker-local.md +1 -1
  112. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +7 -7
  113. package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  114. package/template/.trae/skills/yss-frontend-scaffold-generator/scripts/generate_scaffold.mjs +22 -8
  115. package/template/.trae/skills/yss-frontend-scaffold-generator/scripts/run_scaffold_verification.mjs +5 -6
  116. package/template/.trae/skills/yss-frontend-scaffold-generator/scripts/scaffold-generator.test.mjs +19 -1
  117. package/template/.trae/skills/yss-product-lifecycle/SKILL.md +7 -4
  118. package/template/.trae/skills/yss-product-lifecycle/references/artifact-dependencies.md +1 -1
  119. package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -3
  120. package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +57 -6
  121. package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +11 -1
  122. package/template/.trae/skills/yss-research/SKILL.md +1 -1
  123. package/template/.trae/skills/yss-stage-decision/SKILL.md +5 -3
  124. package/template/.trae/skills/yss-stage-decision/references/stage-decision-package-contract.md +1 -1
  125. package/template/AGENTS.md +2 -2
  126. package/template/CONTEXT.md +3 -2
  127. package/template/README.md +3 -3
  128. package/template/docs/agents/digital-human-roles.md +1 -1
  129. package/template/docs/agents/digital-human-roles.yaml +11 -11
  130. package/template/docs/agents/issue-tracker.md +1 -1
  131. package/template/docs/agents/strategic-design-skills-integration.md +2 -0
  132. package/template/docs/architecture/README.md +1 -1
  133. package/template/docs/architecture/templates/business-architecture-template.md +1 -1
  134. package/template/docs/architecture/templates/functional-architecture-template.md +1 -1
  135. package/template/docs/design/README.md +1 -1
  136. package/template/docs/design/templates/interaction-spec-template.md +1 -1
  137. package/template/docs/design/templates/product-overview-design-template.md +1 -1
  138. package/template/docs/{discovery → plan}/IDEATION.md +5 -5
  139. package/template/docs/plan/README.md +9 -0
  140. package/template/docs/plan/entry-review.md +23 -0
  141. package/template/docs/plan/templates/plan-template.md +58 -0
  142. package/template/docs/process/harness-process-tailoring.md +1 -1
  143. package/template/docs/process/lifecycle-artifact-map.md +6 -6
  144. package/template/docs/process/lifecycle-registry-baseline.json +5 -1
  145. package/template/docs/process/lifecycle-registry.yaml +25 -20
  146. package/template/docs/process/plan-migration.md +13 -0
  147. package/template/docs/process/schemas/digital-human-task-package.schema.json +2 -0
  148. package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +3 -0
  149. package/template/docs/process/schemas/lifecycle-registry.schema.json +1 -1
  150. package/template/docs/process/template-engineering-overview.md +2 -2
  151. package/template/docs/process/template-verification-profiles.yaml +2 -0
  152. package/template/docs/process/templates/context-reconciliation-template.yaml +2 -2
  153. package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +1 -1
  154. package/template/docs/requirements/README.md +2 -2
  155. package/template/docs/templates/local-parent-ticket-template.md +1 -1
  156. package/template/docs/templates/subagent-task-package-template.md +1 -1
  157. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +1 -1
  158. package/template/scripts/cache-template-commit +2 -2
  159. package/template/scripts/fixtures/strategic-handoff/source-roles.json +7 -7
  160. package/template/scripts/fixtures/user-decision/plan-fixture.mjs +30 -0
  161. package/template/scripts/lib/backend-delivery.mjs +5 -2
  162. package/template/scripts/lib/command-runner.mjs +107 -0
  163. package/template/scripts/lib/context-reconciliation.mjs +34 -0
  164. package/template/scripts/lib/json-schema.mjs +42 -13
  165. package/template/scripts/lib/lifecycle-context-query.mjs +7 -3
  166. package/template/scripts/lib/lifecycle-transition.mjs +9 -3
  167. package/template/scripts/lib/plan-spec-entry.mjs +87 -0
  168. package/template/scripts/lib/scenario-checks.mjs +15 -12
  169. package/template/scripts/lib/skill-supply-chain.mjs +18 -2
  170. package/template/scripts/lib/strategic-handoff-io.mjs +5 -4
  171. package/template/scripts/lib/strategic-handoff.mjs +10 -9
  172. package/template/scripts/lib/task-package.mjs +2 -0
  173. package/template/scripts/lib/template-commit-cache.mjs +7 -5
  174. package/template/scripts/run-template-verification +9 -3
  175. package/template/scripts/sync-strategic-handoff-tools +9 -0
  176. package/template/scripts/verify-context-reconciliation +2 -48
  177. package/template/scripts/verify-context-reconciliation-scenarios +1 -1
  178. package/template/scripts/verify-lifecycle-checkpoint +2 -0
  179. package/template/scripts/verify-lifecycle-context-query-scenarios +28 -8
  180. package/template/scripts/verify-plan-spec-entry +11 -0
  181. package/template/scripts/verify-plan-spec-entry-scenarios +79 -0
  182. package/template/scripts/verify-script-performance-scenarios +56 -0
  183. package/template/scripts/verify-subagent-task-package-scenarios +9 -8
  184. package/template/scripts/verify-template-cache-scenarios +11 -1
  185. package/template/scripts/verify-user-decision-scenarios +4 -4
  186. package/template/skills-lock.json +17 -17
  187. package/template.manifest.json +2 -1
  188. package/template.snapshot.json +5 -5
  189. package/template/docs/discovery/README.md +0 -87
  190. package/template/docs/discovery/reports/.gitkeep +0 -0
  191. package/template/docs/discovery/reports/agent-governance-options.md +0 -71
  192. package/template/docs/discovery/reports/competitor-deep-dive/.gitkeep +0 -0
  193. package/template/docs/discovery/templates/discovery-template.md +0 -94
  194. /package/template/docs/{discovery → plan}/templates/competitive-analysis-template.md +0 -0
  195. /package/template/docs/{discovery → plan}/templates/competitive-matrix-template.md +0 -0
  196. /package/template/docs/{discovery → plan}/templates/market-analysis-template.md +0 -0
  197. /package/template/docs/{discovery → plan}/templates/user-pain-points-template.md +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-yss-spec",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "Initialize a YSS spec project template repository",
5
5
  "main": "src/api/index.js",
6
6
  "repository": {
@@ -28,7 +28,8 @@
28
28
  "test:contracts": "node --test tests/contracts.test.js tests/error-output.test.js tests/apply-result-contract.test.js",
29
29
  "test:unit": "node --test tests/*planner*.test.js tests/filesystem-*.test.js tests/validation-modules.test.js tests/doctor-checks.test.js tests/ownership-policy.test.js tests/lifecycle-policy.test.js tests/lifecycle-metadata.test.js tests/verification-runtime.test.js tests/cli-modules.test.js tests/cli-entrypoint.test.js tests/self-update.test.js tests/plan-schema.test.js tests/contracts.test.js tests/error-output.test.js tests/apply-result-contract.test.js tests/programmatic-api-unit.test.js",
30
30
  "test:integration": "npm run sync-template && node --test tests/init-cli.test.js tests/sync-command-modular.test.js tests/attach-command-modular.test.js tests/doctor-diff-command.test.js tests/programmatic-api.test.js tests/family-identity.test.js",
31
- "prepack": "npm run sync-template"
31
+ "prepack": "npm run sync-template",
32
+ "test:prepared": "node --test --test-concurrency=1 tests/*.test.js"
32
33
  },
33
34
  "devDependencies": {
34
35
  "ajv": "^8.17.1"
@@ -0,0 +1,107 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { appendFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { StringDecoder } from "node:string_decoder";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ function killTree(child, signal) {
7
+ if (!child.pid) return;
8
+ try {
9
+ if (process.platform === "win32") spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
10
+ else process.kill(-child.pid, signal);
11
+ } catch (error) { if (error.code !== "ESRCH") throw error; }
12
+ }
13
+
14
+ /** Bounded lifetime for external tools; stdout is never mixed with progress. */
15
+ export function runCommand(command, args, { cwd, env = process.env, timeoutMs = 0, signal, stdoutFile, stderrFile, secrets = [], progress = false } = {}) {
16
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw new TypeError("timeoutMs 必须是非负毫秒数");
17
+ if (signal?.aborted) return Promise.resolve({ status: 1, stdout: "", stderr: "command cancelled", termination: "cancelled", duration_ms: 0 });
18
+ return new Promise(resolve => {
19
+ const started = performance.now();
20
+ const argumentSecrets = args.flatMap(value => { try { const url = new URL(value); return url.password ? [url.password, decodeURIComponent(url.password)] : []; } catch { return []; } });
21
+ const secretNames = new Map(Object.entries(env).filter(([key]) => /(?:PASSWORD|TOKEN|SECRET)$/.test(key) || key === "MAVEN_REPO_USERNAME").map(([key,value]) => [value,key]));
22
+ const replacement = secret => secretNames.has(secret) ? `[REDACTED:${secretNames.get(secret)}]` : "[REDACTED]";
23
+ const sensitive = [...secrets, ...argumentSecrets, ...secretNames.keys()].filter(value => typeof value === "string" && value.length > 0).sort((a, b) => b.length - a.length);
24
+ const redact = text => sensitive.reduce((value, secret) => value.replaceAll(secret, replacement(secret)), text);
25
+ if (progress) process.stderr.write(`[开始] ${redact(command + " " + args.join(" "))}\n`);
26
+ for (const file of [stdoutFile, stderrFile]) if (file) writeFileSync(file, "");
27
+ const child = spawn(command, args, { cwd, env, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"] });
28
+ let termination = null, escalation, timer, errorMessage;
29
+ const makeStream = file => {
30
+ const decoder = new StringDecoder("utf8");
31
+ let pending = "", captured = "";
32
+ const emit = text => { captured += text; if (file) appendFileSync(file, text); };
33
+ function consume(final = false) {
34
+ let output = "", cursor = 0;
35
+ while (cursor < pending.length) {
36
+ const match = sensitive.find(secret => pending.startsWith(secret, cursor));
37
+ if (match) { output += replacement(match); cursor += match.length; continue; }
38
+ if (!final && sensitive.some(secret => secret.startsWith(pending.slice(cursor)))) break;
39
+ output += pending[cursor++];
40
+ }
41
+ pending = pending.slice(cursor);
42
+ emit(output);
43
+ }
44
+ return { write(bytes) { pending += decoder.write(bytes); consume(); }, end() { pending += decoder.end(); consume(true); return captured; } };
45
+ };
46
+ const stdout = makeStream(stdoutFile), stderr = makeStream(stderrFile);
47
+ function cancel(reason) {
48
+ if (termination) return;
49
+ termination = reason;
50
+ killTree(child, "SIGTERM");
51
+ escalation = setTimeout(() => killTree(child, "SIGKILL"), 1000);
52
+ }
53
+ const abort = () => cancel("cancelled");
54
+ const onInterrupt = () => cancel("cancelled");
55
+ signal?.addEventListener("abort", abort, { once: true });
56
+ process.on("SIGINT", onInterrupt); process.on("SIGTERM", onInterrupt);
57
+ if (timeoutMs) timer = setTimeout(() => cancel("timeout"), timeoutMs);
58
+ child.stdout.on("data", bytes => stdout.write(bytes));
59
+ child.stderr.on("data", bytes => stderr.write(bytes));
60
+ child.on("error", error => { errorMessage = redact(error.message); });
61
+ child.on("close", (code, exitSignal) => {
62
+ clearTimeout(timer); clearTimeout(escalation);
63
+ // A group leader can exit before its descendants close redirected streams.
64
+ if (termination) killTree(child, "SIGKILL");
65
+ signal?.removeEventListener("abort", abort);
66
+ process.off("SIGINT", onInterrupt); process.off("SIGTERM", onInterrupt);
67
+ const duration_ms = performance.now() - started;
68
+ const status = termination === "timeout" ? 124 : termination ? 1 : code ?? 1;
69
+ if (progress) process.stderr.write(`[${status === 0 ? "完成" : "失败"}] ${command} (${Math.round(duration_ms)}ms, exit=${status}${termination ? `, ${termination}` : ""})\n`);
70
+ resolve({ status, stdout: stdout.end(), stderr: stderr.end() + (errorMessage || ""), signal: exitSignal, termination, duration_ms });
71
+ });
72
+ });
73
+ }
74
+
75
+ // Synchronous API compatibility for existing generators/cache consumers. The worker
76
+ // owns the process group and streams logs; it never updates project metadata.
77
+ export function runCommandSync(command, args, options = {}) {
78
+ const result = spawnSync(process.execPath, [fileURLToPath(import.meta.url), "--worker"], {
79
+ input: JSON.stringify({ command, args, options }), encoding: "utf8", maxBuffer: 64 * 1024 * 1024,
80
+ stdio: ["pipe", "pipe", "inherit"], env: options.env ?? process.env,
81
+ });
82
+ if (result.error || result.status !== 0) throw new TypeError(result.error?.message || "command worker failed");
83
+ return JSON.parse(result.stdout);
84
+ }
85
+
86
+ if (process.argv[1] === fileURLToPath(import.meta.url) && process.argv[2] === "--worker") {
87
+ const parent = process.ppid;
88
+ let input = "";
89
+ for await (const bytes of process.stdin) input += bytes;
90
+ const { command, args, options } = JSON.parse(input);
91
+ const controller = new AbortController();
92
+ const watchParent = setInterval(() => {
93
+ if (process.ppid !== parent) controller.abort();
94
+ else {
95
+ try { process.kill(parent, 0); } catch (error) { if (error.code === "ESRCH") controller.abort(); }
96
+ }
97
+ }, 100);
98
+ try {
99
+ const result = await runCommand(command, args, { ...options, signal: controller.signal });
100
+ if (result.status !== 0) {
101
+ // Only caller-created temporary resources are supplied here. The worker
102
+ // also owns this cleanup when a terminated synchronous caller cannot run finally.
103
+ for (const directory of options.cleanupPathsOnFailure || []) rmSync(directory, { recursive: true, force: true });
104
+ }
105
+ if (process.ppid === parent) process.stdout.write(JSON.stringify(result));
106
+ } finally { clearInterval(watchParent); }
107
+ }
@@ -46,11 +46,14 @@ function compareSemver(left, right) {
46
46
  }
47
47
 
48
48
  function defaultSpawn(command, args, options = {}) {
49
- return spawnSync(command, args, {
50
- encoding: "utf8",
51
- timeout: 120000,
52
- ...options,
49
+ const result = spawnSync(process.execPath, [path.join(__dirname, "command-runner.mjs"), "--worker"], {
50
+ input: JSON.stringify({ command, args, options: { cwd: options.cwd, env: options.env || process.env, timeoutMs: options.timeout ?? 120000 } }),
51
+ encoding: "utf8", maxBuffer: 64 * 1024 * 1024,
53
52
  });
53
+ if (result.error || result.status !== 0) return result;
54
+ const outcome = JSON.parse(result.stdout);
55
+ if (outcome.termination) outcome.stderr = outcome.termination + ": " + outcome.stderr;
56
+ return outcome;
54
57
  }
55
58
 
56
59
  function spawnFailed(result) {
@@ -168,7 +168,7 @@ function renderTemplateFile(relativePath, content, variables) {
168
168
 
169
169
  if (!variables.includeExampleDocs) {
170
170
  return renderedContent.replace(
171
- /^\| \[docs\/discovery\/IDEATION\.md\]\(\.\/docs\/discovery\/IDEATION\.md\) \|.*\r?\n/m,
171
+ /^\| \[docs\/plan\/IDEATION\.md\]\(\.\/docs\/plan\/IDEATION\.md\) \|.*\r?\n/m,
172
172
  "",
173
173
  );
174
174
  }
@@ -3,7 +3,7 @@
3
3
  "source": "iloveZzz/yss-harness-design-agent",
4
4
  "source_type": "git-submodule",
5
5
  "source_root": ".agents/skills",
6
- "source_revision": "8a67e0f5a56aa5085d18c2b42bb540c8f36c2fab",
6
+ "source_revision": "3a358684c5684ada433dca4fd97ee187050ca6d8",
7
7
  "adaptation_ref": "docs/agents/strategic-design-skills-integration.md",
8
8
  "skills": [
9
9
  {
@@ -34,7 +34,7 @@
34
34
  {
35
35
  "upstream": "yss-stage-decision",
36
36
  "canonical": "yss-stage-decision",
37
- "upstream_hash": "1eadf230f0e17bf6d209c1df1249acef3520ab29c26a80c476a2a557a224c10a"
37
+ "upstream_hash": "2ec63f66aaf799d693cec0f0b5d71b8c4917e4e2822e0ddd519bc9b711fa3c95"
38
38
  }
39
39
  ]
40
40
  }
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: competitive-intelligence
3
- description: Use when researching competitors, market positioning, pricing, feature gaps, customer sentiment, or category trends before Discovery, Spec, product strategy, or roadmap decisions.
3
+ description: Use when researching competitors, market positioning, pricing, feature gaps, customer sentiment, or category trends before Plan, Spec, product strategy, or roadmap decisions.
4
4
  ---
5
5
 
6
6
  # Competitive Intelligence
7
7
 
8
8
  Source requested for this project: `anthropics/knowledge-work-plugins`, `sales/skills/competitive-intelligence`.
9
9
 
10
- Use this skill when competitive or market facts are needed to shape product Discovery, Spec scope, positioning, differentiation, pricing assumptions, or roadmap tradeoffs.
10
+ Use this skill when competitive or market facts are needed to shape product Plan, Spec scope, positioning, differentiation, pricing assumptions, or roadmap tradeoffs.
11
11
 
12
12
  ## Core Rules
13
13
 
@@ -45,8 +45,8 @@ Use this skill when competitive or market facts are needed to shape product Disc
45
45
  - Risks if we copy the competitor too closely.
46
46
  - Product opportunities for Spec or roadmap.
47
47
  5. Hand off to lifecycle assets:
48
- - Update Discovery findings in `docs/.scratch/<feature>/discovery/`.
49
- - Create or update a competitor matrix in `docs/.scratch/<feature>/discovery/reports/`.
48
+ - Update Plan findings in `docs/.scratch/<feature>/plan/`.
49
+ - Create or update a competitor matrix in `docs/.scratch/<feature>/plan/reports/`.
50
50
  - Feed stable user, pain, MVP, non-goal, and success criteria into `grill-with-docs` and `to-spec`.
51
51
  - Record uncertain claims as assumptions, not requirements.
52
52
 
@@ -45,7 +45,7 @@ context_schema_version: 1
45
45
 
46
46
  ## 内容边界
47
47
 
48
- - 只写已经确认、稳定且未来资产需要复用的业务语言。候选、猜测和未决术语保留在 Discovery、业务边界与规则设计或 `context_delta`。
48
+ - 只写已经确认、稳定且未来资产需要复用的业务语言。候选、猜测和未决术语保留在 Plan、业务边界与规则设计或 `context_delta`。
49
49
  - 定义保持一到两句,说明概念是什么;不要写类全名、表名、接口路径、实现步骤或计划。
50
50
  - 禁用别名使用 `避免:别名一、别名二`;需要补充说明时追加 `;备注:...`。
51
51
  - 术语稳定时立即回写。工作单元完成、门禁批准或阶段转换前仍须执行 context reconciliation。
@@ -11,7 +11,7 @@ Call the Skill tool with `grilling`, then call it separately with `domain-modeli
11
11
  1. Read `yss-project.yaml` and the root glossary before the first round, then run `scripts/verify-context-contract --root . --json`. Missing or lowercase root files, nested `CONTEXT.md`, `CONTEXT-MAP.md`, unsupported schema, absolute/cross-repository references, and Markdown pseudo-anchors are `blocked` or `migration-required`.
12
12
  2. Separate discoverable facts from user decisions; investigate facts instead of asking the user.
13
13
  3. Ask the entire current decision frontier, with a recommendation for every question, then wait.
14
- 4. Keep candidate or disputed terms in the Discovery decision frontier. Only after confirmation, use `domain-modeling` to record true stable glossary entries in the repository root's single, case-sensitive `CONTEXT.md`; business rows use the five-column format and `<ContextId>/<EnglishIdentifier>` identity. Non-`Global` Context IDs must come from the approved business responsibility areas in the business-boundary asset.
14
+ 4. Keep candidate or disputed terms in the Plan decision frontier. Only after confirmation, use `domain-modeling` to record true stable glossary entries in the repository root's single, case-sensitive `CONTEXT.md`; business rows use the five-column format and `<ContextId>/<EnglishIdentifier>` identity. Non-`Global` Context IDs must come from the approved business responsibility areas in the business-boundary asset.
15
15
  5. Create an ADR only when the decision is hard to reverse, surprising without context, and a real trade-off.
16
16
  6. Persist factual research/validation records in the repository's existing review or research convention.
17
17
  7. For `project-instance`, before requesting approval or returning a next work unit, generate and validate `context_reconciliation` with a readable ref, `document_digest`, `referenced_terms_digest`, and `term_refs`. Unresolved candidates, scope conflicts, aliases, or digest drift are `blocked`; reconciliation does not create another gate. For `template-source`, do not add fictional business terms and return a reasoned `not-applicable` after validating the template contract.
@@ -31,7 +31,7 @@ When `yss-product-lifecycle` is active, the same `docs/.scratch/<feature-slug>/`
31
31
  ```text
32
32
  docs/.scratch/<feature-slug>/
33
33
  ├── map.md
34
- ├── discovery/
34
+ ├── plan/
35
35
  ├── spec.md
36
36
  ├── spec-delta/
37
37
  ├── parent-ticket.md
@@ -3,7 +3,7 @@
3
3
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import process from "node:process";
6
- import { spawn } from "node:child_process";
6
+ import { runCommand } from "../../../../scripts/lib/command-runner.mjs";
7
7
  import { assertArchitectureAgreement } from "../../../../scripts/lib/backend-architecture.mjs";
8
8
  import { assertLocalDatabaseProfile, scaffoldArchitectureIdentity } from "../../../../scripts/lib/scaffold-local-database.mjs";
9
9
 
@@ -37,7 +37,7 @@ function classifyFailure(phase, outcome) {
37
37
  if (phase === "package") return "packaging";
38
38
  return "maven-execution";
39
39
  }
40
- function parseArgs(argv) { const result = {}; for (let index = 0; index < argv.length; index += 1) { let token = argv[index]; if (token === "--help" || token === "-h") { result.help = true; continue; } const equals = token.indexOf("="); let value; if (equals !== -1) { value = token.slice(equals + 1); token = token.slice(0, equals); } if (!["--project-root", "--evidence-dir"].includes(token)) throw new Error(`不支持的参数: ${token}`); if (value === undefined) value = argv[++index]; if (!value || value.startsWith("--")) throw new Error(`参数 ${token} 缺少值`); result[token === "--project-root" ? "projectRoot" : "evidenceDir"] = path.resolve(value); } if (result.help) return result; if (!result.projectRoot || !result.evidenceDir) throw new Error("必须提供 --project-root 和 --evidence-dir"); return result; }
40
+ function parseArgs(argv) { const result = {}; for (let index = 0; index < argv.length; index += 1) { let token = argv[index]; if (token === "--help" || token === "-h") { result.help = true; continue; } const equals = token.indexOf("="); let value; if (equals !== -1) { value = token.slice(equals + 1); token = token.slice(0, equals); } if (!["--project-root", "--evidence-dir", "--timeout-ms"].includes(token)) throw new Error(`不支持的参数: ${token}`); if (value === undefined) value = argv[++index]; if (!value || value.startsWith("--")) throw new Error(`参数 ${token} 缺少值`); if (token === "--timeout-ms") result.timeoutMs = Number(value); else result[token === "--project-root" ? "projectRoot" : "evidenceDir"] = path.resolve(value); } if (result.help) return result; if (!result.projectRoot || !result.evidenceDir) throw new Error("必须提供 --project-root 和 --evidence-dir"); return result; }
41
41
  function validateManifest(manifest) {
42
42
  if (![2, 3, 4].includes(manifest.schema_version)) throw new Error(`unsupported: scaffold Manifest schema_version=${manifest.schema_version};只读兼容 v2,并验证当前 v3/v4`);
43
43
  const required = ["schema_version", "contract_id", "contract_version", "scaffold_request_id", "contract_digest", "profiles", "ownership", "readiness", "generation_policy", "completion_level", "approval_ref", "approver", "lifecycle_approval_ref", "compiler_draft_ref", "persisted_ref", "contract_file_ref", "current_version", "allowed_write_paths", "expected_evidence_files", "verification_commands", "generation_mode"];
@@ -57,8 +57,7 @@ function validateManifest(manifest) {
57
57
  if (manifest.current_version !== manifest.contract_version) throw new Error("脚手架生成元数据清单不是当前合同版本");
58
58
  if (JSON.stringify(manifest.verification_commands) !== JSON.stringify(COMMANDS)) throw new Error("脚手架生成元数据清单验证命令不符合固定合同");
59
59
  }
60
- function execute(wrapper, phase, cwd, environment) { return new Promise((resolve) => { const child = spawn(wrapper, [phase], { cwd, env: environment, stdio: ["ignore", "pipe", "pipe"] }); let stdout = "", stderr = ""; child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.on("error", (error) => resolve({ exitCode: 127, stdout: "", stderr: String(error) })); child.on("close", (code) => resolve({ exitCode: code ?? 1, stdout, stderr })); }); }
61
- export async function run(projectRoot, evidenceDir, environment = process.env) {
60
+ export async function run(projectRoot, evidenceDir, environment = process.env, { timeoutMs = 0, signal } = {}) {
62
61
  const wrapper = path.join(projectRoot, "mvnw"), manifestPath = path.join(projectRoot, ".yss", "scaffold-generation.json");
63
62
  if (!await isFile(wrapper)) throw new Error(`项目根目录缺少 Maven wrapper: ${wrapper}`);
64
63
  if (!await isFile(manifestPath)) throw new Error(`项目根目录缺少脚手架生成元数据清单: ${manifestPath}`);
@@ -91,7 +90,8 @@ export async function run(projectRoot, evidenceDir, environment = process.env) {
91
90
  const stderrPath = path.join(evidenceDir, `mvnw-${phase}.stderr.log`);
92
91
  const startedAt = isoNow();
93
92
  const started = process.hrtime.bigint();
94
- const outcome = await execute(wrapper, phase, projectRoot, environment);
93
+ const execution = await runCommand(wrapper, [phase], { cwd: projectRoot, env: environment, timeoutMs, signal, stdoutFile: stdoutPath, stderrFile: stderrPath, secrets: [environment.MAVEN_REPO_USERNAME, environment.MAVEN_REPO_PASSWORD], progress: true });
94
+ const outcome = { ...execution, exitCode: execution.status };
95
95
  const durationMs = Number(process.hrtime.bigint() - started) / 1e6;
96
96
  await writeText(stdoutPath, redactSecrets(outcome.stdout, environment));
97
97
  await writeText(stderrPath, redactSecrets(outcome.stderr, environment));
@@ -99,7 +99,7 @@ export async function run(projectRoot, evidenceDir, environment = process.env) {
99
99
  command: `./mvnw ${phase}`,
100
100
  phase,
101
101
  exit_code: outcome.exitCode,
102
- failure_category: classifyFailure(phase, outcome),
102
+ failure_category: outcome.termination || classifyFailure(phase, outcome),
103
103
  duration_ms: Number(durationMs.toFixed(3)),
104
104
  started_at: startedAt,
105
105
  executed_at: isoNow(),
@@ -120,5 +120,5 @@ export async function run(projectRoot, evidenceDir, environment = process.env) {
120
120
  commands
121
121
  };
122
122
  }
123
- async function main() { let args; try { args = parseArgs(process.argv.slice(2)); if (args.help) { console.log("运行 YSS 脚手架真实验证命令\n用法: node scripts/run_scaffold_verification.mjs --project-root <dir> --evidence-dir <dir>"); return 0; } const reportPath = path.join(args.evidenceDir, "scaffold-verification.json"); try { const report = await run(args.projectRoot, args.evidenceDir); await writeText(reportPath, `${JSON.stringify(report, null, 2)}\n`); console.log(`${report.status}: ${reportPath}`); return report.status === "passed" ? 0 : 1; } catch (error) { const report = { verification_mode: "controlled-generation", project_root: args.projectRoot, generated_at: isoNow(), status: "failed", failure_category: "verification-preflight", error: error.message, commands: [] }; await writeText(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stderr.write(`❌ 脚手架验证无法执行: ${error.message}\n`); return 1; } } catch (error) { process.stderr.write(`❌ 脚手架验证无法执行: ${error.message}\n`); return 1; } }
123
+ async function main() { let args; try { args = parseArgs(process.argv.slice(2)); if (args.help) { console.log("运行 YSS 脚手架真实验证命令\n用法: node scripts/run_scaffold_verification.mjs --project-root <dir> --evidence-dir <dir>"); return 0; } const reportPath = path.join(args.evidenceDir, "scaffold-verification.json"); try { const report = await run(args.projectRoot, args.evidenceDir, process.env, { timeoutMs: args.timeoutMs ?? 0 }); await writeText(reportPath, `${JSON.stringify(report, null, 2)}\n`); console.log(`${report.status}: ${reportPath}`); return report.status === "passed" ? 0 : 1; } catch (error) { const report = { verification_mode: "controlled-generation", project_root: args.projectRoot, generated_at: isoNow(), status: "failed", failure_category: "verification-preflight", error: error.message, commands: [] }; await writeText(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stderr.write(`❌ 脚手架验证无法执行: ${error.message}\n`); return 1; } } catch (error) { process.stderr.write(`❌ 脚手架验证无法执行: ${error.message}\n`); return 1; } }
124
124
  if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) process.exitCode = await main();
@@ -36,7 +36,7 @@ branch: template
36
36
  1. 确认当前任务已经通过 Harness 入口分诊、逐项目脚手架决定已由真实用户确认,且 schema v4 合同已由生命周期批准、持久化并保持当前。
37
37
  2. 确认目标是外部实现仓库;只有用户明确选择时才输出到 Harness 仓库的 `apps/frontend/<project>/`。`apps/frontend/` 只能作为项目容器,`app/frontend/`、`app/backend/` 及其子路径禁止作为输出位置。`git-submodule` 只能在已初始化且附加分支的子仓工作树生成;空 gitlink、detached HEAD、`--force` 覆盖挂载点不得当成普通目录。
38
38
  3. 只读检查模板分支和合同锁定的 40 位 commit 是否可访问;生成时使用精确 commit,不跟随浮动分支。
39
- 4. 使用 `scripts/generate_and_verify_scaffold.mjs` 从已核验 checkout 复制工作树,排除模板 `.git`;目标必须不存在或为空。不得默认写入 Harness、不得 `--force`。仅合同 `init_git=true` 时初始化目标 Git。
39
+ 4. 使用 `scripts/generate_and_verify_scaffold.mjs` 从已核验 checkout 的批准 commit 导出模板,排除模板 `.git` 及未提交的工作树内容;目标必须不存在或为空。不得默认写入 Harness、不得 `--force`。仅合同 `init_git=true` 时初始化目标 Git。
40
40
  5. 替换应用名、微应用名、路由、`micro-config.json`、环境变量和 README 中的模板占位。
41
41
  6. 有 API 影响时核验 OpenAPI Freeze 记录和 JSON 派生记录,将摘要一致的 JSON 原样物化到 `<frontend>/openapi/openapi.json`;`openapi_impact=not-applicable` 时必须有原因并禁止生成 API client。
42
42
  7. 保持模板既有的前端代码生成配置不变;本 Harness 只将 SHA-256 一致的 JSON 原样交给既有前端代码生成流程,不修改该配置、不在此仓库执行生成,也不设置生成 CI 门禁。目标前端项目在需要时手动运行其既有命令。
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash } from "node:crypto";
3
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
+ import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import path from "node:path";
5
+ import { tmpdir } from "node:os";
5
6
  import { spawnSync } from "node:child_process";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { validateJsonSchema } from "../../../../scripts/lib/json-schema.mjs";
@@ -22,7 +23,7 @@ function git(cwd, ...args) {
22
23
  function walk(root) {
23
24
  return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
24
25
  const item = path.join(root, entry.name);
25
- return entry.isDirectory() ? walk(item) : entry.isFile() ? [item] : [];
26
+ return entry.isDirectory() ? walk(item) : entry.isFile() || entry.isSymbolicLink() ? [item] : [];
26
27
  });
27
28
  }
28
29
  function validate(contract, outputDir) {
@@ -47,7 +48,15 @@ export function generate({ contractFile, templateCheckout, outputDir }) {
47
48
  if (git(templateCheckout, "config", "--get", "remote.origin.url") !== contract.frontend.template.repository) fail("template repository differs from approved source");
48
49
  if (existsSync(outputDir) && readdirSync(outputDir).length > 0) fail("target directory must not exist or must be empty");
49
50
  mkdirSync(outputDir, { recursive: true });
50
- cpSync(templateCheckout, outputDir, { recursive: true, filter: (source) => path.basename(source) !== ".git" });
51
+ const scratch = mkdtempSync(path.join(tmpdir(), "yss-frontend-source-"));
52
+ try {
53
+ // Read the approved Git tree, never dependency caches or edits in the checkout.
54
+ const archive = spawnSync("git", ["archive", contract.frontend.template.commit], { cwd: templateCheckout, maxBuffer: 256 * 1024 * 1024 });
55
+ if (archive.error || archive.status !== 0) fail(archive.error?.message || String(archive.stderr));
56
+ const unpack = spawnSync("tar", ["-xf", "-", "-C", scratch], { input: archive.stdout });
57
+ if (unpack.error || unpack.status !== 0) fail(unpack.error?.message || String(unpack.stderr));
58
+ cpSync(scratch, outputDir, { recursive: true, verbatimSymlinks: true });
59
+ } finally { rmSync(scratch, { recursive: true, force: true }); }
51
60
 
52
61
  const replacements = {
53
62
  "__APP_NAME__": contract.frontend.app_name,
@@ -55,10 +64,14 @@ export function generate({ contractFile, templateCheckout, outputDir }) {
55
64
  "__BASE_ROUTE__": contract.frontend.base_route,
56
65
  ...(contract.frontend.replacements || {}),
57
66
  };
58
- for (const file of walk(outputDir)) {
59
- if (statSync(file).size > 2_000_000) continue;
60
- let text;
61
- try { text = readFileSync(file, "utf8"); } catch { continue; }
67
+ const generatedFiles = walk(outputDir).map(file => path.relative(outputDir, file));
68
+ for (const ref of generatedFiles) {
69
+ const file = path.join(outputDir, ref);
70
+ const info = lstatSync(file);
71
+ if (info.isSymbolicLink() || info.size > 2_000_000) continue;
72
+ const bytes = readFileSync(file);
73
+ if (bytes.includes(0) || !Buffer.from(bytes.toString("utf8")).equals(bytes)) continue;
74
+ const text = bytes.toString("utf8");
62
75
  const replaced = Object.entries(replacements).reduce((value, [from, to]) => value.split(from).join(to), text);
63
76
  if (replaced !== text) writeFileSync(file, replaced);
64
77
  }
@@ -67,9 +80,10 @@ export function generate({ contractFile, templateCheckout, outputDir }) {
67
80
  if (sha256(bytes) !== contract.frontend.openapi_json_digest) fail("OpenAPI JSON digest mismatch");
68
81
  mkdirSync(path.join(outputDir, "openapi"), { recursive: true });
69
82
  writeFileSync(path.join(outputDir, "openapi", "openapi.json"), bytes);
83
+ if (!generatedFiles.includes("openapi/openapi.json")) generatedFiles.push("openapi/openapi.json");
70
84
  }
71
85
  if (contract.init_git === true) git(outputDir, "init");
72
- const generatedFiles = walk(outputDir).map((file) => path.relative(outputDir, file)).filter((file) => file !== ".yss/scaffold-generation.json").sort();
86
+ generatedFiles.sort();
73
87
  const manifest = {
74
88
  schema_version: 4,
75
89
  kind: "frontend-scaffold",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
- import { spawnSync } from "node:child_process";
4
+ import { runCommandSync } from "../../../../scripts/lib/command-runner.mjs";
5
5
 
6
6
  const ALLOWED = new Map([
7
7
  ["pnpm install --frozen-lockfile", ["install", "--frozen-lockfile"]],
@@ -10,7 +10,7 @@ const ALLOWED = new Map([
10
10
  ["pnpm build", ["build"]],
11
11
  ]);
12
12
  function argsOf(argv) { const args = {}; for (let i = 0; i < argv.length; i += 2) args[argv[i].replace(/^--/, "")] = argv[i + 1]; return args; }
13
- export function verify(projectRoot, evidenceDir) {
13
+ export function verify(projectRoot, evidenceDir, { timeoutMs = 0 } = {}) {
14
14
  projectRoot = path.resolve(projectRoot); evidenceDir = path.resolve(evidenceDir);
15
15
  const manifestPath = path.join(projectRoot, ".yss", "scaffold-generation.json");
16
16
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -23,10 +23,9 @@ export function verify(projectRoot, evidenceDir) {
23
23
  const argv = ALLOWED.get(command);
24
24
  if (!argv) throw new TypeError(`unsupported verification command: ${command}`);
25
25
  const started = Date.now();
26
- const result = spawnSync("pnpm", argv, { cwd: projectRoot, encoding: "utf8" });
27
26
  const stdoutRef = path.join(evidenceDir, `${index + 1}-stdout.log`), stderrRef = path.join(evidenceDir, `${index + 1}-stderr.log`);
28
- writeFileSync(stdoutRef, result.stdout || ""); writeFileSync(stderrRef, result.stderr || "");
29
- return { command, exit_code: result.status ?? 1, duration_ms: Date.now() - started, stdout_ref: stdoutRef, stderr_ref: stderrRef, executed_at: new Date().toISOString() };
27
+ const result = runCommandSync("pnpm", argv, { cwd: projectRoot, timeoutMs, stdoutFile: stdoutRef, stderrFile: stderrRef, progress: true });
28
+ return { command, exit_code: result.status ?? 1, duration_ms: Date.now() - started, ...(result.termination ? { termination: result.termination } : {}), stdout_ref: stdoutRef, stderr_ref: stderrRef, executed_at: new Date().toISOString() };
30
29
  });
31
30
  const passed = results.every((item) => item.exit_code === 0);
32
31
  const report = { schema_version: 1, kind: "frontend-scaffold-verification", status: passed ? "passed" : "failed", project_root: projectRoot, scaffold_manifest_ref: manifestPath, commands: results };
@@ -36,6 +35,6 @@ export function verify(projectRoot, evidenceDir) {
36
35
  return report;
37
36
  }
38
37
  if (import.meta.url === `file://${process.argv[1]}`) {
39
- try { const args = argsOf(process.argv.slice(2)); const report = verify(args["project-root"], args["evidence-dir"]); process.stdout.write(`${report.status}\n`); if (report.status !== "passed") process.exitCode = 1; }
38
+ try { const args = argsOf(process.argv.slice(2)); const report = verify(args["project-root"], args["evidence-dir"], { timeoutMs: Number(args["timeout-ms"] || 0) }); process.stdout.write(`${report.status}\n`); if (report.status !== "passed") process.exitCode = 1; }
40
39
  catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
41
40
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import assert from "node:assert/strict";
3
- import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { spawnSync } from "node:child_process";
@@ -12,10 +12,19 @@ mkdirSync(template); mkdirSync(bin);
12
12
  writeFileSync(path.join(template, "package.json"), JSON.stringify({ name: "__APP_NAME__", scripts: { lint: "noop", "type-check": "noop", build: "noop" } }));
13
13
  writeFileSync(path.join(template, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
14
14
  writeFileSync(path.join(template, "micro-config.json"), JSON.stringify({ name: "__MICROAPP_NAME__", route: "__BASE_ROUTE__" }));
15
+ mkdirSync(path.join(template, "assets"));
16
+ writeFileSync(path.join(template, "assets/binary.bin"), Buffer.from([0, 255, 95, 95, 65, 80, 80, 95, 78, 65, 77, 69, 95, 95]));
17
+ writeFileSync(path.join(template, "assets/large.txt"), "__APP_NAME__".repeat(200_000));
18
+ writeFileSync(path.join(template, "assets/run.sh"), "#!/bin/sh\necho __APP_NAME__\n");
19
+ chmodSync(path.join(template, "assets/run.sh"), 0o755);
20
+ symlinkSync("run.sh", path.join(template, "assets/current"));
15
21
  for (const args of [["init"], ["config", "user.email", "test@example.com"], ["config", "user.name", "test"], ["remote", "add", "origin", template], ["add", "."], ["commit", "-m", "fixture"]]) {
16
22
  const result = spawnSync("git", args, { cwd: template, encoding: "utf8" }); assert.equal(result.status, 0, result.stderr);
17
23
  }
18
24
  const commit = spawnSync("git", ["rev-parse", "HEAD"], { cwd: template, encoding: "utf8" }).stdout.trim();
25
+ mkdirSync(path.join(template, "node_modules"));
26
+ writeFileSync(path.join(template, "node_modules/cache.txt"), "not approved");
27
+ writeFileSync(path.join(template, "micro-config.json"), "uncommitted edit");
19
28
  const contract = {
20
29
  schema_version: 4, kind: "project-scaffold-contract", contract_id: "frontend.demo.v1", contract_version: "1", status: "approved", persisted_ref: "docs/.scratch/demo/scaffold-contract.json", current_version: true,
21
30
  delivery_role: "frontend", scaffold_kind: "frontend-yss-vue3", generator_skill: "yss-frontend-scaffold-generator", implementation_repository: output, target_output_dir: output, repository_scope: "external-repository", init_git: false,
@@ -31,6 +40,15 @@ assert.equal(existsSync(path.join(output, ".git")), false);
31
40
  assert.equal(JSON.parse(readFileSync(path.join(output, "package.json"))).name, "demo-app");
32
41
  assert.equal(JSON.parse(readFileSync(path.join(output, ".yss", "scaffold-generation.json"))).completion_level, "empty-scaffold-verified");
33
42
  assert.equal(JSON.parse(readFileSync(path.join(evidence, "scaffold-verification.json"))).commands.length, 4);
43
+ assert.equal(existsSync(path.join(output, "node_modules")), false);
44
+ assert.equal(JSON.parse(readFileSync(path.join(output, "micro-config.json"))).name, "demo");
45
+ assert.deepEqual(readFileSync(path.join(output, "assets/binary.bin")), readFileSync(path.join(template, "assets/binary.bin")));
46
+ assert.deepEqual(readFileSync(path.join(output, "assets/large.txt")), readFileSync(path.join(template, "assets/large.txt")));
47
+ assert.equal(readlinkSync(path.join(output, "assets/current")), "run.sh");
48
+ assert.equal(statSync(path.join(output, "assets/run.sh")).mode & 0o777, 0o755);
49
+ assert.match(readFileSync(path.join(output, "assets/current"), "utf8"), /echo demo-app/);
50
+ const generated = JSON.parse(readFileSync(path.join(output, ".yss/scaffold-generation.json"))).generated_files;
51
+ assert.deepEqual(generated, ["assets/binary.bin", "assets/current", "assets/large.txt", "assets/run.sh", "micro-config.json", "package.json", "pnpm-lock.yaml"]);
34
52
  const second = spawnSync(process.execPath, [runner, "--contract-file", contractFile, "--template-checkout", template, "--output-dir", output, "--evidence-dir", evidence], { encoding: "utf8", env: { ...process.env, PATH: `${bin}:${process.env.PATH}` } });
35
53
  assert.notEqual(second.status, 0);
36
54
  assert.match(second.stderr, /must not exist or must be empty/);
@@ -22,14 +22,13 @@ description: 编排 YSS 产品或模块从机会调研到 Spec、原型、技术
22
22
  使用 `scripts/query-lifecycle-context` 获取确定性 JSON 投影,不要为普通路由整份读取编排合同:
23
23
 
24
24
  ```bash
25
- scripts/query-lifecycle-context --mode route
26
- scripts/query-lifecycle-context --stage stage.discovery
27
- scripts/query-lifecycle-context --work-unit work-unit.discovery-requirements
28
- scripts/query-lifecycle-context --work-unit work-unit.slice-implementation --include ready_for_agent --include ticket_formalization
25
+ scripts/query-lifecycle-context --mode route --stage stage.plan --work-unit work-unit.plan-requirements --include execution_efficiency
29
26
  ```
30
27
 
31
28
  `--include` 接受编排合同的顶层键;非法模式、ID 或合同键必须失败。查询结果只是带摘要的权威事实投影,不成为新的事实源。解释性细节按需读取 `references/orchestration.md`、`references/state-model.md`、`references/artifact-dependencies.md`、`references/user-decisions.md` 和 `references/matt-yss-adapter.md`,达到最小充分证据后停止扩展上下文。
32
29
 
30
+ 按 `execution_efficiency` 合并查询、复用未变资料并验证当前资产;产品流转不重跑模板套件。词汇对账、恢复核验及门禁不变,执行细节见 `references/orchestration.md`。
31
+
33
32
  ## 入口与模式
34
33
 
35
34
  1. 严格解析 `yss-project.yaml`,不得按目录、Git remote 或占位符猜仓库身份。
@@ -37,6 +36,8 @@ scripts/query-lifecycle-context --work-unit work-unit.slice-implementation --inc
37
36
  3. 模式为 `route`、`orchestrate`、`resume`、`audit`;未明确时使用只读 `route`。具体写入边界从编排合同的 `modes` 子树读取。
38
37
  4. `project-instance` 按生命周期注册表推进;`template-source` 只走模板维护流程。
39
38
 
39
+ Plan 入口读取 `docs/plan/README.md` 和 `docs/process/plan-migration.md`,按注册表的退出条件核查战略输入。关键未决项阻断进入 Spec;非关键细节须有责任人、解决时点和下游接收方。只使用 Plan 标识;旧阶段不提供兼容解析,历史批准不自动成为 Plan 批准。
40
+
40
41
  ## 不可越过的边界
41
42
 
42
43
  ### 仓库身份
@@ -74,6 +75,8 @@ scripts/query-lifecycle-context --work-unit work-unit.slice-implementation --inc
74
75
 
75
76
  先问清用户与结果、通常流程、责任交接、规则与例外、可独立交付的最小范围、证明结果正确的例子和证据。每轮可生成一页“业务方案总览”,只引用权威资产并显示状态、未决问题、责任人和下一步;不复制正文、不成为新的事实源、不新增门禁。
76
77
 
78
+ Plan → Spec(含正式草稿、恢复与显式 `to-spec`)写入前,按 `docs/plan/entry-review.md` 持久化审阅包并运行 `node scripts/verify-plan-spec-entry <state.yaml>`。检查默认 pending,缺项、过期或无真实回复即阻断;独立调研可继续。
79
+
77
80
  ## 结果与暂停
78
81
 
79
82
  输出至少包含:模式、仓库身份、当前阶段和工作单元、影响面、资产与门禁状态、证据、新鲜度、Ticket/垂直切片/合同状态、`ready-for-agent` 结论、阻塞项、本轮动作、下一工作单元、暂停或继续理由、Ticket 同步和 Git checkpoint 判断。
@@ -3,7 +3,7 @@
3
3
  ## 基础依赖
4
4
 
5
5
  ```text
6
- Discovery → Spec → Product Overview / Functional Architecture
6
+ Plan → Spec → Product Overview / Functional Architecture
7
7
  Root CONTEXT.md + current work-unit changes → Context Reconciliation → approval / next work unit
8
8
  Spec + Product Overview → Product Design / Requirement Freeze
9
9
  Spec + Product Overview → API Impact / OpenAPI Draft
@@ -10,7 +10,7 @@ Matt skills 决定如何工作;YSS 生命周期决定是否允许推进;YSS
10
10
  - **直接调用生命周期管理的 Matt user-invoked skill**(`setup-matt-pocock-skills`、`grill-with-docs`、`to-spec`、`to-tickets`、`implement`)时,用户仍是正式资产的创建者;生命周期先校验前置条件,再接受结果并重新计算阶段、门禁和状态。其他 user-invoked skill 同样不得由生命周期自动调用;只有在其专属适配合同存在时才进入 YSS 流程。
11
11
  - **直接调用 `yss-product-lifecycle`** 时,不机械嵌套调用任何 Matt user-invoked skill;编排器直接使用原生工作单元和允许的 model-invoked 原语。Matt user-invoked skill 保持显式兼容入口,生命周期负责准备、校验并验收其结果。
12
12
  - `template-source` 只允许进入模板维护流程。命中 `to-spec`、`to-tickets`、`implement`、Release 或 Retrospective 时返回 `blocked`,原因是 `template-source-product-artifact-forbidden`;`setup-matt-pocock-skills` 不得为具体产品生成 Spec、prototype、OpenAPI 或垂直切片 Ticket。
13
- - `project-instance` 才允许进入产品 Discovery → Spec → 设计 → 契约 → Ticket → 实现 → Release / Retrospective 链路。
13
+ - `project-instance` 才允许进入产品 Plan → Spec → 设计 → 契约 → Ticket → 实现 → Release / Retrospective 链路。
14
14
 
15
15
  | 情形 | Matt flow | 生命周期验收 |
16
16
  |---|---|---|
@@ -66,7 +66,7 @@ blocking_signals: []
66
66
 
67
67
  | Matt flow | 进入条件 | 生命周期结果 |
68
68
  |---|---|---|
69
- | `to-spec`(用户显式) | Discovery work unit 或 `grill-with-docs` 已满足退出条件,且不存在未回流 runnable blocker;用户问题、MVP/非目标、成功标准、测试 seam 和术语审查均有证据 | 生命周期只准备/验收;Spec 初稿为 `ready-for-human`,不等于批准 |
69
+ | `to-spec`(用户显式) | Plan work unit 或 `grill-with-docs` 已满足退出条件,且不存在未回流 runnable blocker;用户问题、MVP/非目标、成功标准、测试 seam 和术语审查均有证据 | 生命周期只准备/验收;Spec 初稿为 `ready-for-human`,不等于批准 |
70
70
  | `to-tickets`(用户显式) | OpenAPI Freeze 或 `no-api-impact` 记录、必要门禁、垂直切片范围和阻塞边均已明确 | 生命周期只准备/验收;只能生成垂直切片 Ticket,初始统一为 `ready-for-human`;必须留下 `ticket_decomposition_result_ref` 和 `vertical_slice_ticket_ref` |
71
71
  | `implement`(用户显式) | `ready-for-agent` 公式、Ticket 正式化结果、垂直切片引用/类型/状态、Contract 已批准/持久化/版本一致、Build Architecture Checklist、实现仓库/分支/CI/验证命令/回滚点,以及后端 Contract(适用时)均满足 | 生命周期只准备/验收;单会话实现同样适用,不得绕过门禁;父 Ticket、`ready-for-human` 切片或跳过 Ticket 正式化的 `next_route` 必须 `blocked` |
72
72
 
@@ -100,7 +100,7 @@ Matt `implement` 的通用提交指令不构成 YSS Git 授权。只有用户明
100
100
 
101
101
  | Matt 产物 | Local 载体 |
102
102
  |---|---|
103
- | Discovery | `docs/.scratch/<feature>/discovery/` |
103
+ | Plan | `docs/.scratch/<feature>/plan/` |
104
104
  | Spec | `docs/.scratch/<feature>/spec.md` |
105
105
  | Spec Delta | `docs/.scratch/<feature>/spec-delta/` |
106
106
  | 功能父 Ticket | `docs/.scratch/<feature>/parent-ticket.md` |