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.
- package/README.md +18 -2
- package/package.json +1 -1
- package/src/cli.js +367 -19
- package/template/.agents/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.agents/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.agents/skills/llm-wiki/SKILL.md +1 -1
- package/template/.agents/skills/llm-wiki/references/discover.md +1 -0
- package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.agents/skills/yss-router/SKILL.md +1 -1
- package/template/.agents/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.claude/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.claude/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.claude/skills/llm-wiki/SKILL.md +1 -1
- package/template/.claude/skills/llm-wiki/references/discover.md +1 -0
- package/template/.claude/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.claude/skills/yss-router/SKILL.md +1 -1
- package/template/.claude/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.codex/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.codex/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.codex/skills/llm-wiki/SKILL.md +1 -1
- package/template/.codex/skills/llm-wiki/references/discover.md +1 -0
- package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.codex/skills/yss-router/SKILL.md +1 -1
- package/template/.codex/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.cursor/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.cursor/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.cursor/skills/llm-wiki/SKILL.md +1 -1
- package/template/.cursor/skills/llm-wiki/references/discover.md +1 -0
- package/template/.cursor/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.cursor/skills/yss-router/SKILL.md +1 -1
- package/template/.cursor/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.cursor/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.hermes/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.hermes/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.hermes/skills/llm-wiki/SKILL.md +1 -1
- package/template/.hermes/skills/llm-wiki/references/discover.md +1 -0
- package/template/.hermes/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.hermes/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.hermes/skills/yss-router/SKILL.md +1 -1
- package/template/.hermes/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.pi/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.pi/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.pi/skills/llm-wiki/SKILL.md +1 -1
- package/template/.pi/skills/llm-wiki/references/discover.md +1 -0
- package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.pi/skills/yss-router/SKILL.md +1 -1
- package/template/.pi/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.qoder/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.qoder/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.qoder/skills/llm-wiki/SKILL.md +1 -1
- package/template/.qoder/skills/llm-wiki/references/discover.md +1 -0
- package/template/.qoder/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.qoder/skills/yss-router/SKILL.md +1 -1
- package/template/.qoder/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/.trae/skills/cross-repo-implementation-routing/SKILL.md +2 -2
- package/template/.trae/skills/implementation-repo-onboarding/SKILL.md +5 -5
- package/template/.trae/skills/llm-wiki/SKILL.md +1 -1
- package/template/.trae/skills/llm-wiki/references/discover.md +1 -0
- package/template/.trae/skills/yss-ddd-scaffold-generator/SKILL.md +1 -1
- package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +30 -3
- package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +52 -0
- package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +1 -1
- package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +24 -0
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +2 -0
- package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.trae/skills/yss-router/SKILL.md +1 -1
- package/template/.trae/skills/yss-router/references/router-contract.yaml +20 -0
- package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +1 -1
- package/template/AGENTS.md +5 -5
- package/template/CONTEXT.md +3 -2
- package/template/docs/process/implementation-repo-integration.md +24 -2
- package/template/docs/templates/cross-repo-slice-template.md +15 -0
- package/template/docs/templates/implementation-repo-registry-template.md +8 -3
- package/template/docs/templates/implementation-routing-template.md +3 -3
- 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
- package/template/scripts/lib/git-submodule-fixtures.mjs +58 -0
- package/template/scripts/lib/repository-scope-policy.mjs +456 -0
- package/template/scripts/lib/scenario-checks.mjs +11 -1
- package/template/scripts/repository-scope-policy +16 -0
- package/template/scripts/verify-repository-scope-scenarios +239 -0
- package/template/scripts/verify-scaffold-generator-scenarios +54 -0
- package/template/scripts/verify-template +3 -2
- package/template/skills-lock.json +7 -7
- package/template.manifest.json +6 -5
- package/template.snapshot.json +4 -4
- package/template/.cursor/environment.json +0 -5
- package/template/.github/workflows/template-node-tooling.yml +0 -61
- package/template/wiki/.wiki-manifest.json +0 -390
- package/template/wiki/raw/AGENTS.md +0 -122
- package/template/wiki/raw/CONTEXT.md +0 -80
- package/template/wiki/raw/README.md +0 -130
- package/template/wiki/raw/adr-0002-repository-mode.md +0 -3
- package/template/wiki/raw/create-yss-spec-repository-mode-contract.md +0 -95
- package/template/wiki/raw/harness-process-tailoring.md +0 -64
- package/template/wiki/raw/implementation-repo-integration.md +0 -62
- package/template/wiki/raw/issue-tracker.md +0 -135
- package/template/wiki/raw/lifecycle-registry.yaml +0 -369
- package/template/wiki/raw/maintenance-intensity.yaml +0 -25
- package/template/wiki/raw/skills-lock-names.md +0 -93
- package/template/wiki/raw/skills-maintenance.md +0 -90
- package/template/wiki/raw/spec-template.md +0 -102
- package/template/wiki/raw/triage-labels.md +0 -15
- package/template/wiki/raw/vertical-slice-ticket-template.md +0 -132
- package/template/wiki/raw/yss-project.yaml +0 -2
- package/template/wiki/raw/yss-skill-registry.yaml +0 -510
- package/template/wiki/wiki/Agent/345/205/245/345/217/243/350/247/204/345/210/231.md +0 -21
- package/template/wiki/wiki/CLAUDE.md +0 -28
- 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
- package/template/wiki/wiki/LLM Wiki.md +0 -30
- package/template/wiki/wiki/Matt/346/212/200/350/203/275/344/275/223/347/263/273.md +0 -21
- package/template/wiki/wiki/OpenAPI/345/245/221/347/272/246.md +0 -20
- package/template/wiki/wiki/SpecDelta.md +0 -19
- package/template/wiki/wiki/Spec/345/237/272/347/272/277.md +0 -22
- package/template/wiki/wiki/Ticket/344/270/216/346/265/201/347/250/213/347/212/266/346/200/201.md +0 -21
- 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
- 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
- package/template/wiki/wiki/concept-table.md +0 -9
- package/template/wiki/wiki/index.md +0 -41
- package/template/wiki/wiki/log.md +0 -51
- 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
- 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
- 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
- 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
- package/template/wiki/wiki//345/236/202/347/233/264/345/210/207/347/211/207Ticket.md +0 -23
- 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
- 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
- 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
- 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
- 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
- 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
- package/template/wiki/wiki//346/250/241/346/235/277/346/200/273/350/247/210.md +0 -20
- 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
package/README.md
CHANGED
|
@@ -14,6 +14,18 @@ npm create yss-spec@latest
|
|
|
14
14
|
npx create-yss-spec@latest
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
查看用法、命令、参数和样例:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx create-yss-spec@latest --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
查看 CLI 版本:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx create-yss-spec@latest --version
|
|
27
|
+
```
|
|
28
|
+
|
|
17
29
|
## 当前支持
|
|
18
30
|
|
|
19
31
|
- 交互式收集 `projectName`、`businessDomain`、`targetDir`
|
|
@@ -32,8 +44,10 @@ npx create-yss-spec@latest
|
|
|
32
44
|
- 接管 / 升级时迁移 Spec / Ticket 路径并删除 `to-prd`、`to-issues` 旧 skill
|
|
33
45
|
- 旧、新资产内容冲突或清单 schema / mode 非法时 fail closed
|
|
34
46
|
- 保留模板的共享 skill 投影;生成实例可在尚未 `git init` 时运行模板校验
|
|
47
|
+
- init / sync 不把模板源治理笔记、wiki、审查证据、源仓 CI / Cloud 环境和公开发布清单写入项目实例;attach 会带上 `yss-public-skills.json` 供 `verify-template` 使用
|
|
48
|
+
- 空 gitlink / detached HEAD / git-submodule 挂载点 fail closed,`--force` 也不能覆盖
|
|
35
49
|
|
|
36
|
-
本次模板适配按 `2.
|
|
50
|
+
本次模板适配按 `2.2.0` 发布,绑定 `yss-spec-project-template@04a6151`。`templateCommit` 会写入实例 metadata;“最新模板”指用户执行的 `npx create-yss-spec@latest` 所携带的最新已发布快照,CLI 运行时不会拉取模板仓库。
|
|
37
51
|
|
|
38
52
|
## 接管已有项目
|
|
39
53
|
|
|
@@ -60,6 +74,7 @@ npx create-yss-spec@latest attach \
|
|
|
60
74
|
- 根规则文件等受管冲突默认阻断;`--force` 才覆盖,并把被覆盖文件备份到目标目录外的临时目录。
|
|
61
75
|
- 合法 `template-source` 身份会转换为 `project-instance`;非法身份、迁移冲突和无法判断归属的扁平 Ticket 始终阻断。
|
|
62
76
|
- Git worktree 有脏改动时只提醒,不自动 stash 或提交。
|
|
77
|
+
- `.gitmodules`、gitlink 和 `apps/` 挂载工作树是用户资产;空 gitlink / detached HEAD 即使 `--force` 也阻断。
|
|
63
78
|
|
|
64
79
|
## 同步已有模板实例仓库
|
|
65
80
|
|
|
@@ -84,7 +99,8 @@ npx create-yss-spec@latest sync --dry-run
|
|
|
84
99
|
- 对模板已删除文件只报告,不自动删除
|
|
85
100
|
- 对已知的 Spec / Ticket 旧路径执行一次性迁移
|
|
86
101
|
- 迁移目标已存在且内容不一致时停止,不静默覆盖
|
|
87
|
-
-
|
|
102
|
+
- `.gitmodules`、gitlink(mode `160000`)和 `apps/` 下已挂载实现仓是用户资产,不创建、不覆盖、不删除
|
|
103
|
+
- init / sync 保持瘦实例:不复制 `scripts/`、`wiki/`、`.github/`、`.nvmrc`、`.gitignore`、`.template-source/`;attach 结束后会执行 `scripts/sync-skills --check`、`scripts/update-skill-lock --check` 和 `scripts/verify-template`,门禁失败会回滚文件和 metadata
|
|
88
104
|
|
|
89
105
|
```bash
|
|
90
106
|
npx create-yss-spec@latest sync --target-dir . --dry-run
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -33,15 +33,27 @@ const TEMPLATE_METADATA_FILENAME = ".yss-template.json";
|
|
|
33
33
|
const TEMPLATE_MANIFEST_VERSION = sha256(TEMPLATE_MANIFEST_TEXT);
|
|
34
34
|
const TEMPLATE_SOURCE = "github:iloveZzz/yss-spec-project-template";
|
|
35
35
|
const METADATA_SCHEMA_VERSION = 2;
|
|
36
|
+
const HELP_FLAGS = new Set(["--help", "-h", "-help"]);
|
|
37
|
+
const VERSION_FLAGS = new Set(["--version", "-v", "-version"]);
|
|
36
38
|
const AGENT_SKILL_ROOTS = [
|
|
37
39
|
".agents/skills",
|
|
38
40
|
".claude/skills",
|
|
39
41
|
".codex/skills",
|
|
42
|
+
".cursor/skills",
|
|
40
43
|
".hermes/skills",
|
|
41
44
|
".pi/skills",
|
|
42
45
|
".qoder/skills",
|
|
43
46
|
".trae/skills",
|
|
44
47
|
];
|
|
48
|
+
const GITLINK_MODE = "160000";
|
|
49
|
+
const UNMANAGED_USER_PATHS = new Set([".gitmodules"]);
|
|
50
|
+
const INSTANCE_FORBIDDEN_PATHS = [
|
|
51
|
+
".template-source",
|
|
52
|
+
".github",
|
|
53
|
+
".cursor/environment.json",
|
|
54
|
+
"wiki",
|
|
55
|
+
"docs/reviews",
|
|
56
|
+
];
|
|
45
57
|
const LEGACY_SKILL_MAPPINGS = [
|
|
46
58
|
["to-prd", "to-spec"],
|
|
47
59
|
["to-issues", "to-tickets"],
|
|
@@ -184,8 +196,10 @@ function parseArgs(argv) {
|
|
|
184
196
|
options.includeExampleDocs = true;
|
|
185
197
|
} else if (current === "--no-example-docs") {
|
|
186
198
|
options.includeExampleDocs = false;
|
|
187
|
-
} else if (current
|
|
199
|
+
} else if (HELP_FLAGS.has(current)) {
|
|
188
200
|
options.help = true;
|
|
201
|
+
} else if (VERSION_FLAGS.has(current)) {
|
|
202
|
+
options.version = true;
|
|
189
203
|
} else {
|
|
190
204
|
throw new Error(`不支持的参数:${current}`);
|
|
191
205
|
}
|
|
@@ -297,6 +311,235 @@ function isInsideTemplateRoot(targetDir) {
|
|
|
297
311
|
);
|
|
298
312
|
}
|
|
299
313
|
|
|
314
|
+
function posixRelative(from, to) {
|
|
315
|
+
return path.relative(from, to).split(path.sep).join("/");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function collectGitRoots(start) {
|
|
319
|
+
const roots = [];
|
|
320
|
+
if (!start) {
|
|
321
|
+
return roots;
|
|
322
|
+
}
|
|
323
|
+
let current = path.resolve(start);
|
|
324
|
+
while (true) {
|
|
325
|
+
if (
|
|
326
|
+
fs.existsSync(path.join(current, ".git")) ||
|
|
327
|
+
fs.existsSync(path.join(current, ".gitmodules"))
|
|
328
|
+
) {
|
|
329
|
+
roots.push(current);
|
|
330
|
+
}
|
|
331
|
+
const parent = path.dirname(current);
|
|
332
|
+
if (parent === current) {
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
current = parent;
|
|
336
|
+
}
|
|
337
|
+
return roots;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function parseGitmodules(content) {
|
|
341
|
+
const modules = [];
|
|
342
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
343
|
+
return modules;
|
|
344
|
+
}
|
|
345
|
+
let current = null;
|
|
346
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
347
|
+
const line = rawLine.trim();
|
|
348
|
+
const name = line.match(/^\[submodule "(.+)"\]$/);
|
|
349
|
+
if (name) {
|
|
350
|
+
current = { name: name[1], path: "", url: "" };
|
|
351
|
+
modules.push(current);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (!current) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const pathMatch = line.match(/^path\s*=\s*(.+)$/);
|
|
358
|
+
if (pathMatch) {
|
|
359
|
+
current.path = pathMatch[1].trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
360
|
+
}
|
|
361
|
+
const urlMatch = line.match(/^url\s*=\s*(.+)$/);
|
|
362
|
+
if (urlMatch) {
|
|
363
|
+
current.url = urlMatch[1].trim();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return modules;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function readGitmodules(repoRoot) {
|
|
370
|
+
const file = path.join(repoRoot, ".gitmodules");
|
|
371
|
+
if (!fs.existsSync(file) || pathKind(file) !== "file") {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
return parseGitmodules(fs.readFileSync(file, "utf8"));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function gitLsFilesStage(repoRoot, relativePath) {
|
|
378
|
+
const result = spawnSync(
|
|
379
|
+
"git",
|
|
380
|
+
["-C", repoRoot, "ls-files", "--stage", "--", relativePath],
|
|
381
|
+
{ encoding: "utf8" },
|
|
382
|
+
);
|
|
383
|
+
if (result.status !== 0) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
const line = (result.stdout || "").trim().split(/\r?\n/).filter(Boolean)[0];
|
|
387
|
+
if (!line) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
const match = line.match(/^(\d+)\s+([0-9a-f]+)\s+(\d+)\s+(.+)$/i);
|
|
391
|
+
if (!match) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
return { mode: match[1], sha: match[2], stage: match[3], path: match[4] };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function gitShowSuperproject(cwd) {
|
|
398
|
+
const result = spawnSync(
|
|
399
|
+
"git",
|
|
400
|
+
["-C", cwd, "rev-parse", "--show-superproject-working-tree"],
|
|
401
|
+
{ encoding: "utf8" },
|
|
402
|
+
);
|
|
403
|
+
if (result.status !== 0) {
|
|
404
|
+
return "";
|
|
405
|
+
}
|
|
406
|
+
return (result.stdout || "").trim();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function gitAbbrevRef(cwd) {
|
|
410
|
+
const result = spawnSync(
|
|
411
|
+
"git",
|
|
412
|
+
["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
413
|
+
{ encoding: "utf8" },
|
|
414
|
+
);
|
|
415
|
+
if (result.status !== 0) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
return (result.stdout || "").trim();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function isEmptyDir(dir) {
|
|
422
|
+
if (!fs.existsSync(dir)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
return fs.readdirSync(dir).filter((name) => name !== "." && name !== "..")
|
|
427
|
+
.length === 0;
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function isGitSubmoduleMount(repoRoot, targetPathValue) {
|
|
434
|
+
const relative = posixRelative(repoRoot, path.resolve(targetPathValue));
|
|
435
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (readGitmodules(repoRoot).some((item) => item.path === relative)) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
const staged = gitLsFilesStage(repoRoot, relative);
|
|
442
|
+
return staged?.mode === GITLINK_MODE;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function inspectCheckoutState(repoRoot, targetPathValue) {
|
|
446
|
+
const resolved = path.resolve(targetPathValue);
|
|
447
|
+
const relative = posixRelative(path.resolve(repoRoot), resolved);
|
|
448
|
+
const inRepo = Boolean(relative) && !relative.startsWith("..");
|
|
449
|
+
const staged = inRepo ? gitLsFilesStage(repoRoot, relative) : null;
|
|
450
|
+
const listed = inRepo
|
|
451
|
+
? readGitmodules(repoRoot).some((item) => item.path === relative)
|
|
452
|
+
: false;
|
|
453
|
+
const superproject = fs.existsSync(path.join(resolved, ".git"))
|
|
454
|
+
? gitShowSuperproject(resolved)
|
|
455
|
+
: "";
|
|
456
|
+
const isMount = staged?.mode === GITLINK_MODE || listed || Boolean(superproject);
|
|
457
|
+
const hasGit = fs.existsSync(path.join(resolved, ".git"));
|
|
458
|
+
if (isMount && !hasGit) {
|
|
459
|
+
return isEmptyDir(resolved) ? "empty-gitlink" : "uninitialized";
|
|
460
|
+
}
|
|
461
|
+
if (isMount && hasGit) {
|
|
462
|
+
const ref = gitAbbrevRef(resolved);
|
|
463
|
+
if (ref === "HEAD") {
|
|
464
|
+
return "detached-head";
|
|
465
|
+
}
|
|
466
|
+
return "attached-branch";
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function gitlinkWriteViolation(targetDir, { force = false } = {}) {
|
|
472
|
+
const resolved = path.resolve(targetDir);
|
|
473
|
+
const roots = collectGitRoots(resolved);
|
|
474
|
+
for (const root of roots) {
|
|
475
|
+
if (path.resolve(root) === PACKAGE_ROOT) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
let current = resolved;
|
|
479
|
+
while (true) {
|
|
480
|
+
if (isGitSubmoduleMount(root, current)) {
|
|
481
|
+
const checkout = inspectCheckoutState(root, current);
|
|
482
|
+
if (checkout === "empty-gitlink" || checkout === "uninitialized") {
|
|
483
|
+
return "空 gitlink 不得当成普通目录写入;gitlink 不得由 CLI 覆盖";
|
|
484
|
+
}
|
|
485
|
+
if (checkout === "detached-head") {
|
|
486
|
+
return "detached HEAD 不得当成普通目录写入;gitlink 不得由 CLI 覆盖";
|
|
487
|
+
}
|
|
488
|
+
if (path.resolve(current) === resolved) {
|
|
489
|
+
return force
|
|
490
|
+
? "--force 不得把 git-submodule 挂载点当成普通目录覆盖"
|
|
491
|
+
: "git-submodule gitlink 不得由 CLI 覆盖;先 git submodule update --init,在子仓附加分支的工作树内操作,或改用独立目录";
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (path.resolve(current) === path.resolve(root)) {
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
const parent = path.dirname(current);
|
|
498
|
+
if (parent === current) {
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
current = parent;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function assertTargetWorkingTreeWritable(targetDir, { force = false } = {}) {
|
|
508
|
+
const violation = gitlinkWriteViolation(targetDir, { force });
|
|
509
|
+
if (violation) {
|
|
510
|
+
throw new Error(violation);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function unmanagedPathReason(targetDir, relativePath) {
|
|
515
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
516
|
+
if (UNMANAGED_USER_PATHS.has(normalized) || normalized.split("/")[0] === ".gitmodules") {
|
|
517
|
+
return ".gitmodules 是用户资产,不属于受管模板文件";
|
|
518
|
+
}
|
|
519
|
+
const absolutePath = path.resolve(targetDir, normalized);
|
|
520
|
+
const roots = collectGitRoots(targetDir);
|
|
521
|
+
for (const root of roots) {
|
|
522
|
+
if (path.resolve(root) === PACKAGE_ROOT) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
let current = absolutePath;
|
|
526
|
+
while (true) {
|
|
527
|
+
if (isGitSubmoduleMount(root, current)) {
|
|
528
|
+
return "gitlink / apps 挂载工作树是用户资产,不属于受管模板文件";
|
|
529
|
+
}
|
|
530
|
+
if (path.resolve(current) === path.resolve(root)) {
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
const parent = path.dirname(current);
|
|
534
|
+
if (parent === current) {
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
current = parent;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
|
|
300
543
|
function normalizeRelativePath(relativePath) {
|
|
301
544
|
return relativePath.split(path.sep).join("/");
|
|
302
545
|
}
|
|
@@ -857,6 +1100,14 @@ function classifyAttachPlan(targetDir, variables) {
|
|
|
857
1100
|
};
|
|
858
1101
|
|
|
859
1102
|
for (const operation of desiredOperations) {
|
|
1103
|
+
const unmanagedReason = unmanagedPathReason(targetDir, operation.relativePath);
|
|
1104
|
+
if (unmanagedReason) {
|
|
1105
|
+
plan.unsafe.push({
|
|
1106
|
+
...operation,
|
|
1107
|
+
reason: unmanagedReason,
|
|
1108
|
+
});
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
860
1111
|
const kind = pathKind(operation.targetPath);
|
|
861
1112
|
if (kind === "missing") {
|
|
862
1113
|
plan.missing.push(operation);
|
|
@@ -900,6 +1151,14 @@ function classifySyncPlan(targetDir, metadata, identity = readTargetIdentity(tar
|
|
|
900
1151
|
const unsafe = [];
|
|
901
1152
|
|
|
902
1153
|
for (const operation of desiredOperations) {
|
|
1154
|
+
const unmanagedReason = unmanagedPathReason(targetDir, operation.relativePath);
|
|
1155
|
+
if (unmanagedReason) {
|
|
1156
|
+
unsafe.push({
|
|
1157
|
+
...operation,
|
|
1158
|
+
reason: unmanagedReason,
|
|
1159
|
+
});
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
903
1162
|
const existingRecord = managedFiles[operation.relativePath];
|
|
904
1163
|
const existingKind = pathKind(operation.targetPath);
|
|
905
1164
|
|
|
@@ -1024,6 +1283,8 @@ function inspectTargetDir(targetDir, force) {
|
|
|
1024
1283
|
throw new Error("目标目录不能位于模板源仓库内部");
|
|
1025
1284
|
}
|
|
1026
1285
|
|
|
1286
|
+
assertTargetWorkingTreeWritable(targetDir, { force });
|
|
1287
|
+
|
|
1027
1288
|
if (!fs.existsSync(targetDir)) {
|
|
1028
1289
|
return { exists: false, clearEntries: false };
|
|
1029
1290
|
}
|
|
@@ -1040,13 +1301,14 @@ function inspectTargetDir(targetDir, force) {
|
|
|
1040
1301
|
return { exists: true, clearEntries: entries.length > 0 && force };
|
|
1041
1302
|
}
|
|
1042
1303
|
|
|
1043
|
-
function inspectExistingTargetDir(targetDir) {
|
|
1304
|
+
function inspectExistingTargetDir(targetDir, { force = false } = {}) {
|
|
1044
1305
|
if (isInsideTemplateRoot(targetDir)) {
|
|
1045
1306
|
throw new Error("目标目录不能位于模板源仓库内部");
|
|
1046
1307
|
}
|
|
1047
1308
|
if (!fs.existsSync(targetDir) || pathKind(targetDir) !== "directory") {
|
|
1048
1309
|
throw new Error("attach 目标目录必须是已经存在的项目目录");
|
|
1049
1310
|
}
|
|
1311
|
+
assertTargetWorkingTreeWritable(targetDir, { force });
|
|
1050
1312
|
}
|
|
1051
1313
|
|
|
1052
1314
|
function prepareTargetDir(targetDir, targetState) {
|
|
@@ -1126,8 +1388,8 @@ function runTemplateVerification(targetDir, scriptPath) {
|
|
|
1126
1388
|
}
|
|
1127
1389
|
|
|
1128
1390
|
function verifyGeneratedTemplate(targetDir, mode = "managed") {
|
|
1129
|
-
if (mode === "init") {
|
|
1130
|
-
verifyGeneratedInstance(targetDir);
|
|
1391
|
+
if (mode === "init" || mode === "sync") {
|
|
1392
|
+
verifyGeneratedInstance(targetDir, { checkForbiddenPaths: mode === "init" });
|
|
1131
1393
|
return;
|
|
1132
1394
|
}
|
|
1133
1395
|
|
|
@@ -1140,15 +1402,18 @@ function verifyGeneratedTemplate(targetDir, mode = "managed") {
|
|
|
1140
1402
|
}
|
|
1141
1403
|
}
|
|
1142
1404
|
|
|
1143
|
-
function verifyGeneratedInstance(targetDir) {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1405
|
+
function verifyGeneratedInstance(targetDir, { checkForbiddenPaths = true } = {}) {
|
|
1406
|
+
if (checkForbiddenPaths) {
|
|
1407
|
+
const forbiddenPaths = [
|
|
1408
|
+
...INSTANCE_FORBIDDEN_PATHS,
|
|
1409
|
+
...[...INIT_EXCLUDED_ROOT_ENTRIES],
|
|
1410
|
+
...[...INIT_EXCLUDED_ROOT_FILES],
|
|
1411
|
+
...[...INIT_EXCLUDED_RELATIVE_PATHS],
|
|
1412
|
+
];
|
|
1413
|
+
for (const relativePath of [...new Set(forbiddenPaths)]) {
|
|
1414
|
+
if (pathKind(targetPath(targetDir, relativePath)) !== "missing") {
|
|
1415
|
+
throw new Error(`初始化结果包含禁止分发的模板源资产:${relativePath}`);
|
|
1416
|
+
}
|
|
1152
1417
|
}
|
|
1153
1418
|
}
|
|
1154
1419
|
|
|
@@ -1539,6 +1804,10 @@ function runInit(argv = []) {
|
|
|
1539
1804
|
printHelp();
|
|
1540
1805
|
return;
|
|
1541
1806
|
}
|
|
1807
|
+
if (options.version) {
|
|
1808
|
+
printVersion();
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1542
1811
|
return promptForMissingOptions(options).then((promptedOptions) => {
|
|
1543
1812
|
assertRequiredOptions(promptedOptions, "init");
|
|
1544
1813
|
readTemplateSnapshot();
|
|
@@ -1592,6 +1861,10 @@ function runAttach(argv = []) {
|
|
|
1592
1861
|
printHelp();
|
|
1593
1862
|
return;
|
|
1594
1863
|
}
|
|
1864
|
+
if (options.version) {
|
|
1865
|
+
printVersion();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1595
1868
|
assertRequiredOptions(options, "attach");
|
|
1596
1869
|
if (options.dryRun && options.apply) {
|
|
1597
1870
|
throw new Error("attach 的 --dry-run 与 --apply 互斥");
|
|
@@ -1602,7 +1875,7 @@ function runAttach(argv = []) {
|
|
|
1602
1875
|
|
|
1603
1876
|
readTemplateSnapshot();
|
|
1604
1877
|
const targetDir = normalizeTargetDir(options.targetDir);
|
|
1605
|
-
inspectExistingTargetDir(targetDir);
|
|
1878
|
+
inspectExistingTargetDir(targetDir, { force: Boolean(options.force) });
|
|
1606
1879
|
if (pathKind(targetPath(targetDir, TEMPLATE_METADATA_FILENAME)) !== "missing") {
|
|
1607
1880
|
throw new Error("当前项目已有模板元数据,请使用 sync,不要重复 attach");
|
|
1608
1881
|
}
|
|
@@ -1692,9 +1965,13 @@ function runSync(argv = []) {
|
|
|
1692
1965
|
printHelp();
|
|
1693
1966
|
return;
|
|
1694
1967
|
}
|
|
1968
|
+
if (options.version) {
|
|
1969
|
+
printVersion();
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1695
1972
|
readTemplateSnapshot();
|
|
1696
1973
|
const targetDir = normalizeTargetDir(options.targetDir || ".");
|
|
1697
|
-
inspectExistingTargetDir(targetDir);
|
|
1974
|
+
inspectExistingTargetDir(targetDir, { force: Boolean(options.force) });
|
|
1698
1975
|
const { metadata } = loadTemplateMetadata(targetDir);
|
|
1699
1976
|
const identity = readTargetIdentity(targetDir);
|
|
1700
1977
|
const syncPlan = classifySyncPlan(targetDir, metadata, identity);
|
|
@@ -1738,7 +2015,7 @@ function runSync(argv = []) {
|
|
|
1738
2015
|
applyManagedOperation(operation, transaction);
|
|
1739
2016
|
}
|
|
1740
2017
|
applyMigrationPlan(migrationPlan, transaction);
|
|
1741
|
-
verifyGeneratedTemplate(targetDir, "
|
|
2018
|
+
verifyGeneratedTemplate(targetDir, "sync");
|
|
1742
2019
|
writeTemplateMetadata(
|
|
1743
2020
|
targetDir,
|
|
1744
2021
|
buildNextSyncMetadata(metadata, syncPlan, targetDir),
|
|
@@ -1782,16 +2059,87 @@ function runSync(argv = []) {
|
|
|
1782
2059
|
console.log("3. 确认无误后提交本次模板同步结果");
|
|
1783
2060
|
}
|
|
1784
2061
|
|
|
2062
|
+
function argvIncludesFlag(argv, flags) {
|
|
2063
|
+
return argv.some((arg) => flags.has(arg));
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function printVersion() {
|
|
2067
|
+
console.log(`create-yss-spec ${PACKAGE_MANIFEST.version}`);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1785
2070
|
function printHelp() {
|
|
1786
2071
|
console.log(`create-yss-spec ${PACKAGE_MANIFEST.version}
|
|
1787
2072
|
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2073
|
+
USAGE
|
|
2074
|
+
$ create-yss-spec [COMMAND] [OPTIONS]
|
|
2075
|
+
|
|
2076
|
+
COMMANDS
|
|
2077
|
+
(default) 初始化新的模板实例仓库
|
|
2078
|
+
attach 向已有项目补齐受管研发管理资产
|
|
2079
|
+
sync 同步已有模板实例的受管资产
|
|
2080
|
+
|
|
2081
|
+
OPTIONS
|
|
2082
|
+
--project-name <name> 项目名称;init 不传则进入交互输入
|
|
2083
|
+
--business-domain <domain> 业务领域;init 不传则进入交互输入
|
|
2084
|
+
--team-size <size> 团队规模;init 不传则可留空,默认「待补充」
|
|
2085
|
+
--target-dir <dir> 目标目录;init 不传则进入交互输入,sync 默认为当前目录
|
|
2086
|
+
--issue-tracker github|gitlab 默认 issue tracker 偏好(默认 github)
|
|
2087
|
+
--dry-run 只预览计划,不写入文件
|
|
2088
|
+
--apply attach 确认执行写入;不能与 --dry-run 同时使用
|
|
2089
|
+
--force init:允许清空非空目录后重新生成
|
|
2090
|
+
attach / sync:覆盖受管冲突文件;unsafe 路径始终阻断
|
|
2091
|
+
--git-init 初始化完成后执行 git init
|
|
2092
|
+
--include-example-docs 显式保留示例文档(默认开启)
|
|
2093
|
+
--no-example-docs 不生成示例文档
|
|
2094
|
+
-h, --help 显示本帮助信息
|
|
2095
|
+
-v, --version 显示 CLI 版本
|
|
2096
|
+
|
|
2097
|
+
EXAMPLES
|
|
2098
|
+
$ npm create yss-spec@latest
|
|
2099
|
+
$ npx create-yss-spec@latest --help
|
|
2100
|
+
$ npx create-yss-spec@latest --version
|
|
2101
|
+
$ npx create-yss-spec@latest \\
|
|
2102
|
+
--project-name "Acme Spec Repo" \\
|
|
2103
|
+
--business-domain "Investment Research" \\
|
|
2104
|
+
--team-size "12" \\
|
|
2105
|
+
--target-dir "./acme-spec-repo" \\
|
|
2106
|
+
--issue-tracker github \\
|
|
2107
|
+
--git-init
|
|
2108
|
+
$ npx create-yss-spec@latest \\
|
|
2109
|
+
--project-name "Preview Repo" \\
|
|
2110
|
+
--business-domain "Data Platform" \\
|
|
2111
|
+
--target-dir "./preview-repo" \\
|
|
2112
|
+
--dry-run
|
|
2113
|
+
$ npx create-yss-spec@latest attach \\
|
|
2114
|
+
--target-dir . \\
|
|
2115
|
+
--project-name "Acme Application" \\
|
|
2116
|
+
--business-domain "Data Platform" \\
|
|
2117
|
+
--dry-run
|
|
2118
|
+
$ npx create-yss-spec@latest attach \\
|
|
2119
|
+
--target-dir . \\
|
|
2120
|
+
--project-name "Acme Application" \\
|
|
2121
|
+
--business-domain "Data Platform" \\
|
|
2122
|
+
--apply
|
|
2123
|
+
$ npx create-yss-spec@latest sync
|
|
2124
|
+
$ npx create-yss-spec@latest sync --dry-run
|
|
2125
|
+
$ npx create-yss-spec@latest sync --target-dir . --force
|
|
2126
|
+
|
|
2127
|
+
LEARN MORE
|
|
2128
|
+
仓库 README:https://github.com/iloveZzz/create-yss-spec#readme
|
|
2129
|
+
使用手册:https://github.com/iloveZzz/create-yss-spec/blob/main/docs/user-guide/create-yss-spec-cli-guide.md
|
|
2130
|
+
模板仓库:https://github.com/iloveZzz/yss-spec-project-template
|
|
1791
2131
|
`);
|
|
1792
2132
|
}
|
|
1793
2133
|
|
|
1794
2134
|
async function runCli(argv = []) {
|
|
2135
|
+
if (argvIncludesFlag(argv, HELP_FLAGS)) {
|
|
2136
|
+
printHelp();
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
if (argvIncludesFlag(argv, VERSION_FLAGS)) {
|
|
2140
|
+
printVersion();
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
1795
2143
|
if (argv[0] === "sync") {
|
|
1796
2144
|
runSync(argv.slice(1));
|
|
1797
2145
|
return;
|
|
@@ -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
|
|
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
|
|
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.
|
|
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:
|
|
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` 承载分层开发规范。
|