create-yss-spec 2.1.14 → 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 +18 -2
  2. package/package.json +1 -1
  3. package/src/cli.js +367 -19
  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
@@ -5,6 +5,12 @@ import path from "node:path";
5
5
  import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
+ import {
9
+ findGitRoot,
10
+ gitSubmoduleScaffoldViolation,
11
+ overlayMountViolation
12
+ } from "../../../../scripts/lib/repository-scope-policy.mjs";
13
+
8
14
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
9
15
  const SKILL_ROOT = path.resolve(SCRIPT_DIR, "..");
10
16
  const REPOSITORY_ROOT = path.resolve(SCRIPT_DIR, "../../../..");
@@ -87,11 +93,28 @@ class ScaffoldGenerator {
87
93
  this.scaffoldContract = undefined;
88
94
  }
89
95
 
96
+ gitRootForOutput() {
97
+ return findGitRoot(this.finalProjectRoot) || findGitRoot(this.outputDir) || REPOSITORY_ROOT;
98
+ }
99
+
100
+ refuseGitlinkAsRegularDirectory(gitRoot, target) {
101
+ const overlay = overlayMountViolation(gitRoot, target, { force: true });
102
+ if (overlay) fail(overlay);
103
+ }
104
+
90
105
  async generate() {
91
106
  await this.validateHarnessOutputLayout();
92
- if (await exists(this.finalProjectRoot) && !this.options.force) fail(`输出目录已存在: ${this.finalProjectRoot};如确认覆盖,请显式传入 --force`);
107
+ const gitRoot = this.gitRootForOutput();
108
+ const gitlinkViolation = gitSubmoduleScaffoldViolation(gitRoot, this.outputDir, this.projectName, { force: this.options.force });
109
+ if (gitlinkViolation) fail(gitlinkViolation);
110
+ this.refuseGitlinkAsRegularDirectory(gitRoot, this.finalProjectRoot);
111
+ const targetExists = await exists(this.finalProjectRoot);
112
+ if (targetExists) {
113
+ this.refuseGitlinkAsRegularDirectory(gitRoot, this.finalProjectRoot);
114
+ if (!this.options.force) fail(`输出目录已存在: ${this.finalProjectRoot};如确认覆盖,请显式传入 --force`);
115
+ }
93
116
  await this.validateContractMetadata();
94
- if (await exists(this.finalProjectRoot) && await nonEmpty(this.finalProjectRoot) && this.options.force) this.validateForceMetadata();
117
+ if (targetExists && await nonEmpty(this.finalProjectRoot) && this.options.force) this.validateForceMetadata();
95
118
  await mkdir(this.outputDir, { recursive: true });
96
119
  const stagingRoot = await mkdtemp(path.join(this.outputDir, `.${this.projectName}.staging-`));
97
120
  this.projectRoot = path.join(stagingRoot, this.projectName);
@@ -101,7 +124,11 @@ class ScaffoldGenerator {
101
124
  this.generateDatabaseScripts(); await this.generateDocumentation(); await this.writeGenerationManifest();
102
125
  await this.copyWrapperFiles(); await this.validateGeneratedArtifacts();
103
126
  let backupPath;
104
- if (await exists(this.finalProjectRoot)) { backupPath = path.join(this.outputDir, `.${this.projectName}.backup-${backupStamp()}`); await rename(this.finalProjectRoot, backupPath); }
127
+ if (await exists(this.finalProjectRoot)) {
128
+ this.refuseGitlinkAsRegularDirectory(gitRoot, this.finalProjectRoot);
129
+ backupPath = path.join(this.outputDir, `.${this.projectName}.backup-${backupStamp()}`);
130
+ await rename(this.finalProjectRoot, backupPath);
131
+ }
105
132
  await rename(this.projectRoot, this.finalProjectRoot); this.projectRoot = this.finalProjectRoot;
106
133
  console.log(`\n✅ 项目生成完成!\n📂 项目位置: ${this.projectRoot}`);
107
134
  if (backupPath) console.log(`♻️ 原项目备份: ${backupPath}`);
@@ -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: []
@@ -19,8 +19,8 @@ description: Use when a Harness change or vertical slice may require work across
19
19
  1. 读取 `docs/process/implementation-repo-integration.md` 和当前 change 资产。
20
20
  2. 判断影响面:Harness-only、backend-only、frontend-only、backend+frontend、contract-only、release-only。
21
21
  3. 确认每个受影响实现仓库已有登记记录;缺失则阻断并要求 onboarding。
22
- 4. 核对项目根路径:Harness 内项目必须是 `apps/backend/<project>/` 或 `apps/frontend/<project>/`;`apps/backend/`、`apps/frontend/` 只能作为容器,`app/backend/`、`app/frontend/` 及其子路径必须阻断。外部仓库记录各自的真实项目根。
23
- 5. 输出最小任务分配:Harness 文档 / OpenAPI / 后端 MR / 前端 MR / 验证 / 发布。
22
+ 4. 核对项目根路径:Harness 内 `harness-apps` 与 `git-submodule` 项目必须是 `apps/backend/<project>/` 或 `apps/frontend/<project>/`;`apps/backend/`、`apps/frontend/` 只能作为容器,`app/backend/`、`app/frontend/` 及其子路径必须阻断。`git-submodule` 另核 gitlink、`.gitmodules` 和独立 `git_url`。外部 `external-repository` 记录各自的真实项目根。
23
+ 5. 输出最小任务分配:Harness 文档 / OpenAPI / 后端 MR / 前端 MR / 验证 / 发布。`git-submodule` 必须追加 `superproject-gitlink-update`。
24
24
  6. 按 `docs/templates/cross-repo-slice-template.md` 生成切片记录草案。
25
25
  7. 给出 fresh verification 命令和阶段 checkpoint 回写字段。
26
26
 
@@ -18,11 +18,11 @@ description: Use when connecting an existing frontend, backend, fullstack, or ot
18
18
 
19
19
  1. 确认当前仓库是 Harness 仓库,读取 `docs/process/implementation-repo-integration.md`。
20
20
  2. 如果输入是本地路径,只读检查 Git remote、当前分支、目录结构、构建文件和测试脚本。
21
- 3. 如果输入是远端 URL,优先使用只读 Git 查询;需要 clone 时只能 clone 到临时目录,不能 clone 到 Harness 仓库内。
21
+ 3. 如果输入是远端 URL,优先使用只读 Git 查询。需要完整工作树时:`external-repository` 只能 clone 到临时目录;`git-submodule` 只能通过 `git submodule add` / `git submodule update --init` 在已批准的 `apps/backend/<project>/` 或 `apps/frontend/<project>/` gitlink 挂载点检出,禁止普通 clone 或复制源码进 Harness。
22
22
  4. 识别技术栈、包管理器、CI、测试命令、构建命令、OpenAPI 接入和设计 token 接入。
23
- 5. 判定实现位置:Harness 内只能使用 `apps/backend/<project>/` 或 `apps/frontend/<project>/`;`apps/backend/`、`apps/frontend/` 仅为容器,`app/backend/`、`app/frontend/` 及其子路径必须标记为阻断。外部实现仓库记录真实项目根路径,不套用 Harness 占位路径。
24
- 6. 按 `docs/templates/implementation-repo-registry-template.md` 输出实现仓库登记内容。
25
- 7. 列出 `known_gaps`、人审点、fresh verification 命令和需要回写到 Harness change / Issue / checkpoint 的信息。
23
+ 5. 判定实现位置与 `repository_scope`:`external-repository` 记录真实项目根;`harness-apps` 与 `git-submodule` 只能使用 `apps/backend/<project>/` 或 `apps/frontend/<project>/`。`apps/backend/`、`apps/frontend/` 仅为容器,`app/backend/`、`app/frontend/` 及其子路径必须标记为阻断。gitlink(mode `160000`)必须登记为 `git-submodule` 和 `layout_policy: git-submodule-harness-apps`,不得写成 `harness-apps`。必须对照工作树:`git ls-files --stage`、`.gitmodules` 与声明 scope 不一致时阻断;缺少 `git_entry_mode: 160000` 不得把挂载点当普通目录。
24
+ 6. 按 `docs/templates/implementation-repo-registry-template.md` 输出实现仓库登记内容。`git-submodule` 必填 `gitmodules_name`、`gitlink_path`、`git_entry_mode`、`superproject_git_url` 和 `checkout_state`。`harness-apps` / `external-repository` 这些字段填 `不适用`。
25
+ 7. 列出 `known_gaps`、人审点、fresh verification 命令和需要回写到 Harness change / Issue / checkpoint 的信息。空 gitlink、detached HEAD、`--force` 覆盖挂载点或缺少递归检出凭据时标记阻断。写入前必须看 `inspectWorkingTreeScope` / `implementationWriteViolation`:只接受对象结果且 `.writable === true` 才可写;字符串、`null` 或 `.writable !== true` 一律不可写。即使已正确登记为 `git-submodule`,空 gitlink 或 detached HEAD 也必须 `.writable === false`,不得当普通目录写文件或脚手架。
26
26
 
27
27
  ## Baseline Checks
28
28
 
@@ -36,7 +36,7 @@ description: Use when connecting an existing frontend, backend, fullstack, or ot
36
36
  ## Boundaries
37
37
 
38
38
  - 不直接提交、推送、创建 MR / PR 或修改实现仓库。
39
- - 不把实现仓库源码复制进 Harness 仓库。
39
+ - 不把实现仓库源码复制进 Harness 仓库;`git-submodule` 只能形成 gitlink,不能 copy / subtree 冒充。
40
40
  - 不把缺失命令编造为已存在;找不到时标记 `unknown` 或 `需人工确认`。
41
41
  - 不得把 `app/backend/`、`app/frontend/` 或 `apps/backend/`、`apps/frontend/` 容器根登记为可生成项目根。
42
42
  - 触碰认证、授权、SQL、迁移、加密、公共基础库 API 时标记 `TODO-HUMAN-REVIEW`。
@@ -51,7 +51,7 @@ node <skill-root>/scripts/extract.mjs skill-names --in <live-lock.json> --out <w
51
51
 
52
52
  ## Steps
53
53
 
54
- 1. Detect: `wiki/index.md` + `CLAUDE.md` (or `AGENTS.md`) means a wiki exists.
54
+ 1. Detect: `<wiki-root>/wiki/index.md` + `CLAUDE.md` (or `AGENTS.md`) means a wiki exists. Default wiki-root is repo-root `wiki/`. If `yss-project.yaml` is `repository_mode: template-source`, wiki-root is `.template-source/wiki`.
55
55
  2. If the user is asking a repository question, load [query.md](references/query.md) and stop. Do not lint, ingest, or append `log.md`.
56
56
  3. Load the mode algorithm: [compile.md](references/compile.md) for init/refresh/rebuild, [ingest.md](references/ingest.md) for ingest. For init/rebuild corpus choice, load [discover.md](discover.md). For writing, load [writing.md](references/writing.md). For checks, load [lint.md](references/lint.md).
57
57
  4. **Fact order:** called code > table comments / config defaults > stale raw copies. After writing, re-read live sources for a sample of claims (`N = min(5, changed pages)`).
@@ -27,6 +27,7 @@ Unless the user replaces this list with the host's single source-of-truth table:
27
27
  ## Default exclude
28
28
 
29
29
  - `docs/reviews/`
30
+ - `.template-source/evidence/`
30
31
  - `docs/.scratch/`
31
32
  - Agent projection directories (copies or symlinks of shared skills)
32
33
  - `node_modules`
@@ -64,7 +64,7 @@ node scripts/generate_scaffold.mjs \
64
64
  - 永不生成 `User CRUD` 示例;`--with-example` 已禁用,业务代码必须按批准的 Slice Implementation Contract 逐切片实现。
65
65
  - 若仓库已经存在相同工程,先核对覆盖范围。
66
66
  - 输出目录必须显式指定;非空目录默认拒绝写入,确认覆盖时才使用 `--force`。
67
- - Harness 内多项目布局必须使用 `apps/backend/<project>/`;`apps/backend/` 只能是生成器的父容器,`app/backend/`、`app/frontend/` 及其子路径一律拒绝。
67
+ - Harness 内多项目布局必须使用 `apps/backend/<project>/`;`apps/backend/` 只能是生成器的父容器,`app/backend/`、`app/frontend/` 及其子路径一律拒绝。`git-submodule` gitlink、空挂载点、detached HEAD 工作树不得覆盖生成,即使传入 `--force` 也不得当成普通目录,且不得走「请显式传入 `--force`」普通目录覆盖 / rename 路径。`--output-dir` 指向 detached HEAD 子仓时不得 mkdir、staging 或生成工程。先 `git submodule update --init` 并在子仓附加分支工作树内生成。生成器必须先调用 `gitSubmoduleScaffoldViolation`,再在 exists / `--force` / rename 之前调用 `refuseGitlinkAsRegularDirectory`。
68
68
  - 不要在 skill 里硬编码用户业务字段或真实连接信息。
69
69
  - 生成后要检查依赖关系是否仍符合分层约束。
70
70
  - 生成后必须继续使用 `yss-backend-scaffold-parent` 校验工程基线,再按垂直切片加载 `yss-domain`、`yss-application`、`yss-repository`、`yss-web-controller` 等行为 skill;各 skill 的 `references/*-layer-guide.md` 承载分层开发规范。
@@ -5,6 +5,12 @@ import path from "node:path";
5
5
  import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
+ import {
9
+ findGitRoot,
10
+ gitSubmoduleScaffoldViolation,
11
+ overlayMountViolation
12
+ } from "../../../../scripts/lib/repository-scope-policy.mjs";
13
+
8
14
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
9
15
  const SKILL_ROOT = path.resolve(SCRIPT_DIR, "..");
10
16
  const REPOSITORY_ROOT = path.resolve(SCRIPT_DIR, "../../../..");
@@ -87,11 +93,28 @@ class ScaffoldGenerator {
87
93
  this.scaffoldContract = undefined;
88
94
  }
89
95
 
96
+ gitRootForOutput() {
97
+ return findGitRoot(this.finalProjectRoot) || findGitRoot(this.outputDir) || REPOSITORY_ROOT;
98
+ }
99
+
100
+ refuseGitlinkAsRegularDirectory(gitRoot, target) {
101
+ const overlay = overlayMountViolation(gitRoot, target, { force: true });
102
+ if (overlay) fail(overlay);
103
+ }
104
+
90
105
  async generate() {
91
106
  await this.validateHarnessOutputLayout();
92
- if (await exists(this.finalProjectRoot) && !this.options.force) fail(`输出目录已存在: ${this.finalProjectRoot};如确认覆盖,请显式传入 --force`);
107
+ const gitRoot = this.gitRootForOutput();
108
+ const gitlinkViolation = gitSubmoduleScaffoldViolation(gitRoot, this.outputDir, this.projectName, { force: this.options.force });
109
+ if (gitlinkViolation) fail(gitlinkViolation);
110
+ this.refuseGitlinkAsRegularDirectory(gitRoot, this.finalProjectRoot);
111
+ const targetExists = await exists(this.finalProjectRoot);
112
+ if (targetExists) {
113
+ this.refuseGitlinkAsRegularDirectory(gitRoot, this.finalProjectRoot);
114
+ if (!this.options.force) fail(`输出目录已存在: ${this.finalProjectRoot};如确认覆盖,请显式传入 --force`);
115
+ }
93
116
  await this.validateContractMetadata();
94
- if (await exists(this.finalProjectRoot) && await nonEmpty(this.finalProjectRoot) && this.options.force) this.validateForceMetadata();
117
+ if (targetExists && await nonEmpty(this.finalProjectRoot) && this.options.force) this.validateForceMetadata();
95
118
  await mkdir(this.outputDir, { recursive: true });
96
119
  const stagingRoot = await mkdtemp(path.join(this.outputDir, `.${this.projectName}.staging-`));
97
120
  this.projectRoot = path.join(stagingRoot, this.projectName);
@@ -101,7 +124,11 @@ class ScaffoldGenerator {
101
124
  this.generateDatabaseScripts(); await this.generateDocumentation(); await this.writeGenerationManifest();
102
125
  await this.copyWrapperFiles(); await this.validateGeneratedArtifacts();
103
126
  let backupPath;
104
- if (await exists(this.finalProjectRoot)) { backupPath = path.join(this.outputDir, `.${this.projectName}.backup-${backupStamp()}`); await rename(this.finalProjectRoot, backupPath); }
127
+ if (await exists(this.finalProjectRoot)) {
128
+ this.refuseGitlinkAsRegularDirectory(gitRoot, this.finalProjectRoot);
129
+ backupPath = path.join(this.outputDir, `.${this.projectName}.backup-${backupStamp()}`);
130
+ await rename(this.finalProjectRoot, backupPath);
131
+ }
105
132
  await rename(this.projectRoot, this.finalProjectRoot); this.projectRoot = this.finalProjectRoot;
106
133
  console.log(`\n✅ 项目生成完成!\n📂 项目位置: ${this.projectRoot}`);
107
134
  if (backupPath) console.log(`♻️ 原项目备份: ${backupPath}`);
@@ -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` 并重新编译业务合同;脚手架不承载业务行为。