create-yss-spec 3.4.10 → 3.5.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 +2 -2
- package/package.json +1 -1
- package/src/api/_sync-service.js +111 -18
- package/src/cli/args.js +1 -0
- package/src/cli/help.js +10 -1
- package/src/cli/index.js +3 -0
- package/src/cli/prompts.js +5 -1
- package/src/cli/router.js +2 -0
- package/src/commands/attach.js +12 -2
- package/src/commands/init.js +2 -0
- package/src/commands/skills.js +253 -0
- package/src/commands/sync.js +3 -0
- package/src/family-identity.js +1 -1
- package/src/template/asset-runtime.js +220 -0
- package/src/template/distribution-runtime.js +165 -0
- package/src/template/instance-runtime.js +76 -29
- package/src/template/ownership-policy.js +17 -1
- package/src/template/prune-planner.js +6 -1
- package/src/template/sync-planner.js +3 -0
- package/src/template/verification-runtime.js +56 -1
- package/src/validation/metadata.js +24 -1
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-cache/SKILL.md +6 -5
- package/template/.agents/skills/yss-cache/references/annotations.md +3 -1
- package/template/.agents/skills/yss-cache/references/architecture.md +4 -2
- package/template/.agents/skills/yss-cache/references/configuration.md +6 -3
- package/template/.agents/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.agents/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.agents/skills/yss-cache/references/verification.md +10 -4
- package/template/.agents/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.agents/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.agents/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.agents/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.agents/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +2 -2
- package/template/.agents/skills/yss-repository/SKILL.md +1 -0
- package/template/.codex/skills/yss-cache/SKILL.md +6 -5
- package/template/.codex/skills/yss-cache/references/annotations.md +3 -1
- package/template/.codex/skills/yss-cache/references/architecture.md +4 -2
- package/template/.codex/skills/yss-cache/references/configuration.md +6 -3
- package/template/.codex/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.codex/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.codex/skills/yss-cache/references/verification.md +10 -4
- package/template/.codex/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.codex/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.codex/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.codex/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.codex/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +2 -2
- package/template/.codex/skills/yss-repository/SKILL.md +1 -0
- package/template/.cursor/skills/yss-cache/SKILL.md +6 -5
- package/template/.cursor/skills/yss-cache/references/annotations.md +3 -1
- package/template/.cursor/skills/yss-cache/references/architecture.md +4 -2
- package/template/.cursor/skills/yss-cache/references/configuration.md +6 -3
- package/template/.cursor/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.cursor/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.cursor/skills/yss-cache/references/verification.md +10 -4
- package/template/.cursor/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.cursor/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.cursor/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.cursor/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.cursor/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +2 -2
- package/template/.cursor/skills/yss-repository/SKILL.md +1 -0
- package/template/.pi/skills/yss-cache/SKILL.md +6 -5
- package/template/.pi/skills/yss-cache/references/annotations.md +3 -1
- package/template/.pi/skills/yss-cache/references/architecture.md +4 -2
- package/template/.pi/skills/yss-cache/references/configuration.md +6 -3
- package/template/.pi/skills/yss-cache/references/jetcache.md +21 -0
- package/template/.pi/skills/yss-cache/references/redis-fallback.md +5 -3
- package/template/.pi/skills/yss-cache/references/verification.md +10 -4
- package/template/.pi/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
- package/template/.pi/skills/yss-distributed-id/SKILL.md +14 -58
- package/template/.pi/skills/yss-distributed-id/agents/openai.yaml +2 -2
- package/template/.pi/skills/yss-distributed-id/references/README.md +11 -47
- package/template/.pi/skills/yss-mybatis/SKILL.md +14 -2
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +5 -4
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -8
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +2 -2
- package/template/.pi/skills/yss-repository/SKILL.md +1 -0
- package/template/README.md +2 -5
- package/template/docs/agents/skill-migrations.md +4 -0
- package/template/docs/agents/yss-skill-registry.yaml +7 -6
- package/template/docs/design/README.md +0 -1
- package/template/docs/design/design.md +2 -2
- package/template/docs/design/templates/interaction-spec-template.md +1 -1
- package/template/docs/engineering/evidence/aliyun-artifact-resolution.json +446 -0
- package/template/scripts/design-md +2 -0
- package/template/scripts/lib/design-md.mjs +230 -0
- package/template/scripts/lib/skill-registry.mjs +9 -2
- package/template/scripts/lib/skill-supply-chain.mjs +33 -14
- package/template/scripts/verify-project-instance +11 -10
- package/template/skills-lock.json +11 -26
- package/template.manifest.json +18 -3
- package/template.snapshot.json +5 -5
- package/template/.agents/skills/grill-me/SKILL.md +0 -7
- package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.agents/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.agents/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.agents/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.codex/skills/grill-me/SKILL.md +0 -7
- package/template/.codex/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.codex/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.codex/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.codex/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.cursor/skills/grill-me/SKILL.md +0 -7
- package/template/.cursor/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.cursor/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.cursor/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.cursor/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/.pi/skills/grill-me/SKILL.md +0 -7
- package/template/.pi/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.pi/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
- package/template/.pi/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
- package/template/.pi/skills/yss-distributed-id/assets/LeafConf.java +0 -36
- package/template/docs/agents/yss-plugin-dependency-contract.md +0 -45
- package/template/docs/api/.gitkeep +0 -0
- package/template/docs/api/specs/.gitkeep +0 -0
- package/template/docs/architecture/.gitkeep +0 -0
- package/template/docs/design/facts/antdv-next/1.5.2/cli-help.txt +0 -38
- package/template/docs/design/facts/antdv-next/1.5.2/component-list.json +0 -434
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/info.json +0 -246
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/semantic.json +0 -40
- package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/token.json +0 -32
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/info.json +0 -190
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/semantic.json +0 -20
- package/template/docs/design/facts/antdv-next/1.5.2/components/Button/token.json +0 -256
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/info.json +0 -460
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Input/token.json +0 -123
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/info.json +0 -646
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/semantic.json +0 -50
- package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/token.json +0 -130
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/info.json +0 -640
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Select/token.json +0 -172
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/info.json +0 -1010
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/semantic.json +0 -70
- package/template/docs/design/facts/antdv-next/1.5.2/components/Table/token.json +0 -263
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/demo-basic.json +0 -7
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/info.json +0 -321
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/semantic.json +0 -25
- package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/token.json +0 -25
- package/template/docs/design/facts/antdv-next/1.5.2/design-md.json +0 -3
- package/template/docs/design/facts/antdv-next/1.5.2/manifest.json +0 -215
- package/template/docs/design/facts/antdv-next/1.5.2/resolution-probe.json +0 -5
- package/template/docs/design/prototypes/.gitkeep +0 -1
- package/template/docs/implementation/.gitkeep +0 -1
- package/template/docs/plan/IDEATION.md +0 -96
- package/template/docs/process/PDCA-SCRUM.md +0 -10
- package/template/docs/process/harness-executive-blueprint.md +0 -10
- package/template/docs/releases/.gitkeep +0 -1
- package/template/docs/requirements/README.md +0 -85
- package/template/docs/requirements/tickets/.gitkeep +0 -1
- package/template/docs/templates/agent-brief-template.md +0 -46
- package/template/docs/templates/architecture-proposal-template.md +0 -18
- package/template/docs/templates/implementation-plan-template.md +0 -16
- package/template/docs/templates/implementation-routing-template.md +0 -350
- package/template/docs/templates/requirement-freeze-template.md +0 -63
- package/template/docs/templates/risk-register-template.md +0 -11
- package/template/docs/templates/user-story-template.md +0 -20
- package/template/docs/testing/README.md +0 -110
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
用于初始化、接管已有项目并持续同步 `yss-spec-project-template` 研发管理资产的 npm CLI。
|
|
4
4
|
|
|
5
|
-
源码候选版本:`3.
|
|
5
|
+
源码候选版本:`3.5.0`。当前固定模板身份、来源状态和摘要见 `template.snapshot.json`;CLI 运行时不会拉取模板仓库。`npm create yss-spec@latest` 获取的是实际已发布 npm 包,发布版本请以 `npm view create-yss-spec version` 为准,并用 `create-yss-spec --version` 核对当前执行版本。
|
|
6
6
|
|
|
7
7
|
## 本版模板能力
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
本版新增按阶段分发项目实例资产及 `assets ensure` 命令;旧实例沿用原有分发范围。CLI 在同一事务内刷新生成型 `skills-lock.json` 并运行项目实例校验,失败时恢复同步前状态。版本升级只完成 GitHub 源码交付,npm 发布状态仍以 registry 为准。
|
|
10
10
|
|
|
11
11
|
## 快速开始
|
|
12
12
|
|
package/package.json
CHANGED
package/src/api/_sync-service.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
|
+
const { isDeepStrictEqual } = require("node:util");
|
|
5
6
|
|
|
6
7
|
const { pathKind, targetPath, normalizeRelativePath } = require("../filesystem/path-utils");
|
|
7
8
|
const { applyManagedOperation, applyMigrationOperations } = require("../filesystem/apply-plan");
|
|
8
9
|
const { runInTransaction } = require("../filesystem/transaction-runner");
|
|
9
10
|
const { assertBundledFamily, assertTargetFamily } = require("../family-runtime");
|
|
10
|
-
const { gitDirtyWarning } = require("../git/worktree");
|
|
11
|
+
const { collectGitRoots, gitDirtyWarning } = require("../git/worktree");
|
|
11
12
|
const { unmanagedPathReason, assertTargetWorkingTreeWritable } = require("../validation/security");
|
|
12
13
|
const { buildSyncPlanFromRuntime } = require("../template/sync-planner-runtime");
|
|
13
14
|
const { buildLegacyMigrationPlan } = require("../template/migration-runtime");
|
|
@@ -15,6 +16,7 @@ const { classifyRemovedFiles } = require("../template/prune-planner");
|
|
|
15
16
|
const { decorateMetadataOwnership } = require("../template/ownership-metadata");
|
|
16
17
|
const { decorateMetadataLifecycle } = require("../template/lifecycle-metadata");
|
|
17
18
|
const {
|
|
19
|
+
readProjectSkillLock,
|
|
18
20
|
refreshGeneratedProjectInstance,
|
|
19
21
|
verifyGeneratedProjectInstance,
|
|
20
22
|
} = require("../template/verification-runtime");
|
|
@@ -47,7 +49,7 @@ function isInsideTemplateRoot(targetDir) {
|
|
|
47
49
|
);
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
function inspectExistingTargetDir(targetDir, { force = false } = {}) {
|
|
52
|
+
function inspectExistingTargetDir(targetDir, { force = false, gitRoots = null } = {}) {
|
|
51
53
|
if (isInsideTemplateRoot(targetDir)) {
|
|
52
54
|
throw new Error("目标目录不能位于模板源仓库内部");
|
|
53
55
|
}
|
|
@@ -57,6 +59,7 @@ function inspectExistingTargetDir(targetDir, { force = false } = {}) {
|
|
|
57
59
|
assertTargetWorkingTreeWritable(targetDir, {
|
|
58
60
|
force,
|
|
59
61
|
packageRoot: PACKAGE_ROOT,
|
|
62
|
+
...(gitRoots && { deps: { collectGitRoots: () => gitRoots } }),
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -75,7 +78,9 @@ function buildSyncContext({ targetDir = ".", force = false, cwd = process.cwd()
|
|
|
75
78
|
assertBundledFamily(snapshot);
|
|
76
79
|
const resolvedTargetDir = normalizeTargetDir(targetDir, cwd);
|
|
77
80
|
assertTargetFamily(resolvedTargetDir);
|
|
78
|
-
|
|
81
|
+
const gitRoots = collectGitRoots(resolvedTargetDir);
|
|
82
|
+
const securityOptions = { packageRoot: PACKAGE_ROOT, deps: { collectGitRoots: () => gitRoots } };
|
|
83
|
+
inspectExistingTargetDir(resolvedTargetDir, { force: Boolean(force), gitRoots });
|
|
79
84
|
|
|
80
85
|
const { metadata } = loadTemplateMetadata(resolvedTargetDir);
|
|
81
86
|
const identity = readTargetIdentity(resolvedTargetDir);
|
|
@@ -83,6 +88,7 @@ function buildSyncContext({ targetDir = ".", force = false, cwd = process.cwd()
|
|
|
83
88
|
resolvedTargetDir,
|
|
84
89
|
metadata,
|
|
85
90
|
identity,
|
|
91
|
+
snapshot,
|
|
86
92
|
);
|
|
87
93
|
const migrationPlan = buildLegacyMigrationPlan(
|
|
88
94
|
resolvedTargetDir,
|
|
@@ -99,9 +105,7 @@ function buildSyncContext({ targetDir = ".", force = false, cwd = process.cwd()
|
|
|
99
105
|
warning,
|
|
100
106
|
toVersion: PACKAGE_MANIFEST.version,
|
|
101
107
|
getUnmanagedReason: (operation) =>
|
|
102
|
-
operation.unsafeReason || unmanagedPathReason(resolvedTargetDir, operation.relativePath,
|
|
103
|
-
packageRoot: PACKAGE_ROOT,
|
|
104
|
-
}),
|
|
108
|
+
operation.unsafeReason || unmanagedPathReason(resolvedTargetDir, operation.relativePath, securityOptions),
|
|
105
109
|
getPathKind: (operation) => pathKind(operation.targetPath),
|
|
106
110
|
getFileHash: (operation) => fileHash(operation.targetPath),
|
|
107
111
|
});
|
|
@@ -111,7 +115,7 @@ function buildSyncContext({ targetDir = ".", force = false, cwd = process.cwd()
|
|
|
111
115
|
getPathKind: (relativePath) => pathKind(targetPath(resolvedTargetDir, relativePath)),
|
|
112
116
|
getFileHash: (relativePath) => fileHash(targetPath(resolvedTargetDir, relativePath)),
|
|
113
117
|
getUnmanagedReason: (relativePath) =>
|
|
114
|
-
unmanagedPathReason(resolvedTargetDir, relativePath,
|
|
118
|
+
unmanagedPathReason(resolvedTargetDir, relativePath, securityOptions),
|
|
115
119
|
});
|
|
116
120
|
const readmeOwnershipTransferred = syncPlan.removed.includes("README.md");
|
|
117
121
|
syncPlan.removed = syncPlan.removed.filter((relativePath) => relativePath !== "README.md");
|
|
@@ -133,6 +137,7 @@ function buildSyncContext({ targetDir = ".", force = false, cwd = process.cwd()
|
|
|
133
137
|
|
|
134
138
|
return {
|
|
135
139
|
targetDir: resolvedTargetDir,
|
|
140
|
+
snapshot,
|
|
136
141
|
metadata,
|
|
137
142
|
identity,
|
|
138
143
|
desiredOperations,
|
|
@@ -158,27 +163,109 @@ function assertSyncApplicable(context) {
|
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
|
|
166
|
+
function isVerifiedNoopCandidate(context) {
|
|
167
|
+
const { metadata, migrationPlan, syncPlan } = context;
|
|
168
|
+
if (
|
|
169
|
+
syncPlan.updated.some((operation) => operation.relativePath !== "skills-lock.json") ||
|
|
170
|
+
syncPlan.added.length > 0 ||
|
|
171
|
+
syncPlan.skipped.length > 0 ||
|
|
172
|
+
syncPlan.removed.length > 0 ||
|
|
173
|
+
syncPlan.prunable.length > 0 ||
|
|
174
|
+
syncPlan.retainedRemoved.length > 0 ||
|
|
175
|
+
syncPlan.readmeOwnershipTransferred ||
|
|
176
|
+
(syncPlan.alreadyMissing || []).length > 0 ||
|
|
177
|
+
migrationPlan.operations.length > 0
|
|
178
|
+
) return false;
|
|
179
|
+
|
|
180
|
+
const nextMetadata = decorateMetadataLifecycle(
|
|
181
|
+
decorateMetadataOwnership(buildNextSyncMetadata(metadata, syncPlan, {
|
|
182
|
+
snapshot: context.snapshot,
|
|
183
|
+
currentHashes: syncPlan.currentHashes,
|
|
184
|
+
})),
|
|
185
|
+
);
|
|
186
|
+
return isDeepStrictEqual(
|
|
187
|
+
{ ...nextMetadata, lastSyncedAt: metadata.lastSyncedAt },
|
|
188
|
+
metadata,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
161
192
|
function applySyncContext(context, { force = false, prune = false } = {}) {
|
|
162
193
|
assertSyncApplicable(context);
|
|
163
|
-
const { targetDir, metadata, migrationPlan, syncPlan } = context;
|
|
194
|
+
const { targetDir, metadata, desiredOperations, migrationPlan, syncPlan } = context;
|
|
195
|
+
if (isVerifiedNoopCandidate(context)) {
|
|
196
|
+
try {
|
|
197
|
+
verifyGeneratedProjectInstance(targetDir);
|
|
198
|
+
return {
|
|
199
|
+
schemaVersion: APPLY_RESULT_SCHEMA_VERSION,
|
|
200
|
+
operation: "sync",
|
|
201
|
+
targetDir,
|
|
202
|
+
backupPath: null,
|
|
203
|
+
template: context.plan.template,
|
|
204
|
+
stats: { updated: 0, added: 0, skipped: 0, removed: 0, forceApplied: 0, pruned: 0 },
|
|
205
|
+
skipped: [],
|
|
206
|
+
removed: [],
|
|
207
|
+
prunable: [],
|
|
208
|
+
pruned: [],
|
|
209
|
+
retainedRemoved: [],
|
|
210
|
+
ownershipTransferred: [],
|
|
211
|
+
};
|
|
212
|
+
} catch {
|
|
213
|
+
// A stale generated lock needs the normal transactional refresh and verification.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const previousSkillLock = readProjectSkillLock(targetDir);
|
|
164
217
|
const managedToApply = [
|
|
165
218
|
...syncPlan.updated,
|
|
166
219
|
...syncPlan.added,
|
|
167
220
|
...(force ? syncPlan.forceableConflicts : []),
|
|
168
221
|
];
|
|
169
222
|
syncPlan.pruned = prune ? [...syncPlan.prunable] : [];
|
|
223
|
+
const affectedPaths = [
|
|
224
|
+
...affectedPathsForManagedOperations(managedToApply),
|
|
225
|
+
...affectedPathsForMigration(migrationPlan),
|
|
226
|
+
...syncPlan.pruned,
|
|
227
|
+
"skills-lock.json",
|
|
228
|
+
TEMPLATE_METADATA_FILENAME,
|
|
229
|
+
];
|
|
170
230
|
|
|
171
231
|
const { backupPath } = runInTransaction({
|
|
172
232
|
targetDir,
|
|
173
233
|
operation: "sync",
|
|
174
|
-
affectedPaths
|
|
175
|
-
...affectedPathsForManagedOperations(managedToApply),
|
|
176
|
-
...affectedPathsForMigration(migrationPlan),
|
|
177
|
-
...syncPlan.pruned,
|
|
178
|
-
"skills-lock.json",
|
|
179
|
-
TEMPLATE_METADATA_FILENAME,
|
|
180
|
-
],
|
|
234
|
+
affectedPaths,
|
|
181
235
|
execute: (transaction) => {
|
|
236
|
+
const gitRoots = collectGitRoots(targetDir);
|
|
237
|
+
const securityOptions = { packageRoot: PACKAGE_ROOT, deps: { collectGitRoots: () => gitRoots } };
|
|
238
|
+
assertTargetWorkingTreeWritable(targetDir, { ...securityOptions, force });
|
|
239
|
+
for (const relativePath of new Set(affectedPaths)) {
|
|
240
|
+
const reason = unmanagedPathReason(targetDir, relativePath, securityOptions);
|
|
241
|
+
if (reason) throw new Error(`${relativePath}: ${reason}`);
|
|
242
|
+
const kind = pathKind(targetPath(targetDir, relativePath));
|
|
243
|
+
if (kind === "other") throw new Error(`${relativePath}: 写入前路径类型已变化为 ${kind}`);
|
|
244
|
+
if (["skills-lock.json", TEMPLATE_METADATA_FILENAME].includes(relativePath) && kind === "directory") {
|
|
245
|
+
throw new Error(`${relativePath}: 写入前路径类型已变化为 ${kind}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (!isDeepStrictEqual(loadTemplateMetadata(targetDir).metadata, metadata)) {
|
|
249
|
+
throw new Error("模板元数据在规划后已变化,请重新运行 sync");
|
|
250
|
+
}
|
|
251
|
+
for (const operation of managedToApply) {
|
|
252
|
+
const kind = pathKind(targetPath(targetDir, operation.relativePath));
|
|
253
|
+
const plannedHash = syncPlan.currentHashes[operation.relativePath];
|
|
254
|
+
if (plannedHash === undefined ? kind !== "missing" : kind !== "file" || fileHash(operation.targetPath) !== plannedHash) {
|
|
255
|
+
throw new Error(`${operation.relativePath}: 规划后目标文件已变化,请重新运行 sync`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const relativePath of syncPlan.pruned) {
|
|
259
|
+
const current = targetPath(targetDir, relativePath);
|
|
260
|
+
if (pathKind(current) !== "file" || fileHash(current) !== metadata.managedFiles[relativePath]?.contentHash) {
|
|
261
|
+
throw new Error(`${relativePath}: 待清理文件在规划后已变化,请重新运行 sync`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const currentMigration = buildLegacyMigrationPlan(targetDir, desiredOperations, { checkFlatTickets: false });
|
|
265
|
+
if (!isDeepStrictEqual(currentMigration.operations, migrationPlan.operations) ||
|
|
266
|
+
currentMigration.unsafe.length || currentMigration.conflicts.length) {
|
|
267
|
+
throw new Error("旧路径迁移在规划后已变化,请重新运行 sync");
|
|
268
|
+
}
|
|
182
269
|
for (const operation of managedToApply) {
|
|
183
270
|
applyManagedOperation(operation, transaction);
|
|
184
271
|
}
|
|
@@ -187,12 +274,18 @@ function applySyncContext(context, { force = false, prune = false } = {}) {
|
|
|
187
274
|
transaction.remove(targetPath(targetDir, relativePath));
|
|
188
275
|
}
|
|
189
276
|
verifyGeneratedSyncInstance(targetDir);
|
|
190
|
-
refreshGeneratedProjectInstance(targetDir
|
|
277
|
+
refreshGeneratedProjectInstance(targetDir, {
|
|
278
|
+
previousSkillLock,
|
|
279
|
+
previousManagedFiles: metadata.managedFiles,
|
|
280
|
+
transaction,
|
|
281
|
+
});
|
|
191
282
|
verifyGeneratedProjectInstance(targetDir);
|
|
192
283
|
const nextMetadata = decorateMetadataLifecycle(
|
|
193
|
-
decorateMetadataOwnership(buildNextSyncMetadata(metadata, syncPlan)),
|
|
284
|
+
decorateMetadataOwnership(buildNextSyncMetadata(metadata, syncPlan, { snapshot: context.snapshot })),
|
|
194
285
|
);
|
|
195
|
-
|
|
286
|
+
if (!isDeepStrictEqual({ ...nextMetadata, lastSyncedAt: metadata.lastSyncedAt }, metadata)) {
|
|
287
|
+
writeTemplateMetadata(targetDir, nextMetadata, transaction);
|
|
288
|
+
}
|
|
196
289
|
},
|
|
197
290
|
});
|
|
198
291
|
|
package/src/cli/args.js
CHANGED
package/src/cli/help.js
CHANGED
|
@@ -16,6 +16,8 @@ COMMANDS
|
|
|
16
16
|
sync 同步已有模板实例的受管资产
|
|
17
17
|
diff 只计算同步差异,不写入文件
|
|
18
18
|
doctor 检查模板实例、身份、Git 和安全状态
|
|
19
|
+
skills 按需补装 Skill 或增加 Agent 平台
|
|
20
|
+
assets 按生命周期阶段补装所需文件与 Skill
|
|
19
21
|
update 检查 npm 最新版本,如有更新则安装
|
|
20
22
|
upgrade update 的别名
|
|
21
23
|
|
|
@@ -25,6 +27,7 @@ OPTIONS
|
|
|
25
27
|
--team-size <size> 团队规模;init 不传则可留空,默认「待补充」
|
|
26
28
|
--target-dir <dir> 目标目录;init 不传则进入交互输入,sync/diff/doctor 默认为当前目录
|
|
27
29
|
--issue-tracker github|gitlab 默认 issue tracker 偏好(默认 github)
|
|
30
|
+
--agent-runtime codex|cursor|pi init/attach 必选;交互模式可在提示中选择
|
|
28
31
|
--dry-run 兼容预演模式,不写入文件
|
|
29
32
|
--plan sync:输出结构化文本计划,不写入文件
|
|
30
33
|
--json sync/diff/doctor:输出机器可读 JSON,不写入文件
|
|
@@ -34,7 +37,7 @@ OPTIONS
|
|
|
34
37
|
update:即使已是最新时仍重新安装;npx / 源码目录仍不覆盖
|
|
35
38
|
--prune sync:备份并删除与旧受管基线一致的退出分发文件
|
|
36
39
|
--git-init 初始化完成后执行 git init
|
|
37
|
-
--include-example-docs
|
|
40
|
+
--include-example-docs 显式保留示例文档(默认关闭)
|
|
38
41
|
--no-example-docs 不生成示例文档
|
|
39
42
|
-h, --help 显示本帮助信息
|
|
40
43
|
-v, --version 显示 CLI 版本
|
|
@@ -70,6 +73,12 @@ EXAMPLES
|
|
|
70
73
|
$ npx create-yss-spec@latest doctor --json
|
|
71
74
|
$ npx create-yss-spec@latest sync --target-dir . --force
|
|
72
75
|
$ npx create-yss-spec@latest sync --plan --prune
|
|
76
|
+
$ npx create-yss-spec@latest skills ensure yss-web-controller --plan
|
|
77
|
+
$ npx create-yss-spec@latest assets ensure stage.spec-architecture --plan
|
|
78
|
+
$ npx create-yss-spec@latest assets ensure stage.spec-architecture --apply
|
|
79
|
+
$ npx create-yss-spec@latest skills ensure yss-web-controller --apply
|
|
80
|
+
$ npx create-yss-spec@latest skills runtime add cursor --plan
|
|
81
|
+
$ npx create-yss-spec@latest skills runtime add cursor --apply
|
|
73
82
|
$ npx create-yss-spec@latest sync --prune
|
|
74
83
|
$ npx create-yss-spec update
|
|
75
84
|
$ npx create-yss-spec update --dry-run
|
package/src/cli/index.js
CHANGED
|
@@ -29,6 +29,7 @@ const { runDiff } = require("../commands/diff");
|
|
|
29
29
|
const { runDoctor } = require("../commands/doctor");
|
|
30
30
|
const { runInit } = require("../commands/init");
|
|
31
31
|
const { runSync } = require("../commands/sync");
|
|
32
|
+
const { runSkills, runAssets } = require("../commands/skills");
|
|
32
33
|
const { runUpdateCommand } = require("../commands/update");
|
|
33
34
|
const { printHelp, printVersion } = require("./help");
|
|
34
35
|
const { resolveCommand } = require("./router");
|
|
@@ -58,6 +59,8 @@ async function runCli(argv = []) {
|
|
|
58
59
|
if (route.command === "sync") {
|
|
59
60
|
return runSync(route.args);
|
|
60
61
|
}
|
|
62
|
+
if (route.command === "skills") return runSkills(route.args);
|
|
63
|
+
if (route.command === "assets") return runAssets(route.args);
|
|
61
64
|
if (route.command === "doctor") {
|
|
62
65
|
return runDoctor(route.args);
|
|
63
66
|
}
|
package/src/cli/prompts.js
CHANGED
|
@@ -11,8 +11,9 @@ function normalizeInteractiveOptions(options, values) {
|
|
|
11
11
|
gitInit: Boolean(options.gitInit),
|
|
12
12
|
includeExampleDocs:
|
|
13
13
|
options.includeExampleDocs === undefined
|
|
14
|
-
?
|
|
14
|
+
? false
|
|
15
15
|
: Boolean(options.includeExampleDocs),
|
|
16
|
+
...(values.agentRuntime || options.agentRuntime ? { agentRuntime: values.agentRuntime || options.agentRuntime } : {}),
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -40,12 +41,14 @@ async function promptForMissingOptions(options, deps = {}) {
|
|
|
40
41
|
: await rl.question("团队规模(可留空): ");
|
|
41
42
|
const targetDir =
|
|
42
43
|
options.targetDir || (await rl.question("目标目录: ")).trim();
|
|
44
|
+
const agentRuntime = options.agentRuntime || (await rl.question("Agent 平台(codex/cursor/pi): ")).trim();
|
|
43
45
|
|
|
44
46
|
return normalizeInteractiveOptions(options, {
|
|
45
47
|
projectName,
|
|
46
48
|
businessDomain,
|
|
47
49
|
teamSize: (teamSizeInput || "").trim() || "待补充",
|
|
48
50
|
targetDir,
|
|
51
|
+
agentRuntime,
|
|
49
52
|
});
|
|
50
53
|
} finally {
|
|
51
54
|
rl.close();
|
|
@@ -53,6 +56,7 @@ async function promptForMissingOptions(options, deps = {}) {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
async function promptFromBufferedInput(options, deps = {}) {
|
|
59
|
+
if (!options.agentRuntime) throw new Error("无交互 init 必须显式指定 --agent-runtime codex|cursor|pi");
|
|
56
60
|
const stdin = deps.stdin || process.stdin;
|
|
57
61
|
const stdout = deps.stdout || process.stdout;
|
|
58
62
|
const chunks = [];
|
package/src/cli/router.js
CHANGED
|
@@ -19,6 +19,8 @@ function resolveCommand(argv = []) {
|
|
|
19
19
|
if (head === "sync") {
|
|
20
20
|
return { command: "sync", args: rest };
|
|
21
21
|
}
|
|
22
|
+
if (head === "skills") return { command: "skills", args: rest };
|
|
23
|
+
if (head === "assets") return { command: "assets", args: rest };
|
|
22
24
|
if (head === "attach") {
|
|
23
25
|
return { command: "attach", args: rest };
|
|
24
26
|
}
|
package/src/commands/attach.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require("node:fs");
|
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
|
|
6
6
|
const { parseArgs } = require("../cli/args");
|
|
7
|
+
const { assertRuntime } = require("../template/distribution-runtime");
|
|
7
8
|
const { pathKind, targetPath, normalizeRelativePath } = require("../filesystem/path-utils");
|
|
8
9
|
const { applyManagedOperation, applyMigrationOperations } = require("../filesystem/apply-plan");
|
|
9
10
|
const { runInTransaction } = require("../filesystem/transaction-runner");
|
|
@@ -29,6 +30,8 @@ const {
|
|
|
29
30
|
} = require("../template/verification-runtime");
|
|
30
31
|
|
|
31
32
|
function assertRequiredOptions(options) {
|
|
33
|
+
if (!options.agentRuntime && !process.stdin.isTTY) throw new Error("无交互 attach 必须显式指定 --agent-runtime codex|cursor|pi");
|
|
34
|
+
if (options.agentRuntime) assertRuntime(options.agentRuntime);
|
|
32
35
|
if (!options.projectName) {
|
|
33
36
|
throw new Error("attach 需要 --project-name,项目名称不能为空");
|
|
34
37
|
}
|
|
@@ -73,8 +76,9 @@ function attachVariables(options) {
|
|
|
73
76
|
issueTracker: options.issueTracker || "github",
|
|
74
77
|
includeExampleDocs:
|
|
75
78
|
options.includeExampleDocs === undefined
|
|
76
|
-
?
|
|
79
|
+
? false
|
|
77
80
|
: Boolean(options.includeExampleDocs),
|
|
81
|
+
agentRuntime: options.agentRuntime,
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -156,8 +160,14 @@ function affectedPathsForMigration(migrationPlan) {
|
|
|
156
160
|
);
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
function runAttach(argv = []) {
|
|
163
|
+
async function runAttach(argv = []) {
|
|
160
164
|
const options = parseArgs(argv);
|
|
165
|
+
if (!options.agentRuntime && process.stdin.isTTY) {
|
|
166
|
+
const readline = require("node:readline/promises");
|
|
167
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
168
|
+
try { options.agentRuntime = (await rl.question("Agent 平台(codex/cursor/pi): ")).trim(); }
|
|
169
|
+
finally { rl.close(); }
|
|
170
|
+
}
|
|
161
171
|
if (options.prune) throw new Error("--prune 仅适用于 sync");
|
|
162
172
|
assertRequiredOptions(options);
|
|
163
173
|
|
package/src/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require("node:fs");
|
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
|
|
6
6
|
const { parseArgs } = require("../cli/args");
|
|
7
|
+
const { assertRuntime } = require("../template/distribution-runtime");
|
|
7
8
|
const { promptForMissingOptions } = require("../cli/prompts");
|
|
8
9
|
const { printHelp, printVersion } = require("../cli/help");
|
|
9
10
|
const { pathKind, normalizeRelativePath } = require("../filesystem/path-utils");
|
|
@@ -32,6 +33,7 @@ const {
|
|
|
32
33
|
} = require("../template/verification-runtime");
|
|
33
34
|
|
|
34
35
|
function assertRequiredOptions(options) {
|
|
36
|
+
assertRuntime(options.agentRuntime);
|
|
35
37
|
if (!options.projectName) {
|
|
36
38
|
throw new Error("init 需要 --project-name,项目名称不能为空");
|
|
37
39
|
}
|