create-yss-spec 3.1.0 → 3.1.3
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 +117 -91
- package/bin/create-yss-spec.js +11 -3
- package/package.json +9 -2
- package/src/api/_sync-service.js +191 -0
- package/src/api/index.js +22 -0
- package/src/api/project-diff.js +9 -0
- package/src/api/project-doctor.js +10 -0
- package/src/api/template-apply.js +10 -0
- package/src/api/template-plan.js +9 -0
- package/src/cli/args.js +60 -0
- package/src/cli/error-output.js +52 -0
- package/src/cli/flags.js +9 -0
- package/src/cli/help.js +95 -0
- package/src/cli/index.js +82 -0
- package/src/cli/plan-output.js +61 -0
- package/src/cli/prompts.js +91 -0
- package/src/cli/router.js +41 -0
- package/src/cli.js +5 -2253
- package/src/commands/attach.js +280 -0
- package/src/commands/diff.js +23 -0
- package/src/commands/doctor.js +417 -0
- package/src/commands/init.js +193 -0
- package/src/commands/sync.js +146 -0
- package/src/commands/update.js +14 -0
- package/src/contracts/apply-result-v1.json +45 -0
- package/src/contracts/customization-policy-v1.json +28 -0
- package/src/contracts/doctor-report-v1.json +29 -0
- package/src/contracts/error-envelope-v1.json +24 -0
- package/src/contracts/generator-policy-v1.json +24 -0
- package/src/contracts/ownership-policy-v1.json +40 -0
- package/src/contracts/plan-schema-v1.json +115 -0
- package/src/family-identity.js +365 -0
- package/src/family-runtime.js +23 -0
- package/src/filesystem/apply-plan.js +44 -0
- package/src/filesystem/copy-path.js +28 -0
- package/src/filesystem/path-utils.js +67 -0
- package/src/filesystem/transaction-runner.js +41 -0
- package/src/filesystem/transaction.js +153 -0
- package/src/git/submodule.js +115 -0
- package/src/git/worktree.js +69 -0
- package/src/template/attach-planner-runtime.js +51 -0
- package/src/template/attach-planner.js +160 -0
- package/src/template/instance-runtime.js +463 -0
- package/src/template/lifecycle-metadata.js +99 -0
- package/src/template/lifecycle-policy.js +129 -0
- package/src/template/lifecycle-runtime.js +90 -0
- package/src/template/migration-planner.js +95 -0
- package/src/template/migration-runtime.js +247 -0
- package/src/template/ownership-metadata.js +79 -0
- package/src/template/ownership-policy.js +149 -0
- package/src/template/ownership-runtime.js +109 -0
- package/src/template/plan-schema.js +36 -0
- package/src/template/sync-planner-runtime.js +57 -0
- package/src/template/sync-planner.js +206 -0
- package/src/template/verification-runtime.js +154 -0
- package/src/validation/identity.js +41 -0
- package/src/validation/metadata.js +105 -0
- package/src/validation/security.js +67 -0
- package/src/validation/snapshot.js +47 -0
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.agents/skills/yss-ui/SKILL.md +2 -0
- package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.claude/skills/yss-ui/SKILL.md +2 -0
- package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.codex/skills/yss-ui/SKILL.md +2 -0
- package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.cursor/skills/yss-ui/SKILL.md +2 -0
- package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.pi/skills/yss-ui/SKILL.md +2 -0
- package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.qoder/skills/yss-ui/SKILL.md +2 -0
- package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.trae/skills/yss-ui/SKILL.md +2 -0
- package/template/CONTEXT.md +2 -0
- package/template/README.md +4 -0
- package/template/docs/agents/digital-human-roles.yaml +1 -0
- package/template/docs/process/frontend-backend-delivery.md +72 -0
- package/template/docs/process/implementation-repo-integration.md +2 -0
- package/template/docs/process/lifecycle-artifact-map.md +1 -0
- package/template/docs/process/lifecycle-registry-baseline.json +2 -1
- package/template/docs/process/lifecycle-registry.yaml +5 -0
- package/template/docs/process/schemas/backend-delivery-verification.schema.json +113 -0
- package/template/docs/process/schemas/backend-delivery.schema.json +301 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +200 -0
- package/template/docs/process/schemas/frontend-implementation-evidence.schema.json +3 -0
- package/template/docs/process/template-verification-profiles.yaml +6 -0
- package/template/docs/user-guide//346/210/230/346/234/257/350/256/276/350/256/241/345/255/220/351/241/271/347/233/256/347/224/250/346/210/267/346/211/213/345/206/214.md +20 -206
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +63 -963
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +11 -14
- package/template/docs/user-guide//350/256/276/345/244/207/345/200/237/347/224/250/350/264/257/347/251/277/346/241/210/344/276/213.md +127 -0
- package/template/scripts/backend-delivery +14 -0
- package/template/scripts/fixtures/backend-delivery/revision-server.mjs +11 -0
- package/template/scripts/instantiate-harness +69 -0
- package/template/scripts/lib/backend-delivery.mjs +164 -0
- package/template/scripts/lib/frontend-delivery-boundary.mjs +49 -0
- package/template/scripts/lib/frontend-delivery.mjs +73 -0
- package/template/scripts/lib/harness-execution-scope.mjs +52 -0
- package/template/scripts/lib/implementation-contract-compiler.mjs +12 -2
- package/template/scripts/lib/lifecycle-transition.mjs +7 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +14 -7
- package/template/scripts/lib/strategic-handoff.mjs +1 -1
- package/template/scripts/lib/task-package.mjs +7 -0
- package/template/scripts/sync-strategic-handoff-tools +3 -1
- package/template/scripts/verify-frontend-delivery +9 -0
- package/template/scripts/verify-frontend-delivery-scenarios +131 -0
- package/template/scripts/verify-frontend-implementation-evidence +2 -0
- package/template/skills-lock.json +11 -11
- package/template.manifest.json +43 -1
- package/template.snapshot.json +5 -5
package/README.md
CHANGED
|
@@ -2,61 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
用于初始化、接管已有项目并持续同步 `yss-spec-project-template` 研发管理资产的 npm CLI。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
源码候选版本:`3.1.2`。当前固定模板为 `yss-spec-project-template@017925706a981aec9eadefd470232bb531acd4d6`;CLI 运行时不会拉取模板仓库。`npm create yss-spec@latest` 获取的是实际已发布 npm 包,发布版本请以 `npm view create-yss-spec version` 为准。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm create yss-spec@latest
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
也可以使用 `npx`:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx create-yss-spec@latest
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
查看用法、命令、参数和样例:
|
|
7
|
+
## 快速开始
|
|
18
8
|
|
|
19
9
|
```bash
|
|
10
|
+
npm create yss-spec@latest
|
|
20
11
|
npx create-yss-spec@latest --help
|
|
12
|
+
npx create-yss-spec@latest --version
|
|
21
13
|
```
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
候选源码示例:
|
|
24
16
|
|
|
25
17
|
```bash
|
|
26
|
-
npx create-yss-spec@latest
|
|
18
|
+
npx create-yss-spec@latest \
|
|
19
|
+
--project-name "设备借用" \
|
|
20
|
+
--business-domain "内部设备管理" \
|
|
21
|
+
--target-dir ./equipment-project
|
|
27
22
|
```
|
|
28
23
|
|
|
29
|
-
##
|
|
24
|
+
## 五家族身份保护
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
- `--team-size`
|
|
33
|
-
- `--dry-run`
|
|
34
|
-
- 非空目录默认拒绝,初始化命令的 `--force` 允许重新生成
|
|
35
|
-
- `--git-init`
|
|
36
|
-
- `--issue-tracker github|gitlab`
|
|
37
|
-
- `--include-example-docs`
|
|
38
|
-
- `--no-example-docs`
|
|
39
|
-
- `attach` 子命令:在已有项目中补齐研发管理资产
|
|
40
|
-
- `sync` 子命令
|
|
41
|
-
- `update` / `upgrade` 子命令:检查 npm 最新版本,如有更新则安装 CLI 自身
|
|
42
|
-
- 基于 `.yss-template.json` 的模板版本基线和 managed baseline
|
|
43
|
-
- 只使用当前 CLI 包内置、绑定不可变 commit 的模板快照
|
|
44
|
-
- 初始化时将 `yss-project.yaml` 从 `template-source` 改写为 `project-instance`
|
|
45
|
-
- 接管 / 升级时迁移 Spec / Ticket 路径并删除 `to-prd`、`to-issues` 旧 skill
|
|
46
|
-
- 旧、新资产内容冲突或清单 schema / mode 非法时 fail closed
|
|
47
|
-
- 保留模板的共享 skill 投影;生成实例可在尚未 `git init` 时运行模板校验
|
|
48
|
-
- init / sync 不把模板源治理笔记、wiki、审查证据、源仓 CI / Cloud 环境和公开发布清单写入项目实例;attach 会带上 `yss-public-skills.json` 供 `verify-template` 使用
|
|
49
|
-
- 空 gitlink / detached HEAD / git-submodule 挂载点 fail closed,`--force` 也不能覆盖
|
|
26
|
+
CLI 在规划和写入前检查模板家族身份,当前识别:
|
|
50
27
|
|
|
51
|
-
|
|
28
|
+
- `create-yss-spec` / `.yss-template.json`
|
|
29
|
+
- `create-yss-harness-design` / `.yss-harness-design.json`
|
|
30
|
+
- `create-yss-harness-dev` / `.yss-harness-dev.json`
|
|
31
|
+
- repository-local backend / `.yss-harness-backend.json`
|
|
32
|
+
- repository-local frontend / `.yss-harness-frontend.json`
|
|
52
33
|
|
|
53
|
-
|
|
34
|
+
`docs/process/harness-profile.yaml` 也参与身份判断。异族、多重 identity、损坏 metadata、未知或矛盾 profile、identity symlink 均在写入前 fail closed,`--force` 不能绕过;Programmatic API 使用相同 guard。帮助和版本查询不受目标家族限制。
|
|
54
35
|
|
|
55
|
-
|
|
36
|
+
后端/前端专职模板使用各自仓库的 `scripts/instantiate-harness --target <新目录>`,不由本 CLI 做跨家族原地迁移。
|
|
56
37
|
|
|
57
|
-
##
|
|
38
|
+
## CLI 能力
|
|
58
39
|
|
|
59
|
-
`attach
|
|
40
|
+
- `attach`:接管已有未管理项目。
|
|
41
|
+
- `sync`:同步受管资产。
|
|
42
|
+
- `sync --dry-run`:传统文本预演。
|
|
43
|
+
- `sync --plan`:结构化文本计划。
|
|
44
|
+
- `sync --json`:Plan Schema v1。
|
|
45
|
+
- `diff / diff --json`:只读差异。
|
|
46
|
+
- `doctor / doctor --json`:只读健康诊断。
|
|
47
|
+
- `update / upgrade`:只更新 CLI 程序,不同步实例资产。
|
|
60
48
|
|
|
61
49
|
```bash
|
|
62
50
|
npx create-yss-spec@latest attach \
|
|
@@ -69,83 +57,121 @@ npx create-yss-spec@latest attach \
|
|
|
69
57
|
--target-dir . \
|
|
70
58
|
--project-name "项目名称" \
|
|
71
59
|
--business-domain "业务领域" \
|
|
72
|
-
--apply
|
|
60
|
+
--apply
|
|
61
|
+
|
|
62
|
+
npx create-yss-spec@latest sync --target-dir . --dry-run
|
|
63
|
+
npx create-yss-spec@latest sync --target-dir .
|
|
73
64
|
```
|
|
74
65
|
|
|
75
|
-
|
|
66
|
+
同步安全规则:
|
|
76
67
|
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
68
|
+
- 默认只更新未被本地修改的受管文件。
|
|
69
|
+
- `replace-with-force` 冲突只有显式 `--force` 才覆盖。
|
|
70
|
+
- `manual` 冲突即使 `--force` 也不会覆盖。
|
|
71
|
+
- `user-owned` / `protected`、gitlink/submodule/detached HEAD、路径越界和中间 symlink 均不可被 force 绕过。
|
|
72
|
+
- 模板删除项只报告,不自动删除。
|
|
73
|
+
- 校验失败通过 FileTransaction 回滚,并保留必要备份。
|
|
83
74
|
|
|
84
|
-
##
|
|
75
|
+
## Programmatic API v1
|
|
85
76
|
|
|
86
|
-
|
|
77
|
+
npm package 根入口已经开放稳定 Node.js API:
|
|
87
78
|
|
|
88
|
-
```
|
|
89
|
-
|
|
79
|
+
```js
|
|
80
|
+
const {
|
|
81
|
+
API_VERSION,
|
|
82
|
+
projectDoctor,
|
|
83
|
+
projectDiff,
|
|
84
|
+
templatePlan,
|
|
85
|
+
templateApply,
|
|
86
|
+
toErrorEnvelope,
|
|
87
|
+
} = require("create-yss-spec");
|
|
90
88
|
```
|
|
91
89
|
|
|
92
|
-
|
|
90
|
+
当前 `API_VERSION = 1`。
|
|
93
91
|
|
|
94
|
-
```
|
|
95
|
-
|
|
92
|
+
```js
|
|
93
|
+
const plan = templatePlan({ targetDir: "/path/to/project" });
|
|
94
|
+
const report = projectDoctor({ targetDir: "/path/to/project" });
|
|
95
|
+
const result = templateApply({ targetDir: "/path/to/project", force: true });
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
返回合同:
|
|
99
99
|
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
- 对模板已删除文件只报告,不自动删除
|
|
105
|
-
- 对已知的 Spec / Ticket 旧路径执行一次性迁移
|
|
106
|
-
- `docs/requirements/tickets/` 只在 attach 时检查归属;sync 不迁移、不删除,也不因其中存在 Ticket 而阻断
|
|
107
|
-
- 迁移目标已存在且内容不一致时停止,不静默覆盖
|
|
108
|
-
- `.gitmodules`、gitlink(mode `160000`)和 `apps/` 下已挂载实现仓是用户资产,不创建、不覆盖、不删除
|
|
109
|
-
- init / sync 保持实例边界:不复制 `wiki/`、`.github/`、`.template-source/`、源仓库 ADR、Cursor Cloud 环境配置、`docs/reviews/` 或根 `package.json`;共享 `scripts/`、`scripts/vendor/`、`.nvmrc` 和 `.gitignore` 属于实例门禁所需资产。attach 结束后会执行 `scripts/sync-skills --check`、`scripts/update-skill-lock --check` 和 `scripts/verify-template`,门禁失败会回滚文件和 metadata
|
|
100
|
+
- `projectDoctor()` → Doctor Report v1
|
|
101
|
+
- `projectDiff()` / `templatePlan()` → Plan Schema v1
|
|
102
|
+
- `templateApply()` → Apply Result v1
|
|
103
|
+
- `toErrorEnvelope(error)` → Error Envelope v1
|
|
110
104
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
API 不解析 argv,也不负责 CLI 文本输出;CLI 与未来 MCP 复用同一 Planner / Policy / Security / Transaction 逻辑。
|
|
106
|
+
|
|
107
|
+
## Policy 与 metadata baseline
|
|
108
|
+
|
|
109
|
+
当前五类 ownership:
|
|
110
|
+
|
|
111
|
+
- `managed`
|
|
112
|
+
- `managed-customizable`
|
|
113
|
+
- `generated`
|
|
114
|
+
- `user-owned`
|
|
115
|
+
- `protected`
|
|
116
|
+
|
|
117
|
+
Customization Policy v1:
|
|
118
|
+
|
|
119
|
+
- `replace-with-force`
|
|
120
|
+
- `manual`
|
|
121
|
+
|
|
122
|
+
Generator Policy v1 为 generated 资产记录稳定 `generatorId + generatorVersion`。
|
|
123
|
+
|
|
124
|
+
`.yss-template.json` 会持久化 ownership/customization/generator policy version/hash,以及每个 managed file 的 ownership、mergeStrategy、generatorId/generatorVersion。Doctor 会报告 policy missing/drift/matched。
|
|
125
|
+
|
|
126
|
+
## 模块化架构
|
|
127
|
+
|
|
128
|
+
历史约 72KB 的 `src/cli.js` 已退役为兼容桥接。当前执行模型:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
CLI / Programmatic API
|
|
132
|
+
↓
|
|
133
|
+
Inspect → Desired State → Policy → Plan → Validate → Transaction → Verify → Metadata
|
|
114
134
|
```
|
|
115
135
|
|
|
116
|
-
|
|
136
|
+
核心机器合同:
|
|
137
|
+
|
|
138
|
+
- Plan Schema v1
|
|
139
|
+
- Error Envelope v1
|
|
140
|
+
- Doctor Report v1
|
|
141
|
+
- Apply Result v1
|
|
142
|
+
- Ownership Policy v1
|
|
143
|
+
- Customization Policy v1
|
|
144
|
+
- Generator Policy v1
|
|
117
145
|
|
|
118
|
-
|
|
146
|
+
## 使用未发布候选包
|
|
147
|
+
|
|
148
|
+
从本仓固定提交构建候选包时,先使用 `scripts/sync-template.js` 中的 `DEFAULT_TEMPLATE_REF` 重建模板快照,再打包:
|
|
119
149
|
|
|
120
150
|
```bash
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
YSS_SPEC_TEMPLATE_REPO=https://github.com/iloveZzz/yss-spec-project-template.git \
|
|
152
|
+
YSS_SPEC_TEMPLATE_REF=017925706a981aec9eadefd470232bb531acd4d6 \
|
|
153
|
+
node scripts/sync-template.js
|
|
154
|
+
|
|
155
|
+
npm pack --ignore-scripts
|
|
124
156
|
```
|
|
125
157
|
|
|
126
|
-
-
|
|
127
|
-
- 项目本地依赖会在对应项目根执行 `npm install create-yss-spec@latest`
|
|
128
|
-
- 通过 npx 运行或在源码目录中运行时只报告版本并给出安装建议,不覆盖当前文件
|
|
129
|
-
- `--dry-run` 只查询和预览,不安装;`--force` 在已是最新时仍重新安装(npx / 源码目录除外)
|
|
158
|
+
`--ignore-scripts` 仅用于已经显式重建并核对固定快照后的候选打包,不代表 npm 发布。
|
|
130
159
|
|
|
131
160
|
## 开发验证
|
|
132
161
|
|
|
133
|
-
CLI 源码和研发记录由本仓库独立维护。首次测试会从
|
|
134
|
-
[`iloveZzz/yss-spec-project-template`](https://github.com/iloveZzz/yss-spec-project-template)
|
|
135
|
-
同步受管模板快照;正式发布应显式绑定确定 commit:
|
|
136
|
-
|
|
137
162
|
```bash
|
|
138
|
-
|
|
139
|
-
|
|
163
|
+
npm run test:contracts
|
|
164
|
+
npm run test:unit
|
|
165
|
+
npm run test:integration
|
|
166
|
+
YSS_SPEC_TEMPLATE_REF=017925706a981aec9eadefd470232bb531acd4d6 npm test
|
|
167
|
+
npm pack --dry-run
|
|
140
168
|
```
|
|
141
169
|
|
|
142
|
-
##
|
|
170
|
+
## 设计与手册
|
|
143
171
|
|
|
144
|
-
- [
|
|
145
|
-
- [
|
|
146
|
-
- [
|
|
147
|
-
- [
|
|
148
|
-
- [
|
|
149
|
-
- [`yss-project.yaml` 跨仓库实现记录](docs/implementation/yss-project-repository-mode-contract.md)
|
|
150
|
-
- [实施路由与 Build Architecture Checklist](docs/implementation/)
|
|
172
|
+
- [CLI v4 模块化](docs/implementation/cli-v4-modularization.md)
|
|
173
|
+
- [Machine Contracts v1](docs/implementation/machine-contracts-v1.md)
|
|
174
|
+
- [Ownership Policy v1](docs/implementation/ownership-policy-v1.md)
|
|
175
|
+
- [Lifecycle Policies v1](docs/implementation/lifecycle-policies-v1.md)
|
|
176
|
+
- [Programmatic API v1](docs/implementation/programmatic-api-v1.md)
|
|
151
177
|
- [完整中文使用手册](docs/user-guide/create-yss-spec-cli-guide.md)
|
package/bin/create-yss-spec.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { runCli } = require("../src/cli");
|
|
3
|
+
const { runCli } = require("../src/cli/index.js");
|
|
4
|
+
const { serializeError } = require("../src/cli/error-output.js");
|
|
5
|
+
|
|
6
|
+
const argv = process.argv.slice(2);
|
|
7
|
+
const wantsJson = argv.includes("--json");
|
|
4
8
|
|
|
5
9
|
(async () => {
|
|
6
|
-
await runCli(
|
|
10
|
+
await runCli(argv);
|
|
7
11
|
})().catch((error) => {
|
|
8
|
-
|
|
12
|
+
if (wantsJson) {
|
|
13
|
+
process.stdout.write(serializeError(error));
|
|
14
|
+
} else {
|
|
15
|
+
console.error(error.message);
|
|
16
|
+
}
|
|
9
17
|
process.exitCode = 1;
|
|
10
18
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-yss-spec",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Initialize a YSS spec project template repository",
|
|
5
|
+
"main": "src/api/index.js",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "git+https://github.com/iloveZzz/create-yss-spec.git"
|
|
@@ -23,7 +24,13 @@
|
|
|
23
24
|
"scripts": {
|
|
24
25
|
"sync-template": "node scripts/sync-template.js",
|
|
25
26
|
"pretest": "npm run sync-template",
|
|
26
|
-
"test": "node --test tests/*.test.js",
|
|
27
|
+
"test": "node --test --test-concurrency=1 tests/*.test.js",
|
|
28
|
+
"test:contracts": "node --test tests/contracts.test.js tests/error-output.test.js tests/apply-result-contract.test.js",
|
|
29
|
+
"test:unit": "node --test tests/*planner*.test.js tests/filesystem-*.test.js tests/validation-modules.test.js tests/doctor-checks.test.js tests/ownership-policy.test.js tests/lifecycle-policy.test.js tests/lifecycle-metadata.test.js tests/verification-runtime.test.js tests/cli-modules.test.js tests/cli-entrypoint.test.js tests/self-update.test.js tests/plan-schema.test.js tests/contracts.test.js tests/error-output.test.js tests/apply-result-contract.test.js tests/programmatic-api-unit.test.js",
|
|
30
|
+
"test:integration": "npm run sync-template && node --test tests/init-cli.test.js tests/sync-command-modular.test.js tests/attach-command-modular.test.js tests/doctor-diff-command.test.js tests/programmatic-api.test.js tests/family-identity.test.js",
|
|
27
31
|
"prepack": "npm run sync-template"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"ajv": "^8.17.1"
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const { pathKind, normalizeRelativePath } = require("../filesystem/path-utils");
|
|
7
|
+
const { applyManagedOperation, applyMigrationOperations } = require("../filesystem/apply-plan");
|
|
8
|
+
const { runInTransaction } = require("../filesystem/transaction-runner");
|
|
9
|
+
const { assertBundledFamily, assertTargetFamily } = require("../family-runtime");
|
|
10
|
+
const { gitDirtyWarning } = require("../git/worktree");
|
|
11
|
+
const { unmanagedPathReason, assertTargetWorkingTreeWritable } = require("../validation/security");
|
|
12
|
+
const { buildSyncPlanFromRuntime } = require("../template/sync-planner-runtime");
|
|
13
|
+
const { buildLegacyMigrationPlan } = require("../template/migration-runtime");
|
|
14
|
+
const { decorateMetadataOwnership } = require("../template/ownership-metadata");
|
|
15
|
+
const { decorateMetadataLifecycle } = require("../template/lifecycle-metadata");
|
|
16
|
+
const {
|
|
17
|
+
PACKAGE_ROOT,
|
|
18
|
+
PACKAGE_MANIFEST,
|
|
19
|
+
BUNDLED_TEMPLATE_ROOT,
|
|
20
|
+
TEMPLATE_METADATA_FILENAME,
|
|
21
|
+
fileHash,
|
|
22
|
+
readTemplateSnapshot,
|
|
23
|
+
readTargetIdentity,
|
|
24
|
+
buildSyncDesiredOperations,
|
|
25
|
+
loadTemplateMetadata,
|
|
26
|
+
writeTemplateMetadata,
|
|
27
|
+
buildNextSyncMetadata,
|
|
28
|
+
verifyGeneratedSyncInstance,
|
|
29
|
+
} = require("../template/instance-runtime");
|
|
30
|
+
|
|
31
|
+
const APPLY_RESULT_SCHEMA_VERSION = 1;
|
|
32
|
+
|
|
33
|
+
function normalizeTargetDir(targetDir = ".", cwd = process.cwd()) {
|
|
34
|
+
return path.resolve(cwd, targetDir);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isInsideTemplateRoot(targetDir) {
|
|
38
|
+
const relativePath = path.relative(BUNDLED_TEMPLATE_ROOT, targetDir);
|
|
39
|
+
return (
|
|
40
|
+
relativePath === "" ||
|
|
41
|
+
(!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function inspectExistingTargetDir(targetDir, { force = false } = {}) {
|
|
46
|
+
if (isInsideTemplateRoot(targetDir)) {
|
|
47
|
+
throw new Error("目标目录不能位于模板源仓库内部");
|
|
48
|
+
}
|
|
49
|
+
if (!fs.existsSync(targetDir) || pathKind(targetDir) !== "directory") {
|
|
50
|
+
throw new Error("attach 目标目录必须是已经存在的项目目录");
|
|
51
|
+
}
|
|
52
|
+
assertTargetWorkingTreeWritable(targetDir, {
|
|
53
|
+
force,
|
|
54
|
+
packageRoot: PACKAGE_ROOT,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function affectedPathsForManagedOperations(operations) {
|
|
59
|
+
return operations.map((operation) => normalizeRelativePath(operation.relativePath));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function affectedPathsForMigration(migrationPlan) {
|
|
63
|
+
return (migrationPlan.operations || []).flatMap((operation) =>
|
|
64
|
+
operation.kind === "move" ? [operation.from, operation.to] : [operation.path],
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildSyncContext({ targetDir = ".", force = false, cwd = process.cwd() } = {}) {
|
|
69
|
+
const snapshot = readTemplateSnapshot();
|
|
70
|
+
assertBundledFamily(snapshot);
|
|
71
|
+
const resolvedTargetDir = normalizeTargetDir(targetDir, cwd);
|
|
72
|
+
assertTargetFamily(resolvedTargetDir);
|
|
73
|
+
inspectExistingTargetDir(resolvedTargetDir, { force: Boolean(force) });
|
|
74
|
+
|
|
75
|
+
const { metadata } = loadTemplateMetadata(resolvedTargetDir);
|
|
76
|
+
const identity = readTargetIdentity(resolvedTargetDir);
|
|
77
|
+
const desiredOperations = buildSyncDesiredOperations(
|
|
78
|
+
resolvedTargetDir,
|
|
79
|
+
metadata,
|
|
80
|
+
identity,
|
|
81
|
+
);
|
|
82
|
+
const migrationPlan = buildLegacyMigrationPlan(
|
|
83
|
+
resolvedTargetDir,
|
|
84
|
+
desiredOperations,
|
|
85
|
+
{ checkFlatTickets: false },
|
|
86
|
+
);
|
|
87
|
+
const warning = gitDirtyWarning(resolvedTargetDir);
|
|
88
|
+
|
|
89
|
+
const { classified: syncPlan, plan } = buildSyncPlanFromRuntime({
|
|
90
|
+
targetDir: resolvedTargetDir,
|
|
91
|
+
metadata,
|
|
92
|
+
desiredOperations,
|
|
93
|
+
migration: migrationPlan,
|
|
94
|
+
warning,
|
|
95
|
+
toVersion: PACKAGE_MANIFEST.version,
|
|
96
|
+
getUnmanagedReason: (operation) =>
|
|
97
|
+
unmanagedPathReason(resolvedTargetDir, operation.relativePath, {
|
|
98
|
+
packageRoot: PACKAGE_ROOT,
|
|
99
|
+
}),
|
|
100
|
+
getPathKind: (operation) => pathKind(operation.targetPath),
|
|
101
|
+
getFileHash: (operation) => fileHash(operation.targetPath),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
targetDir: resolvedTargetDir,
|
|
106
|
+
metadata,
|
|
107
|
+
identity,
|
|
108
|
+
desiredOperations,
|
|
109
|
+
migrationPlan,
|
|
110
|
+
warning,
|
|
111
|
+
syncPlan,
|
|
112
|
+
plan,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function assertSyncApplicable(context) {
|
|
117
|
+
const { migrationPlan, syncPlan } = context;
|
|
118
|
+
if (migrationPlan.unsafe.length > 0) {
|
|
119
|
+
throw new Error("sync 被 unsafe 迁移项阻断;请先人工整理 Ticket 归属");
|
|
120
|
+
}
|
|
121
|
+
if (syncPlan.unsafe.length > 0) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"sync 被 unsafe 受管路径阻断;--force 不能绕过,请先人工整理目标路径",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (migrationPlan.conflicts.length > 0) {
|
|
127
|
+
throw new Error("sync 被旧路径迁移冲突阻断,请先处理目标冲突");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function applySyncContext(context, { force = false } = {}) {
|
|
132
|
+
assertSyncApplicable(context);
|
|
133
|
+
const { targetDir, metadata, migrationPlan, syncPlan } = context;
|
|
134
|
+
const managedToApply = [
|
|
135
|
+
...syncPlan.updated,
|
|
136
|
+
...syncPlan.added,
|
|
137
|
+
...(force ? syncPlan.forceableConflicts : []),
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const { backupPath } = runInTransaction({
|
|
141
|
+
targetDir,
|
|
142
|
+
operation: "sync",
|
|
143
|
+
affectedPaths: [
|
|
144
|
+
...affectedPathsForManagedOperations(managedToApply),
|
|
145
|
+
...affectedPathsForMigration(migrationPlan),
|
|
146
|
+
TEMPLATE_METADATA_FILENAME,
|
|
147
|
+
],
|
|
148
|
+
execute: (transaction) => {
|
|
149
|
+
for (const operation of managedToApply) {
|
|
150
|
+
applyManagedOperation(operation, transaction);
|
|
151
|
+
}
|
|
152
|
+
applyMigrationOperations(migrationPlan, transaction);
|
|
153
|
+
verifyGeneratedSyncInstance(targetDir);
|
|
154
|
+
const nextMetadata = decorateMetadataLifecycle(
|
|
155
|
+
decorateMetadataOwnership(buildNextSyncMetadata(metadata, syncPlan)),
|
|
156
|
+
);
|
|
157
|
+
writeTemplateMetadata(targetDir, nextMetadata, transaction);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
schemaVersion: APPLY_RESULT_SCHEMA_VERSION,
|
|
163
|
+
operation: "sync",
|
|
164
|
+
targetDir,
|
|
165
|
+
backupPath,
|
|
166
|
+
template: context.plan.template,
|
|
167
|
+
stats: {
|
|
168
|
+
updated: syncPlan.updated.length,
|
|
169
|
+
added: syncPlan.added.length,
|
|
170
|
+
skipped: syncPlan.skipped.length,
|
|
171
|
+
removed: syncPlan.removed.length,
|
|
172
|
+
forceApplied: force ? syncPlan.forceableConflicts.length : 0,
|
|
173
|
+
},
|
|
174
|
+
skipped: syncPlan.skipped.map((operation) => ({
|
|
175
|
+
path: operation.relativePath,
|
|
176
|
+
reason: operation.reason,
|
|
177
|
+
ownership: operation.ownership || null,
|
|
178
|
+
mergeStrategy: operation.mergeStrategy || null,
|
|
179
|
+
})),
|
|
180
|
+
removed: [...syncPlan.removed],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
APPLY_RESULT_SCHEMA_VERSION,
|
|
186
|
+
normalizeTargetDir,
|
|
187
|
+
inspectExistingTargetDir,
|
|
188
|
+
buildSyncContext,
|
|
189
|
+
assertSyncApplicable,
|
|
190
|
+
applySyncContext,
|
|
191
|
+
};
|
package/src/api/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { normalizeError } = require("../cli/error-output");
|
|
4
|
+
const { projectDoctor } = require("./project-doctor");
|
|
5
|
+
const { projectDiff } = require("./project-diff");
|
|
6
|
+
const { templatePlan } = require("./template-plan");
|
|
7
|
+
const { templateApply } = require("./template-apply");
|
|
8
|
+
|
|
9
|
+
const API_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
function toErrorEnvelope(error) {
|
|
12
|
+
return normalizeError(error);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
API_VERSION,
|
|
17
|
+
projectDoctor,
|
|
18
|
+
projectDiff,
|
|
19
|
+
templatePlan,
|
|
20
|
+
templateApply,
|
|
21
|
+
toErrorEnvelope,
|
|
22
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
function projectDoctor({ targetDir = ".", cwd = process.cwd() } = {}) {
|
|
6
|
+
const { buildDoctorReport } = require("../commands/doctor");
|
|
7
|
+
return buildDoctorReport(path.resolve(cwd, targetDir));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { projectDoctor };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { buildSyncContext, applySyncContext } = require("./_sync-service");
|
|
4
|
+
|
|
5
|
+
function templateApply(options = {}) {
|
|
6
|
+
const context = buildSyncContext(options);
|
|
7
|
+
return applySyncContext(context, { force: Boolean(options.force) });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { templateApply };
|
package/src/cli/args.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { HELP_FLAGS, VERSION_FLAGS } = require("./flags");
|
|
4
|
+
|
|
5
|
+
const VALUE_OPTIONS = new Map([
|
|
6
|
+
["--project-name", "projectName"],
|
|
7
|
+
["--business-domain", "businessDomain"],
|
|
8
|
+
["--team-size", "teamSize"],
|
|
9
|
+
["--target-dir", "targetDir"],
|
|
10
|
+
["--issue-tracker", "issueTracker"],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function parseArgs(argv = []) {
|
|
14
|
+
const options = {};
|
|
15
|
+
|
|
16
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
17
|
+
const current = argv[index];
|
|
18
|
+
const next = argv[index + 1];
|
|
19
|
+
|
|
20
|
+
if (VALUE_OPTIONS.has(current)) {
|
|
21
|
+
if (!next || next.startsWith("--")) {
|
|
22
|
+
throw new Error(`${current} 需要一个值`);
|
|
23
|
+
}
|
|
24
|
+
options[VALUE_OPTIONS.get(current)] = next;
|
|
25
|
+
index += 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (current === "--dry-run") {
|
|
30
|
+
options.dryRun = true;
|
|
31
|
+
} else if (current === "--plan") {
|
|
32
|
+
options.plan = true;
|
|
33
|
+
} else if (current === "--json") {
|
|
34
|
+
options.json = true;
|
|
35
|
+
} else if (current === "--apply") {
|
|
36
|
+
options.apply = true;
|
|
37
|
+
} else if (current === "--force") {
|
|
38
|
+
options.force = true;
|
|
39
|
+
} else if (current === "--git-init") {
|
|
40
|
+
options.gitInit = true;
|
|
41
|
+
} else if (current === "--include-example-docs") {
|
|
42
|
+
options.includeExampleDocs = true;
|
|
43
|
+
} else if (current === "--no-example-docs") {
|
|
44
|
+
options.includeExampleDocs = false;
|
|
45
|
+
} else if (HELP_FLAGS.has(current)) {
|
|
46
|
+
options.help = true;
|
|
47
|
+
} else if (VERSION_FLAGS.has(current)) {
|
|
48
|
+
options.version = true;
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error(`不支持的参数:${current}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return options;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
VALUE_OPTIONS,
|
|
59
|
+
parseArgs,
|
|
60
|
+
};
|