create-yss-spec 2.1.15 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +6 -2
  2. package/package.json +1 -1
  3. package/src/cli.js +276 -15
  4. package/template/.agents/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  5. package/template/.agents/skills/implementation-repo-onboarding/SKILL.md +5 -5
  6. package/template/.agents/skills/llm-wiki/SKILL.md +1 -1
  7. package/template/.agents/skills/llm-wiki/references/discover.md +1 -0
  8. package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  9. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  10. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  11. package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  12. package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  13. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  14. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  15. package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +1 -1
  16. package/template/.agents/skills/yss-router/SKILL.md +1 -1
  17. package/template/.agents/skills/yss-router/references/router-contract.yaml +20 -0
  18. package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +1 -1
  19. package/template/.claude/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  20. package/template/.claude/skills/implementation-repo-onboarding/SKILL.md +5 -5
  21. package/template/.claude/skills/llm-wiki/SKILL.md +1 -1
  22. package/template/.claude/skills/llm-wiki/references/discover.md +1 -0
  23. package/template/.claude/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  24. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  25. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  26. package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  27. package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  28. package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  29. package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  30. package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +1 -1
  31. package/template/.claude/skills/yss-router/SKILL.md +1 -1
  32. package/template/.claude/skills/yss-router/references/router-contract.yaml +20 -0
  33. package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +1 -1
  34. package/template/.codex/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  35. package/template/.codex/skills/implementation-repo-onboarding/SKILL.md +5 -5
  36. package/template/.codex/skills/llm-wiki/SKILL.md +1 -1
  37. package/template/.codex/skills/llm-wiki/references/discover.md +1 -0
  38. package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  39. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  40. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  41. package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  42. package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  43. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  44. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  45. package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +1 -1
  46. package/template/.codex/skills/yss-router/SKILL.md +1 -1
  47. package/template/.codex/skills/yss-router/references/router-contract.yaml +20 -0
  48. package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +1 -1
  49. package/template/.cursor/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  50. package/template/.cursor/skills/implementation-repo-onboarding/SKILL.md +5 -5
  51. package/template/.cursor/skills/llm-wiki/SKILL.md +1 -1
  52. package/template/.cursor/skills/llm-wiki/references/discover.md +1 -0
  53. package/template/.cursor/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  54. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  55. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  56. package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  57. package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  58. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  59. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  60. package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +1 -1
  61. package/template/.cursor/skills/yss-router/SKILL.md +1 -1
  62. package/template/.cursor/skills/yss-router/references/router-contract.yaml +20 -0
  63. package/template/.cursor/skills/yss-router/references/slice-implementation-contract.md +1 -1
  64. package/template/.hermes/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  65. package/template/.hermes/skills/implementation-repo-onboarding/SKILL.md +5 -5
  66. package/template/.hermes/skills/llm-wiki/SKILL.md +1 -1
  67. package/template/.hermes/skills/llm-wiki/references/discover.md +1 -0
  68. package/template/.hermes/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  69. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  70. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  71. package/template/.hermes/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  72. package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  73. package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  74. package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  75. package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +1 -1
  76. package/template/.hermes/skills/yss-router/SKILL.md +1 -1
  77. package/template/.hermes/skills/yss-router/references/router-contract.yaml +20 -0
  78. package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +1 -1
  79. package/template/.pi/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  80. package/template/.pi/skills/implementation-repo-onboarding/SKILL.md +5 -5
  81. package/template/.pi/skills/llm-wiki/SKILL.md +1 -1
  82. package/template/.pi/skills/llm-wiki/references/discover.md +1 -0
  83. package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  84. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  85. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  86. package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  87. package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  88. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  89. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  90. package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +1 -1
  91. package/template/.pi/skills/yss-router/SKILL.md +1 -1
  92. package/template/.pi/skills/yss-router/references/router-contract.yaml +20 -0
  93. package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +1 -1
  94. package/template/.qoder/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  95. package/template/.qoder/skills/implementation-repo-onboarding/SKILL.md +5 -5
  96. package/template/.qoder/skills/llm-wiki/SKILL.md +1 -1
  97. package/template/.qoder/skills/llm-wiki/references/discover.md +1 -0
  98. package/template/.qoder/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  99. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  100. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  101. package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  102. package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  103. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  104. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  105. package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +1 -1
  106. package/template/.qoder/skills/yss-router/SKILL.md +1 -1
  107. package/template/.qoder/skills/yss-router/references/router-contract.yaml +20 -0
  108. package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +1 -1
  109. package/template/.trae/skills/cross-repo-implementation-routing/SKILL.md +2 -2
  110. package/template/.trae/skills/implementation-repo-onboarding/SKILL.md +5 -5
  111. package/template/.trae/skills/llm-wiki/SKILL.md +1 -1
  112. package/template/.trae/skills/llm-wiki/references/discover.md +1 -0
  113. package/template/.trae/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
  114. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
  115. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
  116. package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
  117. package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
  118. package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
  119. package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +2 -0
  120. package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +1 -1
  121. package/template/.trae/skills/yss-router/SKILL.md +1 -1
  122. package/template/.trae/skills/yss-router/references/router-contract.yaml +20 -0
  123. package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +1 -1
  124. package/template/AGENTS.md +5 -5
  125. package/template/CONTEXT.md +3 -2
  126. package/template/docs/process/implementation-repo-integration.md +24 -2
  127. package/template/docs/templates/cross-repo-slice-template.md +15 -0
  128. package/template/docs/templates/implementation-repo-registry-template.md +8 -3
  129. package/template/docs/templates/implementation-routing-template.md +3 -3
  130. package/template/docs/user-guide//344/272/247/345/223/201/347/224/237/345/221/275/345/221/250/346/234/237/345/267/245/344/275/234/346/265/201.md +2 -2
  131. package/template/scripts/lib/git-submodule-fixtures.mjs +58 -0
  132. package/template/scripts/lib/repository-scope-policy.mjs +456 -0
  133. package/template/scripts/lib/scenario-checks.mjs +11 -1
  134. package/template/scripts/repository-scope-policy +16 -0
  135. package/template/scripts/verify-repository-scope-scenarios +239 -0
  136. package/template/scripts/verify-scaffold-generator-scenarios +54 -0
  137. package/template/scripts/verify-template +3 -2
  138. package/template/skills-lock.json +7 -7
  139. package/template.manifest.json +6 -5
  140. package/template.snapshot.json +4 -4
  141. package/template/.cursor/environment.json +0 -5
  142. package/template/.github/workflows/template-node-tooling.yml +0 -61
  143. package/template/wiki/.wiki-manifest.json +0 -390
  144. package/template/wiki/raw/AGENTS.md +0 -122
  145. package/template/wiki/raw/CONTEXT.md +0 -80
  146. package/template/wiki/raw/README.md +0 -130
  147. package/template/wiki/raw/adr-0002-repository-mode.md +0 -3
  148. package/template/wiki/raw/create-yss-spec-repository-mode-contract.md +0 -95
  149. package/template/wiki/raw/harness-process-tailoring.md +0 -64
  150. package/template/wiki/raw/implementation-repo-integration.md +0 -62
  151. package/template/wiki/raw/issue-tracker.md +0 -135
  152. package/template/wiki/raw/lifecycle-registry.yaml +0 -369
  153. package/template/wiki/raw/maintenance-intensity.yaml +0 -25
  154. package/template/wiki/raw/skills-lock-names.md +0 -93
  155. package/template/wiki/raw/skills-maintenance.md +0 -90
  156. package/template/wiki/raw/spec-template.md +0 -102
  157. package/template/wiki/raw/triage-labels.md +0 -15
  158. package/template/wiki/raw/vertical-slice-ticket-template.md +0 -132
  159. package/template/wiki/raw/yss-project.yaml +0 -2
  160. package/template/wiki/raw/yss-skill-registry.yaml +0 -510
  161. package/template/wiki/wiki/Agent/345/205/245/345/217/243/350/247/204/345/210/231.md +0 -21
  162. package/template/wiki/wiki/CLAUDE.md +0 -28
  163. package/template/wiki/wiki/Fresh/351/252/214/350/257/201/344/270/216/347/213/254/347/253/213/345/256/241/346/237/245.md +0 -19
  164. package/template/wiki/wiki/LLM Wiki.md +0 -30
  165. package/template/wiki/wiki/Matt/346/212/200/350/203/275/344/275/223/347/263/273.md +0 -21
  166. package/template/wiki/wiki/OpenAPI/345/245/221/347/272/246.md +0 -20
  167. package/template/wiki/wiki/SpecDelta.md +0 -19
  168. package/template/wiki/wiki/Spec/345/237/272/347/272/277.md +0 -22
  169. package/template/wiki/wiki/Ticket/344/270/216/346/265/201/347/250/213/347/212/266/346/200/201.md +0 -21
  170. package/template/wiki/wiki/YSS/345/267/245/347/250/213/346/212/200/350/203/275/344/275/223/347/263/273.md +0 -23
  171. package/template/wiki/wiki/YSS/350/267/257/347/224/261/344/270/216/345/220/210/345/220/214/347/274/226/350/257/221.md +0 -20
  172. package/template/wiki/wiki/concept-table.md +0 -9
  173. package/template/wiki/wiki/index.md +0 -41
  174. package/template/wiki/wiki/log.md +0 -51
  175. package/template/wiki/wiki//344/272/247/345/223/201/347/240/224/345/217/221/347/224/237/345/221/275/345/221/250/346/234/237.md +0 -20
  176. package/template/wiki/wiki//344/272/247/345/223/201/350/256/276/350/256/241/345/275/261/345/223/215/344/270/216/345/216/237/345/236/213.md +0 -19
  177. package/template/wiki/wiki//344/273/223/345/272/223/350/272/253/344/273/275/344/270/216/350/267/257/347/224/261.md +0 -20
  178. package/template/wiki/wiki//345/210/207/347/211/207/345/256/236/347/216/260/345/220/210/345/220/214.md +0 -21
  179. package/template/wiki/wiki//345/236/202/347/233/264/345/210/207/347/211/207Ticket.md +0 -23
  180. package/template/wiki/wiki//345/244/215/347/233/230/344/270/216/346/235/203/345/250/201/350/265/204/344/272/247/344/277/256/350/256/242.md +0 -20
  181. package/template/wiki/wiki//345/256/236/347/216/260/344/273/223/345/272/223/344/270/216/350/267/250/344/273/223/345/272/223/345/245/221/347/272/246.md +0 -22
  182. package/template/wiki/wiki//345/275/261/345/223/215/351/235/242/345/210/206/350/257/212/344/270/216/346/265/201/347/250/213/350/243/201/345/211/252.md +0 -20
  183. package/template/wiki/wiki//346/212/200/350/203/275/346/212/225/345/275/261/344/270/216/351/224/201/345/256/232.md +0 -23
  184. package/template/wiki/wiki//346/235/241/344/273/266/345/274/272/345/210/266/351/227/250/347/246/201.md +0 -18
  185. package/template/wiki/wiki//346/250/241/346/235/277/345/217/221/345/270/203/351/227/250/347/246/201/344/270/216/351/252/214/350/257/201.md +0 -22
  186. package/template/wiki/wiki//346/250/241/346/235/277/346/200/273/350/247/210.md +0 -20
  187. package/template/wiki/wiki//346/250/241/346/235/277/347/273/264/346/212/244/346/265/201/347/250/213.md +0 -19
@@ -1,11 +1,14 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { execFile } from "node:child_process";
3
3
  import { chmod, mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
7
  import test from "node:test";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { run } from "./run_scaffold_verification.mjs";
10
+ import { makeGitlinkFixture } from "../../../../scripts/lib/git-submodule-fixtures.mjs";
11
+ import { GITLINK_MODE, gitLsFilesStage } from "../../../../scripts/lib/repository-scope-policy.mjs";
9
12
 
10
13
  const scripts = path.dirname(fileURLToPath(import.meta.url));
11
14
  const generator = path.join(scripts, "generate_scaffold.mjs");
@@ -21,6 +24,55 @@ test("拒绝不一致合同和未经明确批准的非空目录覆盖", async (t
21
24
 
22
25
  test("已批准的 --force 先备份非空目标,再提交 staging 工程", async (t) => { const data = await fixture(); t.after(() => rm(data.root, { recursive: true, force: true })); const oldProject = path.join(data.output, "demo-service"); await mkdir(oldProject); await writeFile(path.join(oldProject, "old.txt"), "recoverable"); const result = await command([...data.args, "--force", "--overwrite-scope", "replace-service", "--rollback-ref", "checkpoint-1"]); assert.equal(result.code, 0, result.stderr); const entries = await readdir(data.output); const backup = entries.find((entry) => entry.startsWith(".demo-service.backup-")); assert.ok(backup); assert.equal(await readFile(path.join(data.output, backup, "old.txt"), "utf8"), "recoverable"); assert.ok((await readdir(path.join(data.output, "demo-service"))).includes("pom.xml")); });
23
26
 
27
+ test("拒绝覆盖 .gitmodules 登记的 git-submodule 挂载点", async (t) => {
28
+ const data = await fixture();
29
+ t.after(() => rm(data.root, { recursive: true, force: true }));
30
+ const superproject = path.join(data.root, "harness");
31
+ const output = path.join(superproject, "apps/backend");
32
+ await mkdir(path.join(output, "demo-service"), { recursive: true });
33
+ await writeFile(path.join(superproject, ".gitmodules"), "[submodule \"demo-service\"]\n\tpath = apps/backend/demo-service\n\turl = https://example.invalid/demo-service.git\n");
34
+ const contractFile = path.join(data.root, "gitlink-contract.json");
35
+ await writeFile(contractFile, `${JSON.stringify(contract(output), null, 2)}\n`);
36
+ const result = await command(["--project-name", "demo-service", "--base-package", "com.yss.demo", "--output-dir", output, "--database", "mysql", "--contract-id", "scaffold-1", "--contract-version", "1", "--approval-ref", "approval-1", "--router-draft-ref", "router-1", "--persisted-ref", "persisted-1", "--contract-file", contractFile, "--force", "--overwrite-scope", "replace-service", "--rollback-ref", "checkpoint-1"]);
37
+ assert.equal(result.code, 1, result.stderr);
38
+ assert.match(`${result.stdout}\n${result.stderr}`, /gitlink 不得由脚手架覆盖/);
39
+ });
40
+
41
+ test("拒绝在 detached HEAD 子仓工作树内当成普通目录生成", async (t) => {
42
+ const data = await fixture();
43
+ const detached = makeGitlinkFixture({ checkout: "detached-head" });
44
+ t.after(() => rm(data.root, { recursive: true, force: true }));
45
+ t.after(() => detached.cleanup());
46
+ const output = path.join(detached.superproject, detached.mount);
47
+ const contractFile = path.join(data.root, "detached-contract.json");
48
+ await writeFile(contractFile, `${JSON.stringify(contract(output), null, 2)}\n`);
49
+ const result = await command(["--project-name", "nested-service", "--base-package", "com.yss.demo", "--output-dir", output, "--database", "mysql", "--contract-id", "scaffold-1", "--contract-version", "1", "--approval-ref", "approval-1", "--router-draft-ref", "router-1", "--persisted-ref", "persisted-1", "--contract-file", contractFile]);
50
+ const text = `${result.stdout}\n${result.stderr}`;
51
+ assert.equal(result.code, 1, text);
52
+ assert.doesNotMatch(text, /请显式传入 --force/);
53
+ assert.match(text, /detached HEAD 不得当成普通目录写入/);
54
+ assert.equal(existsSync(path.join(output, "nested-service", "pom.xml")), false);
55
+ assert.equal((await readdir(output)).some((name) => name.includes("staging") || name === "nested-service"), false);
56
+ });
57
+
58
+ test("--force 覆盖真实 gitlink 不得走普通目录覆盖路径", async (t) => {
59
+ const data = await fixture();
60
+ const empty = makeGitlinkFixture({ checkout: "empty-gitlink" });
61
+ t.after(() => rm(data.root, { recursive: true, force: true }));
62
+ t.after(() => empty.cleanup());
63
+ const output = path.join(empty.superproject, "apps/backend");
64
+ const contractFile = path.join(data.root, "empty-gitlink-contract.json");
65
+ await writeFile(contractFile, `${JSON.stringify(contract(output), null, 2)}\n`);
66
+ const result = await command(["--project-name", "billing-service", "--base-package", "com.yss.demo", "--output-dir", output, "--database", "mysql", "--contract-id", "scaffold-1", "--contract-version", "1", "--approval-ref", "approval-1", "--router-draft-ref", "router-1", "--persisted-ref", "persisted-1", "--contract-file", contractFile, "--force", "--overwrite-scope", "replace-service", "--rollback-ref", "checkpoint-1"]);
67
+ const text = `${result.stdout}\n${result.stderr}`;
68
+ assert.equal(result.code, 1, text);
69
+ assert.doesNotMatch(text, /请显式传入 --force/);
70
+ assert.match(text, /--force 不得把 git-submodule 挂载点当成普通目录覆盖|gitlink 不得/);
71
+ assert.equal(existsSync(path.join(output, "billing-service", "pom.xml")), false);
72
+ assert.equal((await readdir(output)).some((name) => name.includes("backup")), false);
73
+ assert.equal(gitLsFilesStage(empty.superproject, empty.mount)?.mode, GITLINK_MODE);
74
+ });
75
+
24
76
  test("验证器记录三条 wrapper 命令的成功和失败证据", async (t) => { const root = await mkdtemp(path.join(os.tmpdir(), "yss-scaffold-verifier-")); t.after(() => rm(root, { recursive: true, force: true })); const project = path.join(root, "project"); await mkdir(path.join(project, ".yss"), { recursive: true }); await writeFile(path.join(project, ".yss/scaffold-generation.json"), `${JSON.stringify({ schema_version: 1, contract_id: "id", contract_version: 1, slice_id: "slice", approval_ref: "approval", approver: "reviewer", lifecycle_approval_ref: "approval", router_draft_ref: "router", persisted_ref: "persisted", contract_file_ref: "contract", current_version: 1, allowed_write_paths: ["."], expected_evidence_files: ["manifest"], verification_commands: ["./mvnw validate", "./mvnw test", "./mvnw package"], generation_mode: "controlled-generation" })}\n`); const wrapper = path.join(project, "mvnw"); await writeFile(wrapper, "#!/bin/sh\n[ \"$1\" = test ] && exit 2\nprintf 'ran %s\\n' \"$1\"\n"); await chmod(wrapper, 0o755); const evidence = path.join(root, "evidence"); const report = await run(project, evidence); assert.equal(report.status, "failed"); assert.deepEqual(report.commands.map((item) => item.exit_code), [0, 2, 0]); assert.match(await readFile(path.join(evidence, "mvnw-test.stderr.log"), "utf8"), /^$/); });
25
77
 
26
78
  test("验证器在全部 Maven 命令成功时通过并保留日志引用", async (t) => { const root = await mkdtemp(path.join(os.tmpdir(), "yss-scaffold-verifier-success-")); t.after(() => rm(root, { recursive: true, force: true })); const project = path.join(root, "project"); await mkdir(path.join(project, ".yss"), { recursive: true }); await writeFile(path.join(project, ".yss/scaffold-generation.json"), `${JSON.stringify({ schema_version: 1, contract_id: "id", contract_version: 1, slice_id: "slice", approval_ref: "approval", approver: "reviewer", lifecycle_approval_ref: "approval", router_draft_ref: "router", persisted_ref: "persisted", contract_file_ref: "contract", current_version: 1, allowed_write_paths: ["."], expected_evidence_files: ["manifest"], verification_commands: ["./mvnw validate", "./mvnw test", "./mvnw package"], generation_mode: "controlled-generation" })}\n`); const wrapper = path.join(project, "mvnw"); await writeFile(wrapper, "#!/bin/sh\nprintf 'ran %s\\n' \"$1\"\n"); await chmod(wrapper, 0o755); const report = await run(project, path.join(root, "evidence")); assert.equal(report.status, "passed"); assert.ok(report.commands.every((item) => item.exit_code === 0 && item.stdout_ref.endsWith(".stdout.log"))); });
@@ -31,7 +31,7 @@ branch: template
31
31
  ## Workflow
32
32
 
33
33
  1. 确认当前任务已经通过 Harness 入口分诊,且实现位置已记录在 proposal、design、build entry review、实施计划或实现路由记录中。
34
- 2. 确认目标是外部实现仓库;只有用户明确选择时才输出到 Harness 仓库的 `apps/frontend/<project>/`。`apps/frontend/` 只能作为项目容器,`app/frontend/`、`app/backend/` 及其子路径禁止作为输出位置。
34
+ 2. 确认目标是外部实现仓库;只有用户明确选择时才输出到 Harness 仓库的 `apps/frontend/<project>/`。`apps/frontend/` 只能作为项目容器,`app/frontend/`、`app/backend/` 及其子路径禁止作为输出位置。`git-submodule` 只能在已初始化且附加分支的子仓工作树生成;空 gitlink、detached HEAD、`--force` 覆盖挂载点不得当成普通目录。
35
35
  3. 只读检查模板分支是否可访问:`git ls-remote --heads <repo> template`。
36
36
  4. 需要生成工程时,克隆或复制模板到用户确认的目标位置;不得默认写入 Harness 仓库。
37
37
  5. 替换应用名、微应用名、路由、`micro-config.json`、环境变量和 README 中的模板占位。
@@ -78,7 +78,7 @@ Router 状态映射为:`draft → completed`、`blocked → blocked`、`ready-
78
78
 
79
79
  YSS 调用 `code-review` 前必须形成 review input,至少包含 `review_mode`、`review_base_ref`、`implementation_candidate_ref`、`candidate_snapshot_ref`、`candidate_digest`、`spec_ref`、`ticket_ref`、`slice_contract_ref`、`build_architecture_checklist_ref` 和 `yss_execution_result_refs`,并满足 `orchestration-contract.yaml.review_input.manifest_required_by_mode`。`committed` 审查 merge-base 到不可变 `HEAD`;`worktree` 一次捕获 merge-base 到 working tree 的 committed、staged、unstaged 和 untracked 内容,使用 `yss-worktree-candidate-v1` 规定的 raw path、uint64 big-endian 长度、tracked/untracked record 和不支持条目阻断规则计算 SHA-256,让两个 Reviewer 消费同一不可变快照,并在返回和完成 checkpoint 复核摘要未变化。缺少输入、候选为空或摘要变化时返回 `blocked`,不能缩小审查范围或合并不同候选的结论。
80
80
 
81
- Matt `implement` 的通用提交指令不构成 YSS Git 授权。只有用户明确给出 `commit_authorized` 为 `true`、非空 `commit_scope` 和 `commit_authorization_ref` 时才能 commit;只有明确给出 `push_authorized` 为 `true`、非空 `push_scope` 和 `push_authorization_ref` 时才能 push。缺少任一字段时保持工作区不变,只输出 checkpoint 判断;不得把 `orchestrate`、实现授权、当前分支、测试通过或负责人要求解释为隐含授权。
81
+ Matt `implement` 的通用提交指令不构成 YSS Git 授权。只有用户明确给出 `commit_authorized` 为 `true`、非空 `commit_scope` 和 `commit_authorization_ref` 时才能 commit;只有明确给出 `push_authorized` 为 `true`、非空 `push_scope` 和 `push_authorization_ref` 时才能 push。缺少任一字段时保持工作区不变,只输出 checkpoint 判断;不得把 `orchestrate`、实现授权、当前分支、测试通过或负责人要求解释为隐含授权。`git-submodule` 还必须按仓授权、禁止 detached HEAD 提交,并先推子仓再更新父仓 gitlink。
82
82
 
83
83
  “然后 commit”“做完提交”“可以帮我提交”等自然语言意向本身不是结构化授权。编排器必须取得上述三个 commit 字段;不能先把意向解释成授权,再在完成时补 scope 或引用。
84
84
 
@@ -9,6 +9,10 @@ implementation_path_policy:
9
9
  forbidden_roots: [app/backend/, app/frontend/]
10
10
  container_roots_are_projects: false
11
11
  external_repository_uses_native_root: true
12
+ policies: [harness-apps-multi-project, external-repository-native, git-submodule-harness-apps]
13
+ git_submodule:
14
+ repository_scope: git-submodule
15
+ layout_policy: git-submodule-harness-apps
12
16
 
13
17
  review_verification:
14
18
  low_medium_combined_independent_executor: true
@@ -260,6 +264,26 @@ git_authorization:
260
264
  authorized_value: true
261
265
  non_empty: [push_scope, push_authorization_ref]
262
266
  unauthorized_action: checkpoint-only
267
+ git_submodule:
268
+ applies_when: repository_scope=git-submodule
269
+ forbid_commit_on_detached_head: true
270
+ commit_order: [submodule-repositories, superproject-gitlink]
271
+ push_order: [submodule-repositories, superproject-gitlink]
272
+ push_recurse_submodules: check
273
+ empty_gitlink_scaffold: blocked
274
+ treat_empty_gitlink_as_regular_dir: blocked
275
+ treat_detached_head_as_regular_dir: blocked
276
+ force_overlay_mount: blocked
277
+ write_inside_detached_head: blocked
278
+ require_git_entry_mode: "160000"
279
+ working_tree_declared_scope_must_match: true
280
+ copy_source_into_harness: forbidden
281
+ clone_requires_recurse_submodules: true
282
+ delivery_order_must_include: superproject-gitlink-update
283
+ nested_authorization: per-repository
284
+ inspect_working_tree_writable: explicit-boolean
285
+ empty_gitlink_writable: false
286
+ force_overlay_regular_dir_path: blocked
263
287
 
264
288
  seam_deferred_required: [risk, owner, follow_up_ticket, verification_plan, target_version_or_release_date]
265
289
 
@@ -69,6 +69,8 @@ tracker 选择和冲突按 `docs/agents/issue-tracker.md` 裁决:已持久化
69
69
 
70
70
  实现授权、`orchestrate`/`resume` 的有界写入、当前分支和 Git checkpoint 都不蕴含 commit 或 push 授权。执行 commit 前必须同时取得 `commit_authorized=true`、非空 `commit_scope` 和 `commit_authorization_ref`;执行 push 前必须同时取得 `push_authorized=true`、非空 `push_scope` 和 `push_authorization_ref`。任一缺失时只记录 checkpoint 判断并保持 Git 状态不变;负责人要求、时间压力、测试通过或“本地 commit 可逆”都不能补足用户授权。
71
71
 
72
+ `repository_scope: git-submodule` 时授权按仓分别计算:禁止在 detached HEAD 提交;commit / push 顺序必须先子仓、再父仓 gitlink(`superproject-gitlink-update`);父仓 push 使用 `git push --recurse-submodules=check`。空 gitlink、detached HEAD、`--force` 覆盖挂载点不得当成普通目录脚手架,也不得把实现源码复制进 Harness。登记字段必须能与 `harness-apps` / `external-repository` 区分,并对照工作树 gitlink。
73
+
72
74
  ## 必须暂停
73
75
 
74
76
  - Spec baseline、需求冻结、原型确认、OpenAPI Freeze 或 Architecture Review 等普通门禁等待人工裁决。
@@ -59,7 +59,7 @@ AND UI 影响切片的前端实现还原计划已通过 schema 校验、`templat
59
59
 
60
60
  进入代码审查时保存 `review_mode`、`review_base_ref`、`implementation_candidate_ref`、`candidate_snapshot_ref`、`candidate_digest` 及 Spec、Ticket、合同、Checklist、YSS Execution Result 引用。`worktree` 候选必须一次捕获 committed、staged、unstaged 和 untracked 文件;manifest 的按模式必填字段以及 `yss-worktree-candidate-v1`(raw path、uint64 big-endian 长度、tracked/untracked record、symlink 和不支持条目)以 `orchestration-contract.yaml.review_input` 为唯一执行定义。两个 Reviewer 消费同一不可变快照;返回后或完成 checkpoint 摘要变化则返回 `blocked` 并重新审查。不新增生命周期状态,只把该清单作为审查证据。
61
61
 
62
- Git 动作分别保存 `commit_authorized`、`commit_scope`、`commit_authorization_ref`、`push_authorized`、`push_scope`、`push_authorization_ref`。只有授权值严格为 `true`、范围和用户授权引用均非空时才执行相应动作;缺失授权时保持工作区不变并记录 checkpoint 判断。
62
+ Git 动作分别保存 `commit_authorized`、`commit_scope`、`commit_authorization_ref`、`push_authorized`、`push_scope`、`push_authorization_ref`。只有授权值严格为 `true`、范围和用户授权引用均非空时才执行相应动作;缺失授权时保持工作区不变并记录 checkpoint 判断。`git-submodule` 另保存每仓授权、`checkout_state` 和先子后父顺序;空 gitlink、detached HEAD 或 `--force` 覆盖挂载点时不得当成普通目录 commit / 脚手架。
63
63
 
64
64
  ## 状态块
65
65
 
@@ -29,7 +29,7 @@ description: Use when a YSS vertical slice is entering implementation, spans mul
29
29
  - Repository/数据模型影响缺少数据架构时,不得路由持久化实现。
30
30
  - API 变化必须回到生命周期 Draft/Review/Freeze;半成品 backend 不得冒充稳定 source of truth。
31
31
  - 后端端到端切片必须包含 Application;对象/POJO 影响按契约自动补 `mapstruct`、`lombok`、`alibaba-java-code-style`。
32
- - Harness 内实现路径必须落在 `apps/backend/<project>/` 或 `apps/frontend/<project>/` 的具体项目目录;`apps/backend/`、`apps/frontend/` 只能作为容器,`app/backend/`、`app/frontend/` 及其子路径一律阻断。外部实现仓库使用其登记的真实项目根路径。
32
+ - Harness 内实现路径必须落在 `apps/backend/<project>/` 或 `apps/frontend/<project>/` 的具体项目目录;`apps/backend/`、`apps/frontend/` 只能作为容器,`app/backend/`、`app/frontend/` 及其子路径一律阻断。外部实现仓库使用其登记的真实项目根路径。`git-submodule` 使用 `implementation_path_policy: git-submodule-harness-apps`,空 gitlink、detached HEAD 或 `--force` 覆盖挂载点不得脚手架;`inspectWorkingTreeScope.writable` 必须为显式布尔值。
33
33
  - 当前用户、审计事件、普通技术日志、请求校验、错误映射或加解密命中时,必须按 `router-contract.yaml` 的 `component_impact_routing` 补齐长尾 skill;不能只在 `boundaries.md` 中提及。仅复用已经验证的平台认证 / 授权能力不算 component impact,不自动增加权限专项 skill。
34
34
  - 业务行为使用 `behavior-tdd`;只有机械脚手架/生成物可用 `controlled-generation`,并记录例外和验证。
35
35
  - 原型确认后若 backend `scaffold_status=required`,先由本 Router 按 `scaffold_contract_schema` 编译 `yss-ddd-scaffold-generator` 的 `controlled-generation` 工作单元合同 draft;合同必须带 `contract_id`、`contract_version`、Router draft、生命周期批准、持久化引用、允许写路径、预期证据和验证命令。经生命周期编排器批准并持久化后才能运行生成器,再由受控工作单元实际执行固定的 `./mvnw validate`、`./mvnw test`、`./mvnw package` 并记录逐条结果,随后加载 `yss-backend-scaffold-parent` 并重新编译业务合同;脚手架不承载业务行为。
@@ -15,10 +15,30 @@ implementation_path_policy:
15
15
  forbidden_roots:
16
16
  - app/backend/
17
17
  - app/frontend/
18
+ policies:
19
+ - harness-apps-multi-project
20
+ - external-repository-native
21
+ - git-submodule-harness-apps
22
+ git_submodule:
23
+ repository_scope: git-submodule
24
+ layout_policy: git-submodule-harness-apps
25
+ git_entry_mode: "160000"
26
+ empty_gitlink_scaffold: blocked
27
+ treat_empty_gitlink_as_regular_dir: blocked
28
+ treat_detached_head_as_regular_dir: blocked
29
+ force_overlay_mount: blocked
30
+ write_inside_detached_head: blocked
31
+ copy_source_into_harness: forbidden
32
+ inspect_working_tree_writable: explicit-boolean
33
+ empty_gitlink_writable: false
34
+ force_overlay_regular_dir_path: blocked
18
35
  rules:
19
36
  - "Harness 内每个运行时工程必须位于具体 project 目录,不能直接写入 apps/backend/ 或 apps/frontend/。"
20
37
  - "app/backend/、app/frontend/ 及其子路径不是兼容别名,必须阻断。"
21
38
  - "外部实现仓库使用登记的真实项目根路径,不用 Harness 容器路径冒充。"
39
+ - "git-submodule 挂载路径使用 git-submodule-harness-apps,必须是 gitlink 且分别登记;不得登记为 harness-apps。"
40
+ - "空 gitlink、detached HEAD、--force 覆盖挂载点不得当成普通目录;也不得在 detached HEAD 工作树内当普通目录写入。"
41
+ - "inspectWorkingTreeScope 必须返回 { writable };空 gitlink / detached HEAD 的 writable 必为 false;--force 覆盖 gitlink 不得走普通目录覆盖路径。"
22
42
  subcontract_statuses: [required, not-applicable]
23
43
 
24
44
  readiness:
@@ -37,7 +37,7 @@ slice_contract:
37
37
  reason:
38
38
  common:
39
39
  impacted_areas: []
40
- implementation_path_policy: harness-apps-multi-project # or external-repository-native
40
+ implementation_path_policy: harness-apps-multi-project # or external-repository-native | git-submodule-harness-apps
41
41
  project_roots: []
42
42
  required_skills: []
43
43
  optional_skills: []
@@ -83,15 +83,15 @@ README、用户指南、根目录 `CLAUDE.md` 和其他说明文档只引用或
83
83
  | merge / rebase 冲突 | `resolving-merge-conflicts` |
84
84
  | 架构治理、难测模块或深模块设计 | `improve-codebase-architecture` / `codebase-design` |
85
85
  | 跨线程、跨仓库、上下文过长或原型结论回流 | `handoff` 或等价交接记录 |
86
- | 本地知识库 init / refresh / rebuild,或要把研究结果落成持久 wiki | `llm-wiki`(落成持久 wiki 用 `ingest`;已映射 live 源变了用 `refresh`) |
86
+ | 本地知识库 init / refresh / rebuild,或要把研究结果落成持久 wiki | `llm-wiki`(落成持久 wiki 用 `ingest`;已映射 live 源变了用 `refresh`)。`template-source` 的 wiki-root 为 `.template-source/wiki`;`project-instance` 不附带源仓库编译树,需要时在仓库根 `wiki/` 执行 `init` |
87
87
 
88
88
  业务行为默认按 `tdd` 使用已确认的公开 seam 逐切片实现。一次性生成、纯配置或流程文档不适用代码 TDD 时,必须记录例外理由和可执行验证方式。
89
89
 
90
90
  ## 9. 工作区与实现仓库边界
91
91
 
92
- 当前仓库默认是研发管理仓库,运行时代码优先位于已登记的独立实现仓库。只有用户明确选择当前仓库承载实现代码时,才使用唯一的 `apps/backend/<project>/` 或 `apps/frontend/<project>/` 项目根。
92
+ 当前仓库默认是研发管理仓库,运行时代码优先位于已登记的独立实现仓库(`repository_scope: external-repository`)。只有用户明确选择当前仓库承载实现代码时,才使用同源的 `apps/backend/<project>/` 或 `apps/frontend/<project>/`(`harness-apps`),或以 Git submodule 把独立实现仓挂到同一 `apps/` 布局(`git-submodule`)。三种 scope 必须用登记字段、Git 身份和工作树 gitlink 区分;空 gitlink、detached HEAD 或 `--force` 覆盖挂载点不得当成普通目录。
93
93
 
94
- `apps/backend/` 和 `apps/frontend/` 只是项目容器;`app/backend/`、`app/frontend/` 及其子路径禁止作为工程输出。完整登记字段和跨仓约束见 `docs/process/implementation-repo-integration.md`。
94
+ `apps/backend/` 和 `apps/frontend/` 只是项目容器;`app/backend/`、`app/frontend/` 及其子路径禁止作为工程输出。`git-submodule` 不得登记为 `harness-apps`,也不得把实现源码复制进 Harness。完整登记字段、嵌套 Git 授权和跨仓约束见 `docs/process/implementation-repo-integration.md`。
95
95
 
96
96
  ## 10. 独立审查、验证和追踪
97
97
 
@@ -113,8 +113,8 @@ README、用户指南、根目录 `CLAUDE.md` 和其他说明文档只引用或
113
113
 
114
114
  本仓库是 `template-source` Harness / 研发管理仓库,没有前端 / 后端运行时应用;可运行、可测试的表面只有 Node 工具链与治理校验脚本。
115
115
 
116
- - 依赖与工具链:Node `>=22 <27`(`.nvmrc` 固定 22);`pnpm` 通过 `packageManager` 字段由 corepack 自动切换到 `10.15.0`,无需手工切换。Node 依赖只装在 `.template-source/tooling/node`,仓库根没有 `package.json`。
117
- - 测试:`pnpm --dir .template-source/tooling/node test`(`node --test`,共 15 个用例)。
116
+ - 依赖与工具链:Node `>=22 <27`(`.nvmrc` 固定 22);`pnpm` 通过 `packageManager` 字段由 corepack 自动切换到 `10.15.0`,无需手工切换。Node 依赖只装在 `.template-source/tooling/node`,仓库根没有 `package.json`。本仓 wiki-root 为 `.template-source/wiki`。
117
+ - 测试:`pnpm --dir .template-source/tooling/node test`(`node --test`,共 16 个用例)。
118
118
  - 构建 / lint:`pnpm --dir .template-source/tooling/node build:vendor` 与 `check:vendor` 维护 `scripts/vendor/*.mjs`;顶层 lint 是 `verify-template` 内对所有脚本执行的 `node --check`。
119
119
  - 完整发布门禁(相当于"运行应用"):`scripts/verify-template`,成功输出 `模板发布校验通过`。它串联证据索引、`pnpm test`、`check:vendor` 和全部 `scripts/verify-*` 场景校验。
120
120
  - 非显然的坑:`scripts/verify-lifecycle-registry`(及其对应测试 `Node lifecycle registry verifier ...`)会 shell out 到 `python3` 并 `import jsonschema` 做 JSON Schema 校验。缺少该 Python 模块时测试会以 `ModuleNotFoundError: No module named 'jsonschema'` 失败,而不是代码问题;update script 已负责 `pip3 install jsonschema`。
@@ -68,11 +68,12 @@
68
68
  | 生态发布清单 | 关联模板 schema 与 commit、CLI 版本与快照、公开技能来源与导出 hash 的跨仓发布证据。 | — | 不要求尚未生成的仓库 commit 互相循环引用。 |
69
69
  | 研发管理仓库 | 承载 Spec、OpenAPI、架构、Ticket、验证、发布和复盘等研发管理资产的仓库。 | — | 不等同于前端 / 后端代码 monorepo。 |
70
70
  | 实现仓库 | 承载前端、后端或其他运行时代码及其 Git、CI、MR / PR、测试命令和发布流水线的仓库。 | — | 不要把实现仓库的源码所有权混入研发管理仓库。 |
71
+ | Git 子模块分层接入 | 将前端 / 后端实现仓以 Git submodule(gitlink,mode `160000`)挂到 `project-instance` 的 `apps/` 布局下,并登记 `repository_scope: git-submodule`。 | — | 不得与 `harness-apps` 同源 monorepo 或无 gitlink 的 `external-repository` 混用;禁止把实现源码复制进 Harness 冒充 submodule。 |
71
72
  | 跨仓库契约变更 | 需要两个或多个独立仓库协同实现、验证和按顺序发布的共享契约变化。 | — | 任一参与仓库未完成契约对齐和集成验证时,不得单独声称整体可发布。 |
72
73
  | 模板源仓库(`template-source`) | 承载 `yss-spec-project-template` 权威模板资产及其演进规则的仓库身份。 | — | 只管理可复用模板,不承载某个具体产品的研发生命周期资产。 |
73
74
  | 模板实例仓库(`project-instance`) | 由模板初始化后生成、承载某个具体产品研发生命周期资产的仓库身份。 | — | 不作为通用流程模板的权威来源。 |
74
- | 模板实例分发面 | 模板源中应随 CLI 快照进入 `project-instance` 的共享生命周期、模板、用户指南和验证资产集合。 | — | 不包含模板源审查、研究、发布路线或源仓库专属 ADR。 |
75
- | 模板源治理区 | 仅供 `template-source` 使用、保存审查证据、研究记录、跨仓契约、发布路线和源仓库治理决策的归档区域。 | — | 不随 CLI 分发;不等于产品实例的研发管理资产。 |
75
+ | 模板实例分发面 | 模板源中应随 CLI 快照进入 `project-instance` 的共享生命周期、模板、用户指南和验证资产集合。 | — | 不包含模板源审查、研究、发布路线、源仓库专属 ADR 或源仓库 LLM Wiki 编译树。 |
76
+ | 模板源治理区 | 仅供 `template-source` 使用、保存审查证据、研究记录、跨仓契约、发布路线、源仓库治理决策和源仓库 LLM Wiki 编译树的归档区域。 | — | 不随 CLI 分发;不等于产品实例的研发管理资产。`wiki-root` 为 `.template-source/wiki`。 |
76
77
  | 仓库身份清单 | 显式声明仓库身份和清单结构版本的机器可读资产。 | — | 不承载项目名称、团队规模、Tracker 或其他易变业务配置。 |
77
78
 
78
79
  ## 业务术语
@@ -21,7 +21,7 @@ apps/
21
21
  - `allowed_write_paths`、`expected_evidence_files` 和生成器输出位置必须能回指具体项目目录;直接放开 `apps/backend/` 或 `apps/frontend/` 属于路径策略违规。
22
22
  - 外部实现仓库不要求采用 Harness 的 `apps/` 布局,但仍必须登记该仓库内的实际项目根路径;跨仓库切片的写路径不得用本 Harness 的占位路径冒充真实路径。
23
23
 
24
- 每个 Harness 内项目至少登记 `project_type`、`project_name`、`project_root` 和 `repository_scope`。同一 Git monorepo 下的多个项目可以共用一条仓库登记,但必须逐项目列出根路径和独立验证命令;不同 Git 仓库必须分别登记。
24
+ 每个 Harness 内项目至少登记 `project_type`、`project_name`、`project_root` 和 `repository_scope`。`repository_scope` 只允许 `external-repository`、`harness-apps` 或 `git-submodule`。同一 Git monorepo 下的多个项目可以共用一条仓库登记,但必须逐项目列出根路径和独立验证命令;不同 Git 仓库必须分别登记,`git-submodule` 的每个子仓各一条。
25
25
 
26
26
  ## 1.2 前端 / 后端验证命令
27
27
 
@@ -32,6 +32,28 @@ apps/
32
32
  - Ticket、Slice Implementation Contract、CI 和 Review 证据必须写下实际执行的上述命令。既有仓库缺少 `pnpm` 或 Maven Wrapper 时,先记录受控例外、替代命令和责任人,再执行。
33
33
  - 本模板源仓库没有产品 frontend / backend 运行时;其 Node 校验仍按 `AGENTS.md` 的 Cursor Cloud 说明使用 `pnpm --dir .template-source/tooling/node`。
34
34
 
35
+ ## 1.3 `repository_scope: git-submodule`
36
+
37
+ 当用户明确选择分层接入、且前端 / 后端保持独立 Git 历史、同时挂到 `project-instance` 工作树时,使用 Git submodule,而不是把实现源码复制进 Harness,也不是把 gitlink 登记成 `harness-apps`。
38
+
39
+ ```text
40
+ <project-instance>/ # superproject = 研发管理仓库
41
+ ├── .gitmodules
42
+ ├── apps/backend/<backend-project>/ # gitlink mode 160000
43
+ └── apps/frontend/<frontend-project>/ # gitlink mode 160000
44
+ ```
45
+
46
+ 强制规则:
47
+
48
+ - `layout_policy` / `implementation_path_policy` 必须是 `git-submodule-harness-apps`。挂载路径仍须是具体的 `apps/backend/<project>/` 或 `apps/frontend/<project>/`;容器根和 `app/` 单数路径一律阻断。
49
+ - 三个 `repository_scope` 必须在登记字段、Git 身份和写路径上可区分:`git-submodule` 强制 `git_url`、`gitmodules_name`、`gitlink_path`(等于 `project_root`)、`git_entry_mode: 160000`、`superproject_git_url`、`checkout_state`,并分别登记默认分支、CI、验证命令、回滚点(子仓 SHA + 父仓 gitlink SHA);`harness-apps` 与 `external-repository` 禁止填写这些 gitlink 身份字段(可填 `不适用`)。缺 `git_entry_mode` 不得默认为普通目录。
50
+ - 子仓 `git_url` 必须与 `superproject_git_url` 不同。登记后必须用工作树对照(`git ls-files --stage`、`.gitmodules`、`inspectWorkingTreeScope`):声明 `harness-apps` 但路径是 gitlink,或声明 `git-submodule` 但工作树只是普通目录 / 复制源码,均视为误路由并阻断。
51
+ - 只允许 `git submodule add` / `git submodule update --init` 形成 gitlink;禁止把实现仓库源码 copy、subtree 或普通 clone 进 Harness 后冒充 submodule。
52
+ - clone / CI / Cloud Agent 必须递归检出:`git clone --recurse-submodules`,或事后 `git submodule update --init`。GitHub Actions 须显式 `submodules: true|recursive` 且私有子仓另给 PAT / SSH;GitLab 须设 `GIT_SUBMODULE_STRATEGY` 并配置 job token 访问。默认不递归时目录为空,不得当作「工程不存在」去脚手架。
53
+ - 空 gitlink、未初始化、detached HEAD 或 `--force` 覆盖挂载点一律不得当成普通目录:`scaffold_status=required` 阻断,脚手架生成器即使收到 `--force` 也不得覆盖 gitlink,且不得进入「请显式传入 `--force`」普通目录覆盖 / rename 路径;禁止在 detached HEAD 上 commit,也不得把 `--output-dir` 指向 detached HEAD 子仓后 mkdir、staging 或生成工程。先在子仓检出跟踪分支,再写代码。写入前必须读取 `inspectWorkingTreeScope` 的对象结果:只有 `.writable === true` 才可写。已登记为 `git-submodule` 的空 gitlink / uninitialized / detached HEAD 必须 `.writable === false`,即使工作树探测失败也不得把返回值当成可写。
54
+ - Git 授权按仓分别计算,顺序强制为 **先子仓 commit/push,再父仓更新 gitlink**。父仓 push 使用 `git push --recurse-submodules=check`。跨仓库切片的 `delivery_order` 必须包含 `superproject-gitlink-update`。
55
+ - `.gitmodules`、gitlink 和子仓工作树不是 `create-yss-spec` 受管资产;CLI `sync` 不得创建、覆盖或删除它们。
56
+
35
57
  ## 2. 影响面路由
36
58
 
37
59
  | 影响面 | 必须绑定的记录 | 本变更结论 |
@@ -46,7 +68,7 @@ apps/
46
68
 
47
69
  - 模板仓库负责 `yss-project.yaml`、流程事实源、迁移指南、技能投影、模板校验脚本和快照可发布状态。
48
70
  - CLI 仓库负责 `attach`、`sync`、受管 manifest、快照 commit、metadata、迁移计划、备份 / 回滚、端到端测试和用户说明。
49
- - CLI 只写研发管理资产;不得创建或覆盖前后端运行时代码,不删除目标 `.git`。
71
+ - CLI 只写研发管理资产;不得创建或覆盖前后端运行时代码,不删除目标 `.git`,也不创建、覆盖或删除 `.gitmodules` 与 gitlink。
50
72
  - 模板 commit 必须是 40 位不可变提交;开发测试可通过 `YSS_SPEC_TEMPLATE_REF` 覆盖,正式发布不得跟随浮动 `main`。
51
73
 
52
74
  ## 4. Fresh verification 与 checkpoint
@@ -65,6 +65,21 @@ owner: ai
65
65
  | Frontend | | pass / fail / not-applicable |
66
66
  | E2E / 关键路径 | | pass / fail / not-applicable |
67
67
 
68
+ ## 5.1 Git submodule(条件)
69
+
70
+ `repository_scope: git-submodule` 时必填;其他范围标记 `not-applicable`。
71
+
72
+ | 字段 | 值 |
73
+ |---|---|
74
+ | superproject-gitlink-update | pending / ready / blocked / not-applicable |
75
+ | submodule_commit_sha | 子仓已推送的 40 位 SHA |
76
+ | superproject_gitlink_sha | 父仓记录的 gitlink SHA,必须等于 `submodule_commit_sha` |
77
+ | checkout_state | attached-branch / detached-head / uninitialized / empty-gitlink |
78
+ | push_recurse_submodules | `check` |
79
+ | clone_or_ci_recurse | `git clone --recurse-submodules` / `GIT_SUBMODULE_STRATEGY` / Actions `submodules` |
80
+
81
+ `delivery_order` 必须包含子仓 MR / PR,然后是 `superproject-gitlink-update`。空 gitlink、detached HEAD 或未推送的子仓 SHA 一律 `blocked`。
82
+
68
83
  ## 6. Release And Rollback
69
84
 
70
85
  | 字段 | 值 |
@@ -17,11 +17,16 @@ owner: ai
17
17
  | git_url | |
18
18
  | default_branch | |
19
19
  | local_worktree | |
20
- | repository_scope | external-repository / harness-apps |
20
+ | repository_scope | external-repository / harness-apps / git-submodule |
21
21
  | project_type | backend / frontend / fullstack / other |
22
22
  | project_name | |
23
23
  | project_root | 外部仓库相对路径,或 `apps/backend/<project>/` / `apps/frontend/<project>/` |
24
- | layout_policy | `harness-apps-multi-project` / `external-repository-native` |
24
+ | layout_policy | `harness-apps-multi-project` / `external-repository-native` / `git-submodule-harness-apps` |
25
+ | gitmodules_name | `git-submodule` 必填;其他范围填 `不适用` |
26
+ | gitlink_path | 等于 `project_root`;仅 `git-submodule` |
27
+ | git_entry_mode | `160000`;仅 `git-submodule` |
28
+ | superproject_git_url | 父仓远端;必须与 `git_url` 不同 |
29
+ | checkout_state | attached-branch / detached-head / uninitialized / empty-gitlink / 不适用 |
25
30
  | scaffold_status | existing / required / initialized |
26
31
  | scaffold_skill | `yss-ddd-scaffold-generator` / `yss-frontend-scaffold-generator` / none |
27
32
  | scaffold_target_confirmed | 是 / 否 / 不适用 |
@@ -41,7 +46,7 @@ owner: ai
41
46
  | typecheck_command | | |
42
47
  | ci_pipeline | | |
43
48
 
44
- Harness 内项目路径约束:`apps/backend/`、`apps/frontend/` 只能作为项目容器;工程必须位于具体的 `apps/backend/<project>/` 或 `apps/frontend/<project>/`。`app/backend/`、`app/frontend/` 及其子路径禁止登记或生成。外部实现仓库填写真实项目根路径,不使用本表的 Harness 占位路径。
49
+ Harness 内项目路径约束:`apps/backend/`、`apps/frontend/` 只能作为项目容器;工程必须位于具体的 `apps/backend/<project>/` 或 `apps/frontend/<project>/`。`app/backend/`、`app/frontend/` 及其子路径禁止登记或生成。外部实现仓库填写真实项目根路径,不使用本表的 Harness 占位路径。`git-submodule` 必须同时满足 Harness `apps/` 挂载路径和独立 Git 身份,不得登记为 `harness-apps` 或无 gitlink 的 `external-repository`。空 gitlink、detached HEAD、`--force` 覆盖挂载点不得当成普通目录。登记后必须对照工作树 gitlink,禁止把复制进 Harness 的源码冒充 submodule。
45
50
 
46
51
  ## 2.1 项目清单(同一 monorepo 可登记多个项目)
47
52
 
@@ -84,8 +84,8 @@ owner: ai
84
84
  | 字段 | 内容 |
85
85
  |---|---|
86
86
  | impacted_areas | |
87
- | implementation_path_policy | `harness-apps-multi-project` / `external-repository-native` |
88
- | project_roots | Harness 内填写 `apps/backend/<project>/` / `apps/frontend/<project>/`;外部仓库填写真实项目根 |
87
+ | implementation_path_policy | `harness-apps-multi-project` / `external-repository-native` / `git-submodule-harness-apps` |
88
+ | project_roots | Harness 内与 `git-submodule` 填写 `apps/backend/<project>/` / `apps/frontend/<project>/`;外部仓库填写真实项目根 |
89
89
  | required_skills | |
90
90
  | optional_skills | |
91
91
  | unavailable_skills | 写明 provider、fallback 和阻断结论;不得静默跳过 |
@@ -187,7 +187,7 @@ owner: ai
187
187
 
188
188
  ## 4. 外部实现仓库
189
189
 
190
- > 当前仓库默认不承载运行时代码。若用户明确选择 Harness 内实现,项目根必须是 `apps/backend/<project>/` 或 `apps/frontend/<project>/`;`apps/backend/`、`apps/frontend/` 不能作为项目根,`app/backend/`、`app/frontend/` 及其子路径禁止使用。
190
+ > 当前仓库默认不承载运行时代码。若用户明确选择 Harness 内实现,同源代码用 `harness-apps`;独立仓挂载用 `git-submodule`。两种项目根都必须是 `apps/backend/<project>/` 或 `apps/frontend/<project>/`;`apps/backend/`、`apps/frontend/` 不能作为项目根,`app/backend/`、`app/frontend/` 及其子路径禁止使用。
191
191
 
192
192
  | repo_role | project_root | git_url | default_branch | working_branch | MR / PR | CI | test_command | build_command | 状态 |
193
193
  |---|---|---|---|---|---|---|---|---|---|
@@ -24,7 +24,7 @@
24
24
  | 架构资产 | `docs/.scratch/<feature>/architecture/`、`docs/adr/` | 保存业务架构、功能架构、系统总体架构、数据架构和关键 ADR |
25
25
  | 交付过程 | `docs/.scratch/<feature>/parent-ticket.md`、`issues/`、`gates/`、`verification/` | 跟踪垂直切片、实现路由、验证记录和审查结论 |
26
26
 
27
- 推荐把真实工程代码放在独立实现仓库。本仓库默认作为 Harness / 研发管理仓库,保存产品、契约和过程资产。如果用户明确选择把代码放入本仓库,才按需新增:
27
+ 推荐把真实工程代码放在独立实现仓库(`external-repository`)。本仓库默认作为 Harness / 研发管理仓库,保存产品、契约和过程资产。如果用户明确选择把代码放入本仓库,使用同源 `harness-apps`;如果要把独立前后端仓挂进本仓工作树且保留各自 Git 历史,使用 `git-submodule`:
28
28
 
29
29
  ```text
30
30
  apps/
@@ -34,7 +34,7 @@ docs/releases/
34
34
  docs/implementation/
35
35
  ```
36
36
 
37
- `apps/backend/` 和 `apps/frontend/` 只能作为项目容器,不能直接生成工程;`app/backend/`、`app/frontend/` 及其子路径禁止使用。不得由 Agent 自行新建任意顶层业务代码目录。实现仓库接入和跨仓库切片绑定见 `docs/process/implementation-repo-integration.md`。
37
+ `apps/backend/` 和 `apps/frontend/` 只能作为项目容器,不能直接生成工程;`app/backend/`、`app/frontend/` 及其子路径禁止使用。`git-submodule` 挂载点仍是上述具体项目目录,但是 gitlink,不能登记成 `harness-apps`。不得由 Agent 自行新建任意顶层业务代码目录。实现仓库接入和跨仓库切片绑定见 `docs/process/implementation-repo-integration.md`。
38
38
 
39
39
  ## 2. 工具分工
40
40
 
@@ -0,0 +1,58 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+
6
+ function git(cwd, args) {
7
+ return execFileSync("git", args, { cwd, encoding: "utf8" });
8
+ }
9
+
10
+ function initRepo(dir) {
11
+ mkdirSync(dir, { recursive: true });
12
+ git(dir, ["init", "-b", "main"]);
13
+ git(dir, ["config", "user.email", "test@example.invalid"]);
14
+ git(dir, ["config", "user.name", "fixture"]);
15
+ git(dir, ["config", "commit.gpgsign", "false"]);
16
+ }
17
+
18
+ export function makeGitlinkFixture({ checkout = "empty-gitlink" } = {}) {
19
+ const root = mkdtempSync(path.join(tmpdir(), "yss-gitlink-"));
20
+ const child = path.join(root, "child.git");
21
+ const superproject = path.join(root, "super");
22
+ initRepo(child);
23
+ writeFileSync(path.join(child, "README.md"), "child\n");
24
+ git(child, ["add", "."]);
25
+ git(child, ["commit", "-m", "init child"]);
26
+ const sha = git(child, ["rev-parse", "HEAD"]).trim();
27
+ initRepo(superproject);
28
+ mkdirSync(path.join(superproject, "apps/backend"), { recursive: true });
29
+ const mount = "apps/backend/billing-service";
30
+ if (checkout === "empty-gitlink") {
31
+ mkdirSync(path.join(superproject, mount), { recursive: true });
32
+ writeFileSync(
33
+ path.join(superproject, ".gitmodules"),
34
+ `[submodule "backend-billing-service"]\n\tpath = ${mount}\n\turl = ${child}\n`
35
+ );
36
+ git(superproject, ["update-index", "--add", "--cacheinfo", "160000", sha, mount]);
37
+ git(superproject, ["add", ".gitmodules"]);
38
+ git(superproject, ["commit", "-m", "empty gitlink"]);
39
+ } else {
40
+ git(superproject, ["-c", "protocol.file.allow=always", "submodule", "add", child, mount]);
41
+ git(superproject, ["commit", "-m", "add submodule"]);
42
+ if (checkout === "detached-head") {
43
+ git(path.join(superproject, mount), ["checkout", "--detach"]);
44
+ } else if (checkout === "attached-branch") {
45
+ git(path.join(superproject, mount), ["checkout", "-B", "main"]);
46
+ }
47
+ }
48
+ return {
49
+ root,
50
+ superproject,
51
+ child,
52
+ mount,
53
+ sha,
54
+ cleanup() {
55
+ rmSync(root, { recursive: true, force: true });
56
+ }
57
+ };
58
+ }