ai-delivery-workflow 0.2.2 → 0.3.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 CHANGED
@@ -12,12 +12,15 @@
12
12
 
13
13
  - [维护仓库 AI 边界](AGENTS.md)
14
14
  - [完整中文项目手册](docs/PROJECT-MANUAL.zh-CN.md)
15
+ - [CLI 安装参数参考](docs/CLI-PARAMETER-REFERENCE.zh-CN.md)
15
16
  - [双仓工作区使用与维护说明](docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md)
16
17
  - [逐文件参考](docs/FILE-REFERENCE.zh-CN.md)
17
18
  - [工作流物料查看器使用指南](docs/VIEWER-USER-GUIDE.zh-CN.md)
18
19
  - [工作流物料查看器维护指南](docs/VIEWER-MAINTENANCE.zh-CN.md)
19
20
  - [正式状态 CLI 使用指南](docs/STATE-CLI-USER-GUIDE.zh-CN.md)
20
21
  - [正式状态 CLI 维护指南](docs/STATE-CLI-MAINTENANCE.zh-CN.md)
22
+ - [npm 版本与发布管理规范](docs/NPM-RELEASE-MANAGEMENT.zh-CN.md)
23
+ - [维护仓库验证工具:发布预检、成本基准与可信平台证明](docs/MAINTENANCE-VERIFICATION.zh-CN.md)
21
24
  - [审计物料:历史 V1、当前契约 V2 与五版本发布列车示例](audit/README.md)
22
25
  - [审计物料逐文件用途](audit/FILE-GUIDE.zh-CN.md)
23
26
  - [永久验证源码说明](verification/README.zh-CN.md)
@@ -107,6 +110,8 @@ npx ai-delivery-workflow@latest init . --force
107
110
  npm test
108
111
  npm run skills:validate
109
112
  npm run audit:verify
113
+ npm run benchmark:autonomy -- --iterations 5
114
+ npm run release:check -- prepare --version <version>
110
115
  ```
111
116
 
112
117
  `npm run skills:validate` 使用项目已有的 Node.js `yaml` 依赖校验全部 25 个 Skill,无需 Python 或 `PyYAML`。它检查 `SKILL.md` frontmatter、目录名称、`agents/openai.yaml` 界面元数据,以及项目手册和文件参考中的 Skill 覆盖关系;任一错误都会返回非零退出码。该校验只属于本维护仓库,不会安装到业务项目。
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+ import { performance } from "node:perf_hooks";
9
+ import { fileURLToPath } from "node:url";
10
+ import { bootstrapProject } from "../lib/project-bootstrap.mjs";
11
+ import { initProject } from "../lib/project-installer.mjs";
12
+
13
+ const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
14
+
15
+ function parseArguments(argv) {
16
+ const options = { iterations: 5, output: null };
17
+ while (argv.length) {
18
+ const key = argv.shift();
19
+ if (!new Set(["--iterations", "--output"]).has(key)) throw new Error(`unknown option: ${key}`);
20
+ const value = argv.shift();
21
+ if (!value || value.startsWith("--")) throw new Error(`missing value for ${key}`);
22
+ options[key.slice(2)] = key === "--iterations" ? Number(value) : path.resolve(value);
23
+ }
24
+ if (!Number.isInteger(options.iterations) || options.iterations < 1 || options.iterations > 100) {
25
+ throw new Error("--iterations must be an integer from 1 to 100");
26
+ }
27
+ return options;
28
+ }
29
+
30
+ function sha256(value) {
31
+ return crypto.createHash("sha256").update(value).digest("hex");
32
+ }
33
+
34
+ function filesUnder(directory) {
35
+ if (!fs.existsSync(directory)) return [];
36
+ return fs.readdirSync(directory, { withFileTypes: true })
37
+ .flatMap((entry) => {
38
+ const absolute = path.join(directory, entry.name);
39
+ return entry.isDirectory() ? filesUnder(absolute) : [absolute];
40
+ })
41
+ .sort((left, right) => left.localeCompare(right));
42
+ }
43
+
44
+ function durableSnapshot(project) {
45
+ const workflowRoot = path.join(project, ".workflow");
46
+ const files = filesUnder(workflowRoot);
47
+ const hash = crypto.createHash("sha256");
48
+ let bytes = 0;
49
+ for (const file of files) {
50
+ const relative = path.relative(workflowRoot, file).replaceAll("\\", "/");
51
+ const content = fs.readFileSync(file);
52
+ bytes += content.length;
53
+ hash.update(`${relative}\0${content.length}\0`);
54
+ hash.update(content);
55
+ }
56
+ return { checksum: hash.digest("hex"), file_count: files.length, bytes };
57
+ }
58
+
59
+ function withoutVolatileFields(value) {
60
+ if (Array.isArray(value)) return value.map(withoutVolatileFields);
61
+ if (!value || typeof value !== "object") return value;
62
+ return Object.fromEntries(Object.entries(value)
63
+ .filter(([key]) => key !== "detected_at")
64
+ .map(([key, child]) => [key, withoutVolatileFields(child)]));
65
+ }
66
+
67
+ function runChecked(program, args, cwd) {
68
+ const result = spawnSync(program, args, { cwd, encoding: "utf8", windowsHide: true });
69
+ if (result.status !== 0) {
70
+ throw new Error(`${path.basename(program)} ${args.join(" ")} failed: ${result.stderr || result.stdout}`);
71
+ }
72
+ return result.stdout.trim();
73
+ }
74
+
75
+ function prepareRecoveryCheckpoint(project) {
76
+ const codeRoot = path.join(project, "code");
77
+ runChecked("git", ["config", "user.email", "benchmark@example.invalid"], codeRoot);
78
+ runChecked("git", ["config", "user.name", "Autonomy Benchmark"], codeRoot);
79
+ fs.writeFileSync(path.join(codeRoot, "README.md"), "autonomy benchmark\n", "utf8");
80
+ runChecked("git", ["add", "README.md"], codeRoot);
81
+ runChecked("git", ["commit", "-m", "initialize benchmark code"], codeRoot);
82
+ const taskCli = path.join(
83
+ project, ".agents", "skills", "ai-delivery-checkpoint-task", "scripts", "task-state.mjs",
84
+ );
85
+ const stateRoot = ["--state-root", project];
86
+ runChecked(process.execPath, [
87
+ taskCli, "init",
88
+ "--task-id", "TASK-BENCHMARK-RECOVERY",
89
+ "--iteration-id", "BENCHMARK-001",
90
+ "--node", "01-product-discovery",
91
+ "--skill", "ai-delivery-define-product",
92
+ "--goal", "Resume the recorded product discovery question",
93
+ "--branch", "main",
94
+ "--worktree", codeRoot,
95
+ "--status", "running",
96
+ "--next-action", "Ask the recorded business question",
97
+ "--next-action-owner", "agent",
98
+ ...stateRoot,
99
+ ], codeRoot);
100
+ runChecked(process.execPath, [
101
+ taskCli, "finish",
102
+ "--task-id", "TASK-BENCHMARK-RECOVERY",
103
+ "--status", "blocked",
104
+ "--blocker", "Waiting for a business answer",
105
+ "--next-action", "Confirm the primary business problem",
106
+ "--next-action-owner", "user",
107
+ ...stateRoot,
108
+ ], codeRoot);
109
+ }
110
+
111
+ function runAutonomyBenchmark(options) {
112
+ const packageVersion = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version;
113
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), "ai-delivery-autonomy-benchmark-"));
114
+ try {
115
+ const bootstrapStarted = performance.now();
116
+ const bootstrap = bootstrapProject(packageRoot, project, {
117
+ packageVersion,
118
+ initProject,
119
+ });
120
+ const bootstrapElapsed = performance.now() - bootstrapStarted;
121
+ prepareRecoveryCheckpoint(project);
122
+ const before = durableSnapshot(project);
123
+ const samples = [];
124
+ for (let index = 0; index < options.iterations; index += 1) {
125
+ const started = performance.now();
126
+ const route = bootstrapProject(packageRoot, project, { packageVersion });
127
+ const elapsed = performance.now() - started;
128
+ const inspection = route.inspection;
129
+ const payload = Buffer.from(JSON.stringify(route), "utf8");
130
+ const semanticPayload = Buffer.from(JSON.stringify(withoutVolatileFields(route)), "utf8");
131
+ samples.push({
132
+ sequence: index + 1,
133
+ elapsed_ms: Number(elapsed.toFixed(3)),
134
+ state: inspection.state,
135
+ recommended_skill: inspection.recommendation?.skill || null,
136
+ recommended_task_id: inspection.runtime?.recommended_task_id || null,
137
+ next_action_owner: inspection.runtime?.next_action_owner || null,
138
+ deferred_to_user: route.deferred_to_user,
139
+ context_bytes: payload.length,
140
+ context_sha256: sha256(payload),
141
+ semantic_context_sha256: sha256(semanticPayload),
142
+ });
143
+ }
144
+ const after = durableSnapshot(project);
145
+ const semanticChecksums = new Set(samples.map((sample) => sample.semantic_context_sha256));
146
+ const invariants = {
147
+ durable_checksum_stable: before.checksum === after.checksum,
148
+ durable_file_count_stable: before.file_count === after.file_count,
149
+ semantic_context_checksum_stable: semanticChecksums.size === 1,
150
+ durable_growth_bytes: after.bytes - before.bytes,
151
+ };
152
+ const decision = Object.entries(invariants).every(([key, value]) =>
153
+ key === "durable_growth_bytes" ? value === 0 : value === true) ? "pass" : "fail";
154
+ return {
155
+ schema_version: 1,
156
+ benchmark: "autonomous-bootstrap-and-recovery-inspection",
157
+ generated_at: new Date().toISOString(),
158
+ workflow_version: packageVersion,
159
+ environment: { node: process.version, platform: process.platform, architecture: process.arch },
160
+ methodology: {
161
+ llm_token_usage: "unavailable",
162
+ context_cost_proxy: "UTF-8 byte length of bootstrapProject recovery-route JSON",
163
+ volatile_fields_excluded_from_semantic_checksum: ["detected_at"],
164
+ durable_scope: ".workflow/ after first Bootstrap and a user-owned checkpoint",
165
+ },
166
+ bootstrap: {
167
+ elapsed_ms: Number(bootstrapElapsed.toFixed(3)),
168
+ initial_state: bootstrap.initial_state,
169
+ resulting_state: bootstrap.resulting_state,
170
+ initialized: bootstrap.initialized,
171
+ artifacts_written: bootstrap.artifacts_written,
172
+ durable_after: before,
173
+ },
174
+ recovery: {
175
+ route: "bootstrapProject-user-owned-checkpoint",
176
+ iterations: options.iterations,
177
+ elapsed_total_ms: Number(samples.reduce((total, sample) => total + sample.elapsed_ms, 0).toFixed(3)),
178
+ context_bytes_min: Math.min(...samples.map((sample) => sample.context_bytes)),
179
+ context_bytes_max: Math.max(...samples.map((sample) => sample.context_bytes)),
180
+ samples,
181
+ },
182
+ durable_before_recovery: before,
183
+ durable_after_recovery: after,
184
+ invariants,
185
+ decision,
186
+ limitations: [
187
+ "The Codex runtime does not expose per-subagent LLM token usage to this repository command.",
188
+ "Elapsed time is environment-dependent and is recorded as an observation, not a release threshold.",
189
+ ],
190
+ };
191
+ } finally {
192
+ fs.rmSync(project, { recursive: true, force: true });
193
+ }
194
+ }
195
+
196
+ try {
197
+ const options = parseArguments(process.argv.slice(2));
198
+ const report = runAutonomyBenchmark(options);
199
+ const text = `${JSON.stringify(report, null, 2)}\n`;
200
+ if (options.output) {
201
+ fs.mkdirSync(path.dirname(options.output), { recursive: true });
202
+ fs.writeFileSync(options.output, text, "utf8");
203
+ }
204
+ process.stdout.write(text);
205
+ if (report.decision !== "pass") process.exitCode = 1;
206
+ } catch (error) {
207
+ process.stderr.write(`${error.message}\n`);
208
+ process.exitCode = 2;
209
+ }
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+
8
+ function parseArguments(argv) {
9
+ const phase = argv.shift();
10
+ if (!new Set(["prepare", "verify"]).has(phase)) {
11
+ throw new Error("usage: release-check.mjs <prepare|verify> [--root path] [--version x.y.z] [--commit sha] [--scope local|full]");
12
+ }
13
+ const values = { phase, root: process.cwd(), version: null, commit: null, scope: "full" };
14
+ while (argv.length) {
15
+ const key = argv.shift();
16
+ if (!new Set(["--root", "--version", "--commit", "--scope"]).has(key)) {
17
+ throw new Error(`unknown option: ${key}`);
18
+ }
19
+ const value = argv.shift();
20
+ if (!value || value.startsWith("--")) throw new Error(`missing value for ${key}`);
21
+ values[key.slice(2)] = value;
22
+ }
23
+ if (!new Set(["local", "full"]).has(values.scope)) throw new Error("--scope must be local or full");
24
+ values.root = path.resolve(values.root);
25
+ return values;
26
+ }
27
+
28
+ function executable(name) {
29
+ return name;
30
+ }
31
+
32
+ function windowsNpmCli(name) {
33
+ const cliName = `${name}-cli.js`;
34
+ const candidates = [path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", cliName)];
35
+ const located = spawnSync("where.exe", [`${name}.cmd`], {
36
+ encoding: "utf8", windowsHide: true,
37
+ });
38
+ if (located.status === 0) {
39
+ for (const entry of located.stdout.split(/\r?\n/).filter(Boolean)) {
40
+ candidates.push(path.join(path.dirname(entry.trim()), "node_modules", "npm", "bin", cliName));
41
+ }
42
+ }
43
+ return candidates.find((candidate) => fs.existsSync(candidate)) || null;
44
+ }
45
+
46
+ function command(root, program, args, options = {}) {
47
+ if (process.platform === "win32" && new Set(["npm", "npx"]).has(program)) {
48
+ const cli = windowsNpmCli(program);
49
+ if (!cli) return { ok: false, status: null, stdout: "", stderr: `cannot locate ${program} CLI` };
50
+ args = [cli, ...args];
51
+ program = process.execPath;
52
+ }
53
+ const result = spawnSync(program, args, {
54
+ cwd: root,
55
+ encoding: "utf8",
56
+ windowsHide: true,
57
+ timeout: options.timeout || 10 * 60 * 1000,
58
+ env: options.env || process.env,
59
+ });
60
+ return {
61
+ ok: result.status === 0 && !result.error,
62
+ status: result.status,
63
+ stdout: result.stdout?.trim() || "",
64
+ stderr: result.stderr?.trim() || result.error?.message || "",
65
+ };
66
+ }
67
+
68
+ function readJson(file) {
69
+ return JSON.parse(fs.readFileSync(file, "utf8"));
70
+ }
71
+
72
+ function safeDetail(value) {
73
+ return String(value || "").slice(-2000);
74
+ }
75
+
76
+ function createRecorder(checks) {
77
+ return async (id, execute) => {
78
+ try {
79
+ const detail = await execute();
80
+ checks.push({ id, status: "pass", ...(detail === undefined ? {} : { detail }) });
81
+ } catch (error) {
82
+ checks.push({ id, status: "fail", detail: safeDetail(error.message) });
83
+ }
84
+ };
85
+ }
86
+
87
+ function assertCommand(result, label) {
88
+ if (!result.ok) throw new Error(`${label} failed: ${result.stderr || result.stdout || `exit ${result.status}`}`);
89
+ return result.stdout;
90
+ }
91
+
92
+ function requireVersion(value) {
93
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value || "")) {
94
+ throw new Error(`invalid release version: ${value || "<missing>"}`);
95
+ }
96
+ }
97
+
98
+ function validatePackageDryRun(root, result, manifest, label) {
99
+ assertCommand(result, label);
100
+ const warnings = `${result.stdout}\n${result.stderr}`;
101
+ if (/auto-corrected|invalid and removed/i.test(warnings)) {
102
+ throw new Error(`${label} reported package metadata correction warnings`);
103
+ }
104
+ const parsed = JSON.parse(result.stdout);
105
+ const packageResult = Array.isArray(parsed) ? parsed[0] : parsed;
106
+ if (!packageResult || packageResult.error || !Array.isArray(packageResult.files)) {
107
+ throw new Error(`${label} returned no package file inventory`);
108
+ }
109
+ const files = new Set(packageResult.files.map((entry) => entry.path.replaceAll("\\", "/")));
110
+ if (packageResult.name !== manifest.name || packageResult.version !== manifest.version) {
111
+ throw new Error(`${label} returned an unexpected package identity`);
112
+ }
113
+ if (packageResult.id !== `${manifest.name}@${manifest.version}`) {
114
+ throw new Error(`${label} returned an unexpected package ID: ${packageResult.id || "missing"}`);
115
+ }
116
+ if (!files.size || packageResult.entryCount !== files.size) {
117
+ throw new Error(`${label} returned an inconsistent file count`);
118
+ }
119
+ if (!Number.isInteger(packageResult.size) || packageResult.size <= 0
120
+ || !Number.isInteger(packageResult.unpackedSize) || packageResult.unpackedSize <= 0
121
+ || !/^[a-f0-9]{40}$/i.test(packageResult.shasum || "")
122
+ || !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(packageResult.integrity || "")) {
123
+ throw new Error(`${label} returned incomplete package digests or sizes`);
124
+ }
125
+ for (const includedRoot of manifest.files || []) {
126
+ if (![...files].some((file) => file === includedRoot || file.startsWith(`${includedRoot}/`))) {
127
+ throw new Error(`${label} omitted declared package root: ${includedRoot}`);
128
+ }
129
+ }
130
+ for (const [name, binPath] of Object.entries(manifest.bin || {})) {
131
+ const normalized = binPath.replace(/^\.\//, "").replaceAll("\\", "/");
132
+ if (!files.has(normalized)) throw new Error(`${label} omitted bin.${name}: ${normalized}`);
133
+ }
134
+ if (manifest.name === "ai-delivery-workflow") {
135
+ if (manifest.bin?.["ai-delivery"] !== "bin/ai-delivery.mjs") {
136
+ throw new Error(`${label} requires bin.ai-delivery to be bin/ai-delivery.mjs`);
137
+ }
138
+ const executableContent = fs.readFileSync(path.join(root, manifest.bin["ai-delivery"]), "utf8");
139
+ if (!executableContent.startsWith("#!/usr/bin/env node")) {
140
+ throw new Error("bin.ai-delivery lacks a Node shebang: bin/ai-delivery.mjs");
141
+ }
142
+ for (const requiredRoot of ["bin", "docs", "lib", "skills"]) {
143
+ if (![...files].some((file) => file.startsWith(`${requiredRoot}/`))) {
144
+ throw new Error(`${label} omitted required release root: ${requiredRoot}`);
145
+ }
146
+ }
147
+ const excluded = [...files].filter((file) => /^(?:\.tmp|verification|audit)(?:\/|$)/.test(file));
148
+ if (excluded.length) throw new Error(`${label} included maintenance-only files: ${excluded.join(", ")}`);
149
+ }
150
+ return packageResult;
151
+ }
152
+
153
+ function runPackageDryRun(root, manifest, args, label) {
154
+ const cache = fs.mkdtempSync(path.join(os.tmpdir(), "ai-delivery-npm-cache-"));
155
+ try {
156
+ const result = command(root, executable("npm"), [...args, "--json"], {
157
+ env: { ...process.env, npm_config_cache: cache },
158
+ });
159
+ return validatePackageDryRun(root, result, manifest, label);
160
+ } finally {
161
+ fs.rmSync(cache, { recursive: true, force: true });
162
+ }
163
+ }
164
+
165
+ function samePackageDryRun(left, right) {
166
+ const summarize = (value) => ({
167
+ name: value.name,
168
+ version: value.version,
169
+ entryCount: value.entryCount,
170
+ shasum: value.shasum,
171
+ integrity: value.integrity,
172
+ files: value.files.map((entry) => entry.path.replaceAll("\\", "/")).sort(),
173
+ });
174
+ return JSON.stringify(summarize(left)) === JSON.stringify(summarize(right));
175
+ }
176
+
177
+ async function prepare(options, context) {
178
+ const { root, version, scope } = options;
179
+ const { manifest, lock, record, checks } = context;
180
+ await record("package-version", () => {
181
+ if (manifest.version !== version) throw new Error(`package.json is ${manifest.version}; expected ${version}`);
182
+ return version;
183
+ });
184
+ await record("package-lock-version", () => {
185
+ if (lock.version !== version || lock.packages?.[""]?.version !== version) {
186
+ throw new Error(`package-lock.json does not consistently declare ${version}`);
187
+ }
188
+ return version;
189
+ });
190
+ await record("git-main", () => {
191
+ const branch = assertCommand(command(root, "git", ["branch", "--show-current"]), "git branch");
192
+ if (branch !== "main") throw new Error(`release preparation requires main; current branch is ${branch || "detached"}`);
193
+ return branch;
194
+ });
195
+ await record("git-clean", () => {
196
+ const status = assertCommand(command(root, "git", ["status", "--porcelain=v1"]), "git status");
197
+ if (status) throw new Error(`worktree is not clean:\n${status}`);
198
+ return "clean";
199
+ });
200
+ await record("git-tag-available", () => {
201
+ const result = command(root, "git", ["rev-parse", "-q", "--verify", `refs/tags/v${version}`]);
202
+ if (result.ok) throw new Error(`tag v${version} already exists at ${result.stdout}`);
203
+ return `v${version}`;
204
+ });
205
+ const origin = command(root, "git", ["remote", "get-url", "origin"]);
206
+ await record("git-origin-main", () => {
207
+ if (!origin.ok) {
208
+ if (scope === "full") throw new Error("release preparation requires an origin remote");
209
+ return "not-configured-local-scope";
210
+ }
211
+ assertCommand(command(root, "git", ["fetch", "--quiet", "origin", "main"]), "git fetch origin main");
212
+ const counts = assertCommand(
213
+ command(root, "git", ["rev-list", "--left-right", "--count", "origin/main...HEAD"]),
214
+ "git compare origin/main",
215
+ ).split(/\s+/).map(Number);
216
+ if (counts[0] !== 0 || counts[1] !== 0) {
217
+ throw new Error(`main differs from origin/main: behind=${counts[0]} ahead=${counts[1]}`);
218
+ }
219
+ return "synchronized";
220
+ });
221
+ await record("git-remote-tag-available", () => {
222
+ if (!origin.ok) {
223
+ if (scope === "full") throw new Error("release preparation requires an origin remote");
224
+ return "not-configured-local-scope";
225
+ }
226
+ const remoteTag = command(root, "git", [
227
+ "ls-remote", "--exit-code", "--tags", "origin", `refs/tags/v${version}`,
228
+ ]);
229
+ if (remoteTag.ok && remoteTag.stdout) throw new Error(`remote tag v${version} already exists`);
230
+ if (remoteTag.status !== 2) throw new Error(`could not inspect remote tag: ${remoteTag.stderr || remoteTag.stdout}`);
231
+ return `v${version}`;
232
+ });
233
+ for (const [id, script] of [
234
+ ["tests", "test"],
235
+ ["skills", "skills:validate"],
236
+ ["audit", "audit:verify"],
237
+ ]) {
238
+ await record(id, () => {
239
+ if (!manifest.scripts?.[script]) throw new Error(`missing npm script: ${script}`);
240
+ assertCommand(command(root, executable("npm"), ["run", script]), `npm run ${script}`);
241
+ return script;
242
+ });
243
+ }
244
+ let packDryRun;
245
+ await record("npm-pack-dry-run", () => {
246
+ packDryRun = runPackageDryRun(root, manifest, ["pack", "--dry-run"], "npm pack --dry-run");
247
+ return `${manifest.name}@${version}; files=${packDryRun.entryCount}; integrity=${packDryRun.integrity}`;
248
+ });
249
+
250
+ if (scope === "full") {
251
+ const registry = "https://registry.npmjs.org/";
252
+ await record("npm-registry", () => {
253
+ if (manifest.publishConfig?.registry !== registry) {
254
+ throw new Error(`publishConfig.registry must be ${registry}`);
255
+ }
256
+ return registry;
257
+ });
258
+ await record("npm-publish-dry-run", () => {
259
+ const publishDryRun = runPackageDryRun(
260
+ root, manifest, ["publish", "--dry-run", `--registry=${registry}`], "npm publish --dry-run",
261
+ );
262
+ if (!packDryRun || !samePackageDryRun(packDryRun, publishDryRun)) {
263
+ throw new Error("npm publish dry-run differs from npm pack dry-run");
264
+ }
265
+ return `${manifest.name}@${version}; files=${publishDryRun.entryCount}; integrity=${publishDryRun.integrity}`;
266
+ });
267
+ await record("npm-version-available", () => {
268
+ const result = command(root, executable("npm"), [
269
+ "view", `${manifest.name}@${version}`, "version", "--json", `--registry=${registry}`,
270
+ ]);
271
+ if (result.ok && result.stdout && result.stdout !== "null") {
272
+ throw new Error(`${manifest.name}@${version} is already published`);
273
+ }
274
+ const missing = /E404|404 Not Found|is not in this registry/i.test(`${result.stdout}\n${result.stderr}`);
275
+ if (!missing) throw new Error(`could not confirm npm version availability: ${result.stderr || result.stdout}`);
276
+ return "available";
277
+ });
278
+ await record("npm-whoami", () => assertCommand(command(root, executable("npm"), [
279
+ "whoami", `--registry=${registry}`,
280
+ ]), "npm whoami"));
281
+ await record("npm-two-factor-auth", () => {
282
+ const output = assertCommand(command(root, executable("npm"), [
283
+ "profile", "get", "--json", `--registry=${registry}`,
284
+ ]), "npm profile get");
285
+ const profile = JSON.parse(output);
286
+ const mode = profile.tfa?.mode || profile.tfa || profile.twoFactorAuth || profile["two-factor auth"];
287
+ if (!/auth-and-writes/i.test(String(mode))) {
288
+ throw new Error(`npm 2FA must be auth-and-writes; reported ${JSON.stringify(mode ?? null)}`);
289
+ }
290
+ return "auth-and-writes";
291
+ });
292
+ }
293
+ return checks;
294
+ }
295
+
296
+ async function verify(options, context) {
297
+ const { root, version, commit: requestedCommit, scope } = options;
298
+ const { manifest, lock, record, checks } = context;
299
+ const head = assertCommand(command(root, "git", ["rev-parse", "HEAD"]), "git rev-parse HEAD");
300
+ const expectedCommit = requestedCommit || head;
301
+ await record("package-version", () => {
302
+ if (manifest.version !== version || lock.version !== version || lock.packages?.[""]?.version !== version) {
303
+ throw new Error(`package metadata does not consistently declare ${version}`);
304
+ }
305
+ return version;
306
+ });
307
+ await record("git-tag-identity", () => {
308
+ const tagCommit = assertCommand(
309
+ command(root, "git", ["rev-parse", `${`v${version}`}^{commit}`]),
310
+ `git tag v${version}`,
311
+ );
312
+ if (tagCommit !== expectedCommit) throw new Error(`v${version} points to ${tagCommit}; expected ${expectedCommit}`);
313
+ return tagCommit;
314
+ });
315
+
316
+ if (scope === "full") {
317
+ const registry = "https://registry.npmjs.org/";
318
+ await record("npm-metadata", () => {
319
+ const output = assertCommand(command(root, executable("npm"), [
320
+ "view", `${manifest.name}@${version}`, "version", "dist-tags", "bin", "gitHead", "--json",
321
+ `--registry=${registry}`,
322
+ ]), "npm view");
323
+ const metadata = JSON.parse(output);
324
+ if (metadata.version !== version) throw new Error(`registry returned version ${metadata.version}`);
325
+ if (metadata["dist-tags"]?.latest !== version) throw new Error(`npm latest is ${metadata["dist-tags"]?.latest || "missing"}`);
326
+ if (JSON.stringify(metadata.bin || {}) !== JSON.stringify(manifest.bin || {})) {
327
+ throw new Error("published bin entry differs from package.json");
328
+ }
329
+ if (metadata.gitHead && metadata.gitHead !== expectedCommit) {
330
+ throw new Error(`published gitHead is ${metadata.gitHead}; expected ${expectedCommit}`);
331
+ }
332
+ return `${manifest.name}@${metadata.version}`;
333
+ });
334
+ await record("external-npx-smoke", () => {
335
+ const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "ai-delivery-npx-smoke-"));
336
+ try {
337
+ const output = assertCommand(command(temporary, executable("npx"), [
338
+ "--yes", `--registry=${registry}`, `${manifest.name}@${version}`, "--version",
339
+ ]), "external npx smoke");
340
+ if (output.trim() !== version) throw new Error(`external npx reported ${output || "no version"}`);
341
+ return output.trim();
342
+ } finally {
343
+ fs.rmSync(temporary, { recursive: true, force: true });
344
+ }
345
+ });
346
+ }
347
+ return checks;
348
+ }
349
+
350
+ async function runReleaseCheck(options) {
351
+ const manifestFile = path.join(options.root, "package.json");
352
+ const lockFile = path.join(options.root, "package-lock.json");
353
+ if (!fs.existsSync(manifestFile) || !fs.existsSync(lockFile)) {
354
+ throw new Error("release root must contain package.json and package-lock.json");
355
+ }
356
+ const manifest = readJson(manifestFile);
357
+ const lock = readJson(lockFile);
358
+ const version = options.version || manifest.version;
359
+ requireVersion(version);
360
+ const checks = [];
361
+ const context = { manifest, lock, checks, record: createRecorder(checks) };
362
+ if (options.phase === "prepare") await prepare({ ...options, version }, context);
363
+ else await verify({ ...options, version }, context);
364
+ return {
365
+ schema_version: 1,
366
+ decision: checks.every((check) => check.status === "pass") ? "pass" : "fail",
367
+ phase: options.phase,
368
+ scope: options.scope,
369
+ package: { name: manifest.name, version },
370
+ checks,
371
+ };
372
+ }
373
+
374
+ try {
375
+ const report = await runReleaseCheck(parseArguments(process.argv.slice(2)));
376
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
377
+ if (report.decision !== "pass") process.exitCode = 1;
378
+ } catch (error) {
379
+ process.stderr.write(`${error.message}\n`);
380
+ process.exitCode = 2;
381
+ }
@@ -0,0 +1,204 @@
1
+ # CLI 安装参数参考
2
+
3
+ 本文说明 `ai-delivery-workflow` 安装、检查和自主引导相关的顶层 CLI 参数。它适用于安装后的业务项目,不用于初始化本维护仓库。正式状态和自进化子命令分别见[正式状态 CLI 使用指南](STATE-CLI-USER-GUIDE.zh-CN.md)与[自进化使用指南](EVOLUTION-USER-GUIDE.zh-CN.md)。
4
+
5
+ ## 1. 调用格式
6
+
7
+ ```text
8
+ ai-delivery init [workspace-directory] [--workspace-id id] [--code-path code] [--code-id app]
9
+ [--code-remote url] [--workflow-remote url] [--shared-remote url]
10
+ [--dry-run] [--force]
11
+ ai-delivery inspect [workspace-directory]
12
+ ai-delivery bootstrap [workspace-directory] [--dry-run] [--force]
13
+ ai-delivery doctor [workspace-directory]
14
+ ai-delivery --help
15
+ ai-delivery --version
16
+ ```
17
+
18
+ 从 npm 调用时,`ai-delivery` 由 `npx ai-delivery-workflow@<版本>` 提供:
19
+
20
+ ```powershell
21
+ npx ai-delivery-workflow@latest init .
22
+ npx ai-delivery-workflow@0.3.0 doctor .
23
+ ```
24
+
25
+ `@latest` 表示 npm 的当前稳定 dist-tag;固定版本用于可重复安装。`.` 表示当前目录。PowerShell 行末反引号 `` ` `` 只是续行符,不是 CLI 参数。
26
+
27
+ ## 2. 命令
28
+
29
+ | 命令 | 作用 | 是否可能写入 |
30
+ | --- | --- | --- |
31
+ | `init` | 安装或升级 25 个项目级 Skill、Hook、自包含 CLI、查看器和工作流目录,并初始化或绑定两套独立 Git。 | 是 |
32
+ | `inspect` | 只读识别工作区、两套 Git、安装完整性、正式状态和任务恢复建议。 | 否 |
33
+ | `bootstrap` | 根据只读检查决定初始化缺失工作流、恢复 Agent 责任任务或停在用户/外部输入边界。 | 视路由而定 |
34
+ | `doctor` | 校验受管文件 checksum、Skill、Hook、CLI、目录和项目配置;查看器缺失只警告。 | 否 |
35
+ | `state` | 进入正式状态 CLI;参数不由本页的顶层解析器处理。 | 取决于子命令 |
36
+ | `evolve` | 进入项目级自进化 CLI;参数不由本页的顶层解析器处理。 | 取决于子命令 |
37
+ | `help` 或 `--help` | 输出顶层帮助。 | 否 |
38
+ | `--version` | 输出 npm 包版本。 | 否 |
39
+
40
+ 省略命令时顶层 CLI 默认使用 `init`,但为了审阅和日志清晰,安装命令应显式写出 `init`。
41
+
42
+ ## 3. 位置参数
43
+
44
+ ### 3.1 `workspace-directory`
45
+
46
+ - 适用命令:`init`、`inspect`、`bootstrap`、`doctor`。
47
+ - 含义:目标业务项目根目录,也是外层工作流 Git 根目录。
48
+ - 默认值:当前工作目录。
49
+ - 可以使用相对路径或绝对路径;CLI 会转换为绝对路径。
50
+ - 目标目录必须存在,并且不得是用户主目录、全局 `.codex` 或 `.agents` 目录。
51
+ - 当前维护仓库不是安装目标,不得在仓库根执行面向业务项目的 `init` 或 `bootstrap`。
52
+
53
+ ## 4. `init` 值参数
54
+
55
+ | 参数 | 含义 | 默认值或省略行为 |
56
+ | --- | --- | --- |
57
+ | `--workspace-id <id>` | 工作区稳定身份,并生成外层工作流分支 `workflow/<id>`。 | 新项目根据目录名推导;已有项目保留配置值。 |
58
+ | `--code-path <directory>` | 内层代码 Git 在工作区中的直接子目录。 | 新项目为 `code`;已有项目保留配置值。 |
59
+ | `--code-id <id>` | 代码仓库逻辑身份 `repo_id`,供任务、版本、Tag、候选和发布物料追踪。 | 新项目为 `app`;已有项目保留配置值。 |
60
+ | `--code-remote <url-or-path>` | 代码 Git 的 `origin`;代码目录不存在且远程非空时克隆 `main`。 | 不配置远程。 |
61
+ | `--workflow-remote <url-or-path>` | 外层工作流 Git 的 `origin`,使用 `workflow/<workspace-id>` 分支。 | 不配置远程。 |
62
+ | `--shared-remote <url-or-path>` | 让两个独立 Git 根使用同一 `origin`,但仍保持不同分支命名空间和提交历史。 | 不配置远程。 |
63
+
64
+ 值参数后必须紧跟非空值。未知的 `--option` 或缺失值会被拒绝。
65
+
66
+ ### 4.1 `--workspace-id`
67
+
68
+ - 必须为 1 至 64 个字符;
69
+ - 只能包含小写 ASCII 字母、数字、点、下划线和连字符;
70
+ - 必须以字母或数字开头、结尾;
71
+ - 合法示例:`tasklite`、`team-duty-v2`、`product_01`;
72
+ - 非法示例:`TeamDuty`、`-tasklite`、包含中文或空格的 ID。
73
+
74
+ 省略时,安装器把目录名转为小写,将不支持的字符替换为连字符并去除首尾连字符;结果仍不合法时停止。工作区 ID 是 Git 分支身份,进入共享历史后不应随意改变。
75
+
76
+ ### 4.2 `--code-path`
77
+
78
+ - 必须是相对于工作区根的一个直接子目录;
79
+ - 不接受绝对路径、`.`、`..` 或 `apps/code` 等多层路径;
80
+ - 不能使用 `.git`、`.workflow`、`.ai-delivery`、`.agents`、`.codex` 等保留名称;
81
+ - 不能指向符号链接;
82
+ - 目录非空时必须已经是以该目录为根的独立 Git 仓库;
83
+ - 不支持 Git submodule、linked worktree,且不能与外层工作流 Git 共用 Git common directory。
84
+
85
+ 外层 `.gitignore` 会精确忽略该目录。代码仓库不跟踪父目录,工作流仓库也不跟踪代码内容。
86
+
87
+ ### 4.3 `--code-id`
88
+
89
+ `code-id` 是逻辑身份,不是目录、远程或分支名称。当前 CLI 要求提供非空值,但不额外限制字符集;为保证 YAML、CSV、Tag 和跨系统追踪稳定,建议使用小写 ASCII 字母、数字和连字符,例如 `tasklite-app`。
90
+
91
+ 一旦产生任务、版本或发布物料,`repo_id` 应保持不变。显式改变它会影响现有引用,必须先完成影响分析,不能把改名当作普通重装。
92
+
93
+ ### 4.4 远程参数
94
+
95
+ 远程值可以是 Git URL,也可以是可访问的本地仓库路径。初始化器会读取远程 heads:
96
+
97
+ - 非空代码远程必须存在 `main`,否则拒绝绑定;
98
+ - 工作流远程已有 `workflow/<workspace-id>` 时会提取并跟踪该分支;
99
+ - 本地已有 `origin` 与请求远程不一致时拒绝覆盖;
100
+ - `--shared-remote` 不能与 `--code-remote` 或 `--workflow-remote` 同时使用;
101
+ - 共用远程不等于共用 Git 根,外层和内层仍必须各自拥有独立 `.git`。
102
+
103
+ ## 5. 布尔选项
104
+
105
+ ### 5.1 `--dry-run`
106
+
107
+ - 适用命令:`init`、`bootstrap`;
108
+ - 只返回计划动作,不写文件、不创建 Git 仓库、不修改远程;
109
+ - 用于安装、升级、迁移和冲突处理前审阅;
110
+ - `inspect` 和 `doctor` 本身只读,不需要该参数。
111
+
112
+ ### 5.2 `--force`
113
+
114
+ - 适用命令:`init`、`bootstrap`;
115
+ - 在替换受管文件前备份到 `.workflow/delivery/runtime/backups/<timestamp>/`;
116
+ - 可处理受管文件 checksum 冲突,以及用户明确批准的包版本降级;
117
+ - 不会覆盖冲突的已批准规范正文;
118
+ - 不会绕过远程冲突、非法代码路径、非空非 Git 目录、双 Git 共根、状态不一致或安全 Gate;
119
+ - 必须先执行 `--dry-run` 并审阅影响,不能作为通用修复开关。
120
+
121
+ `--dry-run --force` 可以预览强制替换计划,但不会实际备份或替换。
122
+
123
+ ## 6. 重复执行 `init`
124
+
125
+ - 未显式提供 `--code-path`、`--code-id` 或 `--workspace-id` 时,沿用 `.workflow/config/workspace.yaml` 的稳定值;
126
+ - 显式提供的新值会被视为身份变更请求,仍需通过目录、Git 和远程校验;
127
+ - 已有远程与请求值不一致时停止,不会静默改写 `origin`;
128
+ - 相同发行版本和相同 checksum 的受管文件保持不变;
129
+ - 包升级会替换发行包管理的 Skill 和运行时,同时保留产品、架构、体验、迭代、发布、正式状态和自进化物料;
130
+ - 查看器是可选工具,删除后重新执行 `init` 可以恢复,且不改变正式状态。
131
+
132
+ ## 7. 常用组合
133
+
134
+ 最小项目级安装:
135
+
136
+ ```powershell
137
+ npx ai-delivery-workflow@latest init . --workspace-id tasklite --code-path code --code-id tasklite-app
138
+ npx ai-delivery-workflow@latest doctor .
139
+ ```
140
+
141
+ ### 7.1 `init` 后补绑远程仓库
142
+
143
+ 项目最初只在本地初始化时,可以先预览补绑结果,再使用同一条 `init` 命令写入两个 Git 根的 `origin`:
144
+
145
+ ```powershell
146
+ npx ai-delivery-workflow@latest init . `
147
+ --workflow-remote git@gitee.com:example/tasklite-workflow.git `
148
+ --code-remote git@gitee.com:example/tasklite-code.git `
149
+ --dry-run
150
+
151
+ npx ai-delivery-workflow@latest init . `
152
+ --workflow-remote git@gitee.com:example/tasklite-workflow.git `
153
+ --code-remote git@gitee.com:example/tasklite-code.git
154
+ ```
155
+
156
+ 补绑只设置 `origin` 并更新 `.workflow/config/workspace.yaml`,不会自动推送。首次推送必须分别在两个 Git 根执行:
157
+
158
+ ```powershell
159
+ git push -u origin workflow/tasklite
160
+ git -C code push -u origin main
161
+ ```
162
+
163
+ 其中 `workflow/tasklite` 应替换为配置文件中的 `workflow_repository.branch`,`code` 应替换为 `code_repository.path`。推送前先运行 `inspect`;如果 `ahead`、`behind` 同时大于零,表示本地与远端已经分叉,应先人工选择合并或变基策略,不要使用强制推送。远程非空时,代码远程必须存在 `main`;本地已有不同 `origin` 时命令会拒绝覆盖。绑定共享远程时使用 `--shared-remote`,不要同时提供另外两个远程参数。
164
+
165
+ 工作流和代码使用不同远程:
166
+
167
+ ```powershell
168
+ npx ai-delivery-workflow@latest init . `
169
+ --workspace-id tasklite `
170
+ --code-path code `
171
+ --code-id tasklite-app `
172
+ --workflow-remote git@gitee.com:example/tasklite-workflow.git `
173
+ --code-remote git@gitee.com:example/tasklite-code.git
174
+ ```
175
+
176
+ 两个 Git 根共用一个远程:
177
+
178
+ ```powershell
179
+ npx ai-delivery-workflow@latest init . `
180
+ --workspace-id tasklite `
181
+ --code-path code `
182
+ --code-id tasklite-app `
183
+ --shared-remote git@gitee.com:example/tasklite.git
184
+ ```
185
+
186
+ 先预览再确认替换受管文件:
187
+
188
+ ```powershell
189
+ npx ai-delivery-workflow@latest init . --dry-run
190
+ npx ai-delivery-workflow@latest init . --force
191
+ npx ai-delivery-workflow@latest doctor .
192
+ ```
193
+
194
+ 只读检查和自主引导:
195
+
196
+ ```powershell
197
+ npx ai-delivery-workflow@latest inspect .
198
+ npx ai-delivery-workflow@latest bootstrap . --dry-run
199
+ npx ai-delivery-workflow@latest bootstrap .
200
+ ```
201
+
202
+ 安装完成后应重新创建 Codex 任务,使项目级 Skill 和 Hook 信任状态重新加载。此后用户直接说“开始”“继续”“下一步”或描述需求,由 AI 自主选择内部 Skill 和 CLI。
203
+
204
+ 完整工作区结构和 Git 行为见[双仓工作区说明](DUAL-REPOSITORY-WORKSPACE.zh-CN.md),整个系统的使用顺序见[完整项目手册](PROJECT-MANUAL.zh-CN.md)。
@@ -3,12 +3,14 @@
3
3
  > 当前双仓目录、初始化规则、状态分层和 Git 边界见 [DUAL-REPOSITORY-WORKSPACE.zh-CN.md](DUAL-REPOSITORY-WORKSPACE.zh-CN.md)。
4
4
 
5
5
  > 适用目录:工作流发行仓库
6
- > 适用包版本:`ai-delivery-workflow@0.2.0`
6
+ > 适用包版本:`ai-delivery-workflow@0.3.0`
7
7
  > 使用方法和完整流程:[PROJECT-MANUAL.zh-CN.md](PROJECT-MANUAL.zh-CN.md)
8
+ > CLI 安装参数:[CLI-PARAMETER-REFERENCE.zh-CN.md](CLI-PARAMETER-REFERENCE.zh-CN.md)
8
9
  > 查看器使用:[VIEWER-USER-GUIDE.zh-CN.md](VIEWER-USER-GUIDE.zh-CN.md)
9
10
  > 查看器维护:[VIEWER-MAINTENANCE.zh-CN.md](VIEWER-MAINTENANCE.zh-CN.md)
10
11
  > 正式状态使用:[STATE-CLI-USER-GUIDE.zh-CN.md](STATE-CLI-USER-GUIDE.zh-CN.md)
11
12
  > 正式状态维护:[STATE-CLI-MAINTENANCE.zh-CN.md](STATE-CLI-MAINTENANCE.zh-CN.md)
13
+ > npm 版本与发布:[NPM-RELEASE-MANAGEMENT.zh-CN.md](NPM-RELEASE-MANAGEMENT.zh-CN.md)
12
14
  > 审计物料逐文件说明:[../audit/FILE-GUIDE.zh-CN.md](../audit/FILE-GUIDE.zh-CN.md)
13
15
 
14
16
  ## 1. 阅读说明
@@ -54,6 +56,7 @@
54
56
  | `audit/generate-dual-repo.mjs` | 创建 `.tmp/dual-repo-e2e/` 真实双 Git 工作区,并生成不含 `.git` 的持久审阅样本。 |
55
57
  | `audit/DUAL-REPO-FULL-FLOW/` | 双仓全流程持久物料,包含任务分片、共享 checkpoint、双 Tag 记录、发布、重发、Hotfix 和回滚证据。 |
56
58
  | `audit/AUTONOMOUS-GUIDANCE/` | 安装后零命令自主规划与引导的脱敏黑盒审计;保存声明清单、原始 Git 对象证明和四个最小远程快照,区分仓库可复算结论与未获平台签名的运行声明。 |
59
+ | `audit/AUTONOMY-COST/` | 首次 Bootstrap 与重复只读恢复的成本基线;记录上下文字节代理并证明持久工作流状态零增长。 |
57
60
 
58
61
  ### 3.1 `bin/`
59
62
 
@@ -61,6 +64,8 @@
61
64
  | --- | --- |
62
65
  | `bin/ai-delivery.mjs` | npm `ai-delivery` 可执行入口;解析 `init`、`inspect`、`bootstrap`、`doctor`、`state`、`--dry-run`、`--force` 和版本参数,调用 `lib/` 并设置退出码。 |
63
66
  | `bin/validate-skills.mjs` | `npm run skills:validate` 的维护命令入口;调用 Skill 校验库、输出汇总或逐条错误并设置退出码,不会被 `init` 复制到业务项目。 |
67
+ | `bin/release-check.mjs` | 维护仓库 npm 发布前后预检;以结构化 JSON 核对版本、Git、测试、审计、registry、2FA、Tag 和外部 `npx`。 |
68
+ | `bin/benchmark-autonomy.mjs` | 在系统临时目录测量首次 Bootstrap 与重复只读恢复检查的耗时、上下文字节和持久物料稳定性。 |
64
69
 
65
70
  ### 3.2 `lib/`
66
71
 
@@ -79,6 +84,7 @@
79
84
  | 文件 | 作用 |
80
85
  | --- | --- |
81
86
  | `docs/PROJECT-MANUAL.zh-CN.md` | 完整中文项目手册:定位、安装、状态识别、全流程、Gate、原型、TDD、Git、发布、checkpoint、规范、审计、维护和故障排查。 |
87
+ | `docs/CLI-PARAMETER-REFERENCE.zh-CN.md` | CLI 安装参数参考:命令、位置参数、默认值、校验、远程组合、重复初始化和强制替换边界。 |
82
88
  | `docs/FILE-REFERENCE.zh-CN.md` | 本文件,说明发行仓库全部有效文件及同构路径。 |
83
89
  | `docs/VIEWER-USER-GUIDE.zh-CN.md` | 查看器用户手册:安装、启动、界面、识别规则、安全边界、升级和故障排查。 |
84
90
  | `docs/VIEWER-MAINTENANCE.zh-CN.md` | 查看器维护手册:权威源码、架构、API、推导契约、修改入口、验证流程和完成定义。 |
@@ -86,6 +92,8 @@
86
92
  | `docs/STATE-CLI-MAINTENANCE.zh-CN.md` | 正式状态 CLI 维护手册:实现边界、自包含安装、验证契约、测试和完成定义。 |
87
93
  | `docs/EVOLUTION-USER-GUIDE.zh-CN.md` | 自进化用户手册:反馈、风险、生命周期、Hook、trial、批准、回退和导出。 |
88
94
  | `docs/EVOLUTION-MAINTENANCE.zh-CN.md` | 自进化维护手册:权威源码、安装边界、数据模型、安全限制和验证方法。 |
95
+ | `docs/NPM-RELEASE-MANAGEMENT.zh-CN.md` | npm 版本与发布管理规范:SemVer、Git Tag、发布门槛、2FA、失败处理和发布后验证。 |
96
+ | `docs/MAINTENANCE-VERIFICATION.zh-CN.md` | 发布预检、自主恢复成本基准和 Ed25519 可信平台证明协议的维护说明。 |
89
97
 
90
98
  ## 5. 永久验证源码
91
99
 
@@ -99,12 +107,15 @@
99
107
  | `verification/release-planner.test.mjs` | 连续版本闭包、同版本重发、倒退、断链、stale、缺 Tag 和 Git 血缘。 |
100
108
  | `verification/audit-workflow.test.mjs` | 运行审计总验证器,要求历史 V1 与当前契约 V2 均通过、各归档 15 个任务,并检查 checksum 格式和未发布版本数。 |
101
109
  | `verification/autonomous-guidance-evidence.test.mjs` | 验证自主引导证据可离线复算,并拒绝不完整清单、错误 commit tree、被篡改的快照/运行日志和符号链接逃逸。 |
110
+ | `verification/release-check.test.mjs` | 验证发布预检的本地通过、结构化失败和 Tag/提交绑定。 |
111
+ | `verification/autonomy-cost-benchmark.test.mjs` | 验证成本基准的迭代参数、上下文代理和重复恢复零持久增长。 |
102
112
  | `verification/workflow-viewer.test.mjs` | 验证查看器目录扫描、状态与关系推导、HTTP UI、`.workflow/` 读取边界和前后端语法。 |
103
113
  | `verification/delivery-state.test.mjs` | 验证正式状态 inspect/verify、物料版本、节点流转、Gate、revision 冲突、checksum 和绕过写入检测。 |
104
114
  | `verification/checkpoint-task-state.test.mjs` | 独立验证 checkpoint、任务依赖、恢复顺序、共享发布和版本归档。 |
105
115
  | `verification/evolution.test.mjs` | 验证反馈脱敏与幂等、证据门槛、风险抬升、checksum 批准、Hook、冲突、trial 晋升与回退。 |
106
116
  | `verification/audit/verify-audit.mjs` | 一键检查审计节点、引用、checksum、部署、发布列车、归档和自主引导证据一致性。 |
107
- | `verification/audit/verify-autonomous-guidance.mjs` | 复算自主引导结果/运行日志、原始 commit/tree、四个 Git blob 路径归属和任务语义,并报告仓库可验证与平台未认证声明。 |
117
+ | `verification/audit/verify-autonomous-guidance.mjs` | 复算自主引导结果/运行日志、Git 证据和任务语义;缺省保留平台未认证声明,也可用外部受信 Ed25519 公钥验证平台签名。 |
118
+ | `verification/audit/verify-autonomy-cost.mjs` | 校验正式成本基线至少包含三次恢复样本,且持久 checksum、文件数、字节数和语义上下文均稳定。 |
108
119
 
109
120
  ## 6. Skill 通用结构
110
121
 
@@ -0,0 +1,51 @@
1
+ # 维护仓库验证工具
2
+
3
+ 本文说明维护与发行仓库新增的发布预检、自主恢复成本基准和可信平台证明协议。这些命令不随 `init` 写入业务项目,也不改变业务项目的研发或发布状态。
4
+
5
+ ## 1. npm 发布预检
6
+
7
+ ```powershell
8
+ npm run release:check -- prepare --version 0.3.0
9
+ npm run release:check -- verify --version 0.3.0 --commit <40位提交ID>
10
+ ```
11
+
12
+ `prepare` 默认执行 `full` 检查:清单与锁文件版本、`main`、干净工作树、Tag 可用性、全量测试、25 个 Skill、审计验证、`npm pack --dry-run`、`npm publish --dry-run`、官方 registry、版本未占用、npm 身份和 `auth-and-writes` 2FA。两个 dry-run 还必须返回一致的包名、版本、文件清单、文件数量和制品摘要;本包必须包含 `bin/`、`docs/`、`lib/`、`skills/` 及有效的 `bin.ai-delivery`。任一项失败都会输出 `decision: fail` 并返回非零退出码。
13
+
14
+ `verify` 用于 npm 发布后、推送 Git Tag 前,核对精确版本、`latest`、`bin`、可选 `gitHead`、本地 Tag 与发行提交,并在系统临时目录从维护仓库之外运行 `npx <package>@<version> --version`。
15
+
16
+ `--scope local` 只用于离线开发和自动化回归测试,执行本地 Git、测试、审计和打包检查,不等价于正式发布门槛。`--root` 可指定其他通用 npm 包根目录;默认是当前目录。JSON 只保留检查结论和脱敏错误摘要,不回显 token、OTP 或 npm profile 正文。
17
+
18
+ ## 2. 自主恢复成本基准
19
+
20
+ ```powershell
21
+ npm run benchmark:autonomy -- --iterations 5
22
+ node bin/benchmark-autonomy.mjs --iterations 5 --output audit/AUTONOMY-COST/baseline.json
23
+ ```
24
+
25
+ 命令在系统临时目录创建隔离业务项目,执行一次真实安装和 Bootstrap,通过正式任务 CLI 建立等待用户输入的 checkpoint,然后重复调用 `bootstrapProject` 模拟用户只说“继续”时的恢复路由。报告记录首次 Bootstrap 和每次恢复检查耗时、路由 JSON 的 UTF-8 字节数与 SHA-256、排除 `detected_at` 后的语义上下文 SHA-256,以及重复检查前后 `.workflow/` 的文件数、总字节和目录摘要。
26
+
27
+ 通过条件是重复检查不新增或修改任何持久物料,并且语义上下文稳定。Codex 平台没有向仓库命令暴露每次 Agent 的 LLM token,因此报告明确写入 `llm_token_usage: unavailable`,使用上下文字节数作为仓库可观测代理。耗时受机器、磁盘和安全软件影响,只作为观测值,不作为发行阈值。
28
+
29
+ ## 3. 可信平台证明
30
+
31
+ 自主引导审计验证器支持两种状态:
32
+
33
+ - `not-provided`:现有历史审计的默认状态,平台行为声明继续标记为 `unattested`;
34
+ - `signed`:由可信运行平台使用 Ed25519 私钥签名,验证时从审计包之外显式传入受信公钥。
35
+
36
+ ```powershell
37
+ node verification/audit/verify-autonomous-guidance.mjs `
38
+ --audit-root <审计目录> `
39
+ --trusted-key <可信平台Ed25519公钥PEM>
40
+ ```
41
+
42
+ 签名载荷 schema 1 绑定 `audit_id`、被认证的声明 ID,以及每轮运行的 `run_id`、输入 SHA-256、`fork_mode` 和输出 SHA-256。证明文件、描述符 checksum、Ed25519 公钥指纹和签名必须同时一致。公钥必须由审计使用方通过独立可信渠道提供;把公钥或自签名证明随审计包一起提交不能建立信任。
43
+
44
+ 缺少信任根、错误密钥、载荷篡改、证明 checksum 变化、声明清单不完整或运行记录不一致都会使验证失败。维护仓库不会生成或保存平台私钥,也不会把操作员记录自动升级成平台认证事实。
45
+
46
+ ## 4. 维护边界
47
+
48
+ - 通用命令位于 `bin/`,自动化测试位于 `verification/`。
49
+ - 成本审计的正式基线位于 `audit/AUTONOMY-COST/`;临时项目始终位于系统临时目录或 `.tmp/`。
50
+ - 发布检查、基准和证明验证均不得在本维护仓库执行面向业务项目的 `init`。
51
+ - 所有工具失败后先修复输入或环境,不得手工篡改 JSON 结论绕过门槛。
@@ -0,0 +1,106 @@
1
+ # npm 版本与发布管理规范
2
+
3
+ 本文约束 `ai-delivery-workflow` 维护仓库的 npm 版本和发行,不描述安装后业务项目的研发版本或生产发布状态机。npm 包、Git 提交和 Git Tag 必须能够相互追踪;已经公开的版本和 Tag 不得改写。
4
+
5
+ ## 1. 权威身份
6
+
7
+ - `package.json` 的 `version` 是待发布或最近发布的 npm 版本,`package-lock.json` 必须同步。
8
+ - Git Tag 固定使用 `v<version>`,例如 `v0.2.2`,并指向包含该版本号的唯一发行提交。
9
+ - npm 官方仓库 `ai-delivery-workflow@<version>` 是公开制品身份;版本一经发布即不可复用。
10
+ - `latest` 只指向稳定版本。预发布版本使用 `x.y.z-beta.n`、`x.y.z-rc.n` 和 `next` 等非 `latest` dist-tag。
11
+ - `main` 可以在发布后继续包含未发布变更;只有完成本文门槛后才能声称某个提交已经发行。
12
+
13
+ ## 2. 版本选择
14
+
15
+ 采用 SemVer,并对 `0.x` 阶段使用更严格的兼容约束:
16
+
17
+ | 变更 | 版本规则 | 示例 |
18
+ | --- | --- | --- |
19
+ | 修复缺陷、补充验证或文档,且不改变公开契约 | PATCH | `0.2.2` -> `0.2.3` |
20
+ | 新增向后兼容能力、命令或 Skill | MINOR | `0.2.x` -> `0.3.0` |
21
+ | CLI、状态 schema、Hook 协议、安装结构或 Skill 上下游契约发生不兼容变化 | `0.x` 阶段至少 MINOR,并提供迁移说明;`1.0.0` 后使用 MAJOR | `0.3.x` -> `0.4.0` |
22
+
23
+ 自动迁移能够完整保留既有物料时可视为兼容;需要用户手工改文件、丢弃状态或改变已有命令语义时属于不兼容变更。安装清单 schema、正式状态 schema 和 npm 版本是不同身份,不得互相替代。
24
+
25
+ ## 3. 发布前门槛
26
+
27
+ 发布必须从干净、已同步的 `main` 开始。不得从临时工作区、审计远程或业务项目发布。依次完成:
28
+
29
+ ```powershell
30
+ npm run release:check -- prepare --version <version>
31
+ git diff --check
32
+ ```
33
+
34
+ `release:check prepare` 以结构化 JSON 一次执行并汇总版本、Git、全量测试、Skill、审计、打包、官方 registry、版本占用、npm 身份和 2FA 门槛。正式发布必须使用默认 `full` scope;`--scope local` 只用于离线回归测试,不能替代发布门槛。完整字段见[维护仓库验证工具](MAINTENANCE-VERIFICATION.zh-CN.md)。
35
+
36
+ 该命令内部必须实际执行 `npm test`、`npm run skills:validate`、`npm run audit:verify` 和 `npm publish --dry-run`;结构化汇总不能用静态声明替代这些命令。
37
+
38
+ 发布预检必须使用 `npm publish --dry-run`,不能只依赖 `npm pack --dry-run`;前者还会执行 npm 的发布规范化检查。输出不得包含 `auto-corrected`、`invalid and removed`,并必须确认:
39
+
40
+ - 包名、版本、文件数量和制品摘要符合预期;
41
+ - `bin.ai-delivery` 为 `bin/ai-delivery.mjs`,文件以 Node shebang 开头;
42
+ - `bin/`、`lib/`、`skills/` 和 `docs/` 均进入制品;
43
+ - `.tmp/`、`verification/`、`audit/`、凭据和本地日志不进入 npm 包。
44
+
45
+ 版本号使用以下命令同步到两个清单文件:
46
+
47
+ ```powershell
48
+ npm version <version> --no-git-tag-version
49
+ ```
50
+
51
+ 修改后必须重新执行全部门槛。禁止先发布、后补测试或文档。
52
+
53
+ ## 4. Git 与 npm 发布顺序
54
+
55
+ 1. 提交通过验证的发行内容,并把发行提交推送到 `origin/main`。
56
+ 2. 确认远程不存在同名 Tag,在发行提交上创建本地签注 Tag `v<version>`。
57
+ 3. 使用官方仓库登录身份和发布级 2FA 执行 `npm publish --access public`。
58
+ 4. 从官方仓库回读版本、dist-tag、CLI、repository 和完整性摘要。
59
+ 5. 安装精确版本并执行 `ai-delivery --version` 冒烟验证。
60
+ 6. npm 验证通过后推送 Git Tag。若团队要求先公开 Git Tag,则 npm 发布失败后不得改写该 Tag,修复必须使用新的 PATCH 版本。
61
+
62
+ Git Tag 和 npm 版本必须指向同一发行内容。不得删除远程 Tag 后复用版本,也不得把可移动 branch head 当作发行身份。
63
+
64
+ ## 5. 认证与凭据
65
+
66
+ - 发布前执行 `npm whoami --registry=https://registry.npmjs.org/`,确认账号正确。
67
+ - 交互发布必须启用 `auth-and-writes` 2FA,并使用 npm 的一次性网页认证或 OTP。
68
+ - 密码、OTP、恢复码和 npm token 不得写入仓库、`.tmp/` 审计物料、命令日志或对话文档。
69
+ - CI 只能使用最小权限、限定包、可过期的 granular access token;只有发布任务需要时才允许 bypass 2FA,并存入 CI secret store。
70
+ - `publishConfig.registry` 必须固定为 `https://registry.npmjs.org/`,避免把正式包发布到本地镜像。
71
+
72
+ ## 6. 失败处理
73
+
74
+ | 场景 | 处理 |
75
+ | --- | --- |
76
+ | npm 在创建版本前拒绝发布 | 修复原因并重新执行全部门槛;若远程 Tag 已存在,递增 PATCH,不改写 Tag。 |
77
+ | npm 已发布但 Tag 推送失败 | 保留 npm 版本,重试把同一发行提交的 Tag 推送到远程;不得重新发布。 |
78
+ | 发布后发现普通缺陷 | 发布新的 PATCH;不得覆盖或复用旧版本。 |
79
+ | 发布后发现严重安全或安装问题 | 经人工确认后使用 `npm deprecate` 标注并立即发布修复版;只有满足 npm 官方撤回政策时才考虑 unpublish。 |
80
+ | `latest` 指向错误版本 | 核对制品后显式修正 dist-tag,不重新打包同一版本。 |
81
+ | 2FA 或权限失败 | 保持源码和 npm 状态不变,完成账号授权后重试;不得把令牌写入项目配置。 |
82
+
83
+ 任何失败都要先用 `npm view ai-delivery-workflow@<version>` 确认版本是否真正创建,再决定重试或递增版本。
84
+
85
+ ## 7. 发布后验证
86
+
87
+ ```powershell
88
+ npm run release:check -- verify --version <version> --commit <40位发行提交ID>
89
+ npm view ai-delivery-workflow@<version> version dist-tags bin repository homepage --json --registry=https://registry.npmjs.org/
90
+ npm install --prefix .tmp/npm-smoke-<version> --no-save --package-lock=false ai-delivery-workflow@<version> --registry=https://registry.npmjs.org/
91
+ .\.tmp\npm-smoke-<version>\node_modules\.bin\ai-delivery.cmd --version
92
+ ```
93
+
94
+ POSIX 环境使用对应的 `.bin/ai-delivery`。验证结果必须满足:精确版本可安装、`latest` 符合发布意图、CLI 返回相同版本、Git Tag 指向发行提交、维护仓库工作区干净。
95
+
96
+ 公开 `npx` 入口还必须从维护仓库及其子目录之外执行一次:
97
+
98
+ ```powershell
99
+ Push-Location C:\tmp
100
+ npx --yes --registry=https://registry.npmjs.org/ ai-delivery-workflow@<version> --version
101
+ Pop-Location
102
+ ```
103
+
104
+ npm 会向父目录查找 `package.json`;如果在本维护仓库或 `.tmp/` 子目录执行同名包,可能把当前源码误认为已经安装并跳过临时安装,产生假的“命令不存在”。仓库外消费目录才代表用户的真实 `npx` 路径。
105
+
106
+ 本规范属于维护仓库发行规则,不写入[安装目标模板](../skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md)。维护入口和其他仓库边界见[完整项目手册](PROJECT-MANUAL.zh-CN.md)。
@@ -16,7 +16,7 @@ workspace/code/.git 代码 Git
16
16
  初始化器还会合并根 `.gitattributes`,对 `.workflow/**`、项目级 `ai-delivery-*` Skills、`.codex/config.toml`、`AGENTS.md`、`.gitignore` 和 `.gitattributes` 强制 `eol=lf`。这是 checksum 可移植性约束;不会修改嵌套代码仓的换行策略。缺失规则会使 `doctor` 失败,避免 Windows checkout 后正式状态和物料被误判为篡改。
17
17
 
18
18
  > 文档版本:1.0
19
- > 适用包版本:`ai-delivery-workflow@0.2.0`
19
+ > 适用包版本:`ai-delivery-workflow@0.3.0`
20
20
  > 默认文档语言:简体中文
21
21
  > 文件级索引:[FILE-REFERENCE.zh-CN.md](FILE-REFERENCE.zh-CN.md)
22
22
  > 全流程审计物料(历史 V1 与当前契约 V2):[../audit/README.md](../audit/README.md)
@@ -168,6 +168,8 @@ npx ai-delivery-workflow@latest doctor .
168
168
 
169
169
  这只会写入当前项目,不会安装全局 skill。
170
170
 
171
+ `workspace-directory`、`--workspace-id`、`--code-path`、`--code-id`、三种远程参数、`--dry-run` 和 `--force` 的完整含义、默认值、互斥关系及重复初始化规则见 [CLI 安装参数参考](CLI-PARAMETER-REFERENCE.zh-CN.md)。
172
+
171
173
  `init` 会同时内置正式状态 CLI 和本地工作流物料查看器,不修改业务项目 `package.json`。安装完成后无需安装额外前端依赖,直接在目标项目运行:
172
174
 
173
175
  ```powershell
@@ -200,7 +202,7 @@ node E:\work\ai\codex\workflow\bin\ai-delivery.mjs doctor E:\path\to\target-proj
200
202
  也可先在本仓库执行 `npm pack`,再在目标项目使用生成的 `.tgz`:
201
203
 
202
204
  ```powershell
203
- npx --yes --package E:\path\to\ai-delivery-workflow-0.2.0.tgz ai-delivery init .
205
+ npx --yes --package E:\path\to\ai-delivery-workflow-0.3.0.tgz ai-delivery init .
204
206
  ```
205
207
 
206
208
  安装完成后,在目标项目中新建 Codex 任务。不要继续依赖安装前的会话来判断 skill 是否可用。
@@ -888,12 +890,14 @@ npm test
888
890
  npm run audit:verify
889
891
  ```
890
892
 
891
- 检查 npm 发布内容:
893
+ 检查 npm 发布内容和发布阶段规范化:
892
894
 
893
895
  ```bash
894
- npm pack --dry-run
896
+ npm --cache .tmp/npm-cache publish --dry-run --access public --registry=https://registry.npmjs.org/
895
897
  ```
896
898
 
899
+ 正式版本选择、Git Tag、2FA、失败版本处理和发布后安装冒烟必须遵循 [npm 版本与发布管理规范](NPM-RELEASE-MANAGEMENT.zh-CN.md)。`npm pack --dry-run` 可用于快速查看文件,但不能替代 `npm publish --dry-run`。
900
+
897
901
  skill 自身还应使用 skill 校验器检查 frontmatter、命名和引用;YAML 产物应使用结构化解析器逐个解析,不应用字符串匹配替代语法校验。
898
902
 
899
903
  ## 20. 故障排查
@@ -924,7 +928,7 @@ skill 自身还应使用 skill 校验器检查 frontmatter、命名和引用;Y
924
928
  - 修改主流程或 Gate 时,同步更新第 7、8 节和 `workflow-model.md`;
925
929
  - 修改原型契约时,同步更新第 9 节和原型交接测试;
926
930
  - 修改 checkpoint 命令或状态时,同步更新第 13 节及恢复示例;
927
- - 修改 npm 发布文件时,运行 `npm pack --dry-run`;
931
+ - 修改 npm 发布文件时,遵循 `NPM-RELEASE-MANAGEMENT.zh-CN.md` 并运行 `npm publish --dry-run`;
928
932
  - 新文档默认中文;
929
933
  - 已批准、冻结、发布和 `audit/` 中已归档版本不原地重写。
930
934
 
@@ -361,7 +361,7 @@ node bin/ai-delivery.mjs init E:\path\to\isolated-project --force
361
361
 
362
362
  截至本指南建立时:
363
363
 
364
- - 包版本:`0.2.0`;
364
+ - 包版本:`0.3.0`;
365
365
  - 流程导航:治理、13 个研发节点、11 个发布节点和按需显示的历史兼容节点;
366
366
  - 支持预览:Markdown、CSV、JSON、YAML、JSONL、TOML、文本、HTML 原型源码和图片;
367
367
  - 文本预览限制:2 MiB;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-delivery-workflow",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Project-scoped Codex skills and hooks for artifact-driven AI software delivery",
5
5
  "type": "module",
6
6
  "repository": {
@@ -26,6 +26,8 @@
26
26
  "skills:validate": "node bin/validate-skills.mjs",
27
27
  "audit:generate": "node audit/generate-all.mjs",
28
28
  "audit:verify": "node verification/audit/verify-audit.mjs",
29
+ "benchmark:autonomy": "node bin/benchmark-autonomy.mjs",
30
+ "release:check": "node bin/release-check.mjs",
29
31
  "pack:check": "npm pack --dry-run"
30
32
  },
31
33
  "dependencies": {