@namewta/speculo 1.0.15 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +17 -5
  2. package/dist/src/agent-files.d.ts +9 -0
  3. package/dist/src/agent-files.js +106 -0
  4. package/dist/src/agent-files.js.map +1 -0
  5. package/dist/src/cli.js +64 -14
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/doctor.d.ts +2 -0
  8. package/dist/src/doctor.js +120 -53
  9. package/dist/src/doctor.js.map +1 -1
  10. package/dist/src/external-files.d.ts +22 -0
  11. package/dist/src/external-files.js +110 -0
  12. package/dist/src/external-files.js.map +1 -0
  13. package/dist/src/index.d.ts +3 -0
  14. package/dist/src/index.js +31 -182
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/paths.d.ts +12 -0
  17. package/dist/src/paths.js +57 -0
  18. package/dist/src/paths.js.map +1 -0
  19. package/dist/src/transaction.d.ts +27 -0
  20. package/dist/src/transaction.js +187 -0
  21. package/dist/src/transaction.js.map +1 -0
  22. package/dist/src/utils.js +4 -2
  23. package/dist/src/utils.js.map +1 -1
  24. package/dist/src/workflows.js +3 -4
  25. package/dist/src/workflows.js.map +1 -1
  26. package/package.json +6 -4
  27. package/template/.speculo/README.md +9 -3
  28. package/template/.speculo/refresh-contract.json +6 -2
  29. package/template/AGENTS.md +1 -1
  30. package/template/canonical/canonical-specdev-goal-plan.md +23 -12
  31. package/template/canonical/canonical-specdev-tickets.md +1 -1
  32. package/template/commands/docs-sync.md +13 -11
  33. package/template/skills/archive-and-consolidate/SKILL.md +2 -4
  34. package/template/skills/docs-sync/SKILL.md +22 -11
  35. package/template/skills/docs-sync/references/agents-contract.md +6 -4
  36. package/template/skills/docs-sync/references/document-lifecycle-contract.md +2 -0
  37. package/template/skills/docs-sync/references/entry-procedure.md +2 -17
  38. package/template/skills/docs-sync/references/git-state-contract.md +1 -1
  39. package/template/skills/docs-sync/references/mode-contract.md +21 -0
  40. package/template/skills/docs-sync/references/workflow-scope-contract.md +5 -3
  41. package/template/skills/engineering-standards-builder/SKILL.md +3 -1
  42. package/template/skills/engineering-standards-builder/examples/README.md +6 -0
  43. package/template/skills/engineering-standards-builder/manifest.txt +1 -0
  44. package/template/skills/engineering-standards-builder/scripts/self-test.mjs +1 -0
  45. package/template/skills/engineering-standards-builder/scripts/sync-manifest.mjs +2 -2
  46. package/template/skills/engineering-standards-builder/scripts/validate-builder.mjs +12 -4
  47. package/template/skills/git-history-squash/SKILL.md +3 -3
  48. package/template/skills/github-npm-ops/SKILL.md +2 -4
  49. package/template/skills/source-code-zip/SKILL.md +0 -1
  50. package/template/skills/speculo-retro/SKILL.md +2 -4
  51. package/template/skills/upstream-fork-sync/SKILL.md +1 -3
  52. package/template/skills/writing-great-skills/SKILL.md +3 -3
  53. package/template/workflows/learning/A-archive/A-archive.md +2 -0
  54. package/template/workflows/learning/A-assess-and-plan/A-assess-and-plan.md +2 -0
  55. package/template/workflows/learning/C-consolidate/C-consolidate.md +2 -0
  56. package/template/workflows/learning/G-goal/G-goal.md +6 -0
  57. package/template/workflows/learning/G-goal/references/external-goal-runner.md +4 -0
  58. package/template/workflows/learning/H-homework/H-homework.md +2 -0
  59. package/template/workflows/learning/L-lesson/L-lesson.md +2 -0
  60. package/template/workflows/learning/Q-question/Q-question.md +2 -0
  61. package/template/workflows/learning/R-review/R-review.md +2 -0
  62. package/template/workflows/learning/README.md +4 -53
  63. package/template/workflows/learning/common/rules/artifact-layouts.md +46 -0
  64. package/template/workflows/learning/common/rules/consolidation-contract.md +5 -0
  65. package/template/workflows/learning/common/rules/homework-contract.md +3 -0
  66. package/template/workflows/learning/common/rules/lesson-contract.md +7 -0
  67. package/template/workflows/learning/common/skills/socratic-questioning/SKILL.md +1 -0
  68. package/template/workflows/learning/common/skills/topic-synthesis/SKILL.md +1 -0
  69. package/template/workflows/ops/README.md +7 -3
  70. package/template/workflows/ops/_state/status.json +2 -1
  71. package/template/workflows/ops/common/USAGE.md +4 -0
  72. package/template/workflows/ops/common/rules/persistence-and-secrets.md +4 -2
  73. package/template/workflows/ops/common/schemas/plan.schema.json +4 -0
  74. package/template/workflows/ops/common/schemas/spec.schema.json +4 -0
  75. package/template/workflows/ops/common/schemas/status.schema.json +4 -0
  76. package/template/workflows/ops/common/tests/test_ops.mjs +33 -2
  77. package/template/workflows/ops/common/tools/opslib/core.mjs +1 -1
  78. package/template/workflows/ops/common/tools/opslib/docs.mjs +12 -7
  79. package/template/workflows/ops/common/tools/opslib/planner.mjs +7 -1
  80. package/template/workflows/specdev/P-goal-plan/P-goal-plan.md +4 -2
  81. package/template/workflows/specdev/P-goal-plan/goal-plan-template.md +2 -0
  82. package/template/workflows/specdev/P-goal-plan/planning-modes.md +13 -8
  83. package/template/workflows/specdev/P-goal-plan/references/map-control.md +1 -1
  84. package/template/workflows/specdev/P-goal-plan/references/single-change-plan.md +4 -2
  85. package/template/workflows/specdev/README.md +2 -2
  86. package/template/workflows/specdev/common/rules/path-reference-contract.md +6 -0
  87. package/template/workflows/specdev/common/rules/workflow-routing.md +2 -2
  88. package/template/workflows/specdev/common/skills/code-review/SKILL.md +3 -1
  89. package/template/workflows/specdev/common/skills/dev-worktree/SKILL.md +1 -0
  90. package/template/workflows/specdev/common/skills/plan-quality-review/SKILL.md +3 -1
  91. package/template/workflows/specdev/common/skills/research/SKILL.md +3 -1
  92. package/template/workflows/specdev/common/skills/subagent-delivery/SKILL.md +1 -0
  93. package/template/workflows/specdev/common/tools/README.md +7 -1
  94. package/template/workflows/specdev/common/tools/validate-specdev.mjs +19 -18
@@ -35,7 +35,7 @@ host_root/
35
35
  _runtime/docker/ # 仅经准备/显式迁移的 Docker Engine
36
36
  app-a/
37
37
  README.md # 版本、时间、路径、依赖、启停、备份恢复
38
- OPERATIONS.md # 策略启用时:受限真实明文凭据
38
+ OPERATIONS.md # 默认 secret_ref;获批 opt-in 才导出受限明文
39
39
  project.yaml # JSON 格式(同时是有效 YAML)资源投影
40
40
  compose/compose.yaml # Docker 时;Dockerfile 同目录
41
41
  service/ # 原生部署定义
@@ -55,7 +55,7 @@ host_root/
55
55
 
56
56
  明确多实例时使用 `project/instances/environment/instance/`,每个实例重复上述自有布局;顶层 README 变为实例索引。单实例与多实例根不可重叠,不自动搬迁。
57
57
 
58
- 部署机对应记录固定为 `state_root/hosts/host_id/deployments/deployment_id/`,包含完整 README、OPERATIONS、deployment.json、server/README、server-files 配置副本与 docs-receipt。全域总册为 FLEET-DEPLOYMENTS.md,真实明文账本为 private/credentials.json。双边记录不等于自动复制业务数据。
58
+ 部署机对应记录固定为 `state_root/hosts/host_id/deployments/deployment_id/`,包含完整 README、OPERATIONS、deployment.json、server/README、server-files 配置副本与 docs-receipt。全域总册为 FLEET-DEPLOYMENTS.md,受限真实值账本为 private/credentials.json;新安装的文档默认只记录 secret_ref/版本。双边记录不等于自动复制业务数据。
59
59
 
60
60
  ## 启动协议
61
61
 
@@ -73,12 +73,16 @@ schema_version=3;hosts、projects、deployments、allocations、bindings、rel
73
73
 
74
74
  ## 路径分配
75
75
 
76
- 部署根由 host/project/layout 唯一派生。所有声明的 APP data/config/env/log/backups 路径都必须在该根内;容器只用显式 bind,禁止命名卷、匿名卷、跨项目 bind。默认只读容器根;仅当镜像仍必须写根文件系统时,才允许带 `writable_root_justification` 的 `read_only: false`。config/env 默认 0644 只读挂载,供非 root 镜像用户读取。项目 env 文件集中在 env/;Compose 使用 raw env_file,要求实际 Compose >=2.30。`compose --wait` 之后仍检查容器 running 与 Health=healthy;TCP/docker-proxy 监听不是生产健康证明。
76
+ 部署根由 host/project/layout 唯一派生。所有声明的 APP data/config/env/log/backups 路径都必须在该根内;容器只用显式 bind,禁止命名卷、匿名卷、跨项目 bind。默认只读容器根;仅当镜像仍必须写根文件系统时,才允许带 `writable_root_justification` 的 `read_only: false`。env、带 credential 占位符的配置和明文文档采用 0600;不含秘密的 config 默认 0644 只读挂载。不能为非 root 容器读取方便把秘密放宽到 world-readable,应在批准的部署计划中安排 owner/secret 注入。项目 env 文件集中在 env/;Compose 使用 raw env_file,要求实际 Compose >=2.30。`compose --wait` 之后仍检查容器 running 与 Health=healthy;TCP/docker-proxy 监听不是生产健康证明。
77
77
 
78
78
  原生服务设置 HOME、XDG、缓存、临时目录和 OPS_* 到 APP 根内;Linux systemd 还设置 ProtectSystem/ReadWritePaths。通用自定义命令是用户审核的可执行代码,不是一个能阻止恶意程序所有系统调用的沙箱。来源代码必须可信,必须明确映射项目真实数据参数,并实际验证;发现无法约束的数据路径就阻塞,不能报完成。
79
79
 
80
80
  systemd 单元等系统控制文件可有计划内的精确例外;业务持久化数据没有该例外。Docker 自身的运行数据固定为 host_root/_runtime/docker;既有 engine 不能被静默迁移。Docker Desktop 的隐藏虚拟机布局不自动等同于原生 Windows 根。
81
81
 
82
+ ## 凭据文档策略
83
+
84
+ 新控制端 `policies.plaintext_documentation=false`;README、OPERATIONS 与总册默认无真实值。需要受限明文交付时,在输入 spec 中明确设置 `plaintext_documentation: true`,它进入 registry_after 与精确计划批准摘要。旧状态缺字段仍保留旧行为,不由刷新自动改写;收紧时使用显式 false 的新批准计划。旧历史文档/回执不自动改写或删除,应另做授权清理。env 和受限 server-files 配置副本仍按原双边合同交付,不能把“文档脱敏”称作所有状态不含秘密。
85
+
82
86
  ## 副作用边界
83
87
 
84
88
  init/register/credential-put 是用户显式请求的部署机本地记录操作;probe/analyze 是有边界读取。source-fetch 与 mirror-probe 要求显式网络标志。其余目标修改全部先生成完整计划,用户确认精确摘要后执行,包括本地可逆动作。
@@ -12,7 +12,8 @@
12
12
  "policies": {
13
13
  "server_readme_credentials": false,
14
14
  "server_operations": true,
15
- "strict_docker_root": true
15
+ "strict_docker_root": true,
16
+ "plaintext_documentation": false
16
17
  },
17
18
  "updated_at": null
18
19
  }
@@ -129,3 +129,7 @@ node /path/to/ops/common/tools/validate-ops.mjs --self-check
129
129
  ```
130
130
 
131
131
  演练只使用指定新空目录,创建演示 APP 与演示明文凭据,运行真实有限 Node 程序,完成双边文档;不连接其他主机、不安装软件。它不是生产系统验收。
132
+
133
+ ## 凭据文档 opt-in
134
+
135
+ 新安装默认文档只含 secret_ref。输入 spec 的可选 `plaintext_documentation` 布尔值是受计划摘要保护的策略变更,设 true 才导出受限明文,设 false 可显式收紧旧状态。生成计划后照常批准精确摘要并执行;更改策略使旧批准失效。旧状态缺字段保留原语义,CLI 刷新不改历史凭据或回执。env 与 server-files 配置副本仍是受限真实配置,不属于无秘密文档承诺。
@@ -14,9 +14,11 @@ Docker 要求明确映射镜像声明的全部 VOLUME,实际启动后再次检
14
14
 
15
15
  private/credentials.json 是版本化明文账本;credential-put 不会修改服务器密码。相同版本不可覆盖,轮换先增加新版本,再通过独立部署/迁移计划应用与验证。引用形如 app-a-db@1,值占位形如 {{credential:app-a-db@1:password}}。
16
16
 
17
- 控制端项目 README、OPERATIONS 与全域总册保存真实值,特殊字符逐字保留。服务端 README 默认无密码但不能缺路径、版本、时间、启动停止、依赖和备份恢复;OPERATIONS 与 DEPLOYMENTS 默认是受限明文。未知旧密码不能编造;密钥认证没有密码,记录实际密钥认证而不是虚构一个口令。
17
+ 新安装的控制端项目 README、OPERATIONS 与全域总册默认仅保存 secret_ref、凭据版本与用途;只有获批 `plaintext_documentation: true` 的计划才逐字导出真实值。服务端 README 默认无密码但不能缺路径、版本、时间、启动停止、依赖和备份恢复;OPERATIONS 与 DEPLOYMENTS 默认只保存引用;显式 opt-in 时按各文档职责导出受限明文。未知旧密码不能编造;密钥认证没有密码,记录实际密钥认证而不是虚构一个口令。
18
18
 
19
- POSIX 账本、env 与明文文档 0600、控制端目录 0700。`config/` 与 `*.conf`/`*.acl` 默认 0644,供非 root 容器用户读取只读挂载;Windows 使用受限 ACL,不以 chmod 代替 DACL。明文不进入 Git、Web 目录、构建上下文、普通日志;env 是指定的本地配置文件例外,不能再进入镜像层。示例凭据全为演示,不可用于生产。
19
+ POSIX 账本、env 与明文文档 0600、控制端目录 0700。不含秘密的 `config/` 与 `*.conf`/`*.acl` 默认 0644;含 credential 占位符的配置自动收紧为 0600,显式宽权限会阻塞规划,不以非 root 读取为由放宽秘密权限;Windows 使用受限 ACL,不以 chmod 代替 DACL。明文不进入 Git、Web 目录、构建上下文、普通日志;env 是指定的本地配置文件例外,不能再进入镜像层。示例凭据全为演示,不可用于生产。
20
+
21
+ 旧状态缺 `plaintext_documentation` 字段时保留旧语义,新的 false/true 策略必须通过新计划与精确摘要批准;不复用旧批准,不回写历史 Run/Release。关闭导出不代表删除既有历史明文,历史清理需另行授权。服务 env 与受限 server-files 副本仍包含运行必需真实值;未知硬编码秘密不能仅依赖占位符检测,计划审核仍需人工/Agent 检查。
20
22
 
21
23
  ## 双边完成门
22
24
 
@@ -469,6 +469,10 @@
469
469
  },
470
470
  "strict_docker_root": {
471
471
  "const": true
472
+ },
473
+ "plaintext_documentation": {
474
+ "type": "boolean",
475
+ "description": "Explicit restricted plaintext document export. Missing on legacy state retains the prior policy; new states default false."
472
476
  }
473
477
  },
474
478
  "required": [
@@ -1155,6 +1155,10 @@
1155
1155
  "mappings"
1156
1156
  ],
1157
1157
  "additionalProperties": false
1158
+ },
1159
+ "plaintext_documentation": {
1160
+ "type": "boolean",
1161
+ "description": "Explicitly opt in/out of restricted plaintext document delivery; bound by the plan approval digest."
1158
1162
  }
1159
1163
  },
1160
1164
  "required": [
@@ -185,6 +185,10 @@
185
185
  },
186
186
  "strict_docker_root": {
187
187
  "const": true
188
+ },
189
+ "plaintext_documentation": {
190
+ "type": "boolean",
191
+ "description": "Explicit restricted plaintext document export. Missing on legacy state retains the prior policy; new states default false."
188
192
  }
189
193
  },
190
194
  "required": [
@@ -580,8 +580,9 @@ test("planner contracts", async (t) => {
580
580
  });
581
581
  });
582
582
 
583
- test("real native and dual docs", () => runFx((fx) => {
584
- const [info, , result] = fx.runSpec();
583
+ test("real native and explicitly approved plaintext dual docs", () => runFx((fx) => {
584
+ const input = fx.spec(); input.plaintext_documentation = true;
585
+ const [info, , result] = fx.runSpec(input);
585
586
  assert.equal(result.status, "completed", JSON.stringify(result));
586
587
  const dep = model.load(fx.state).deployments["app-a-prod"];
587
588
  assert.equal(dep.observed_version, "v1");
@@ -980,3 +981,33 @@ test("host services and public ingress are delivered", () => runFx((fx) => {
980
981
  assert.ok(fleet.includes("公网访问内网"));
981
982
  assert.ok(!fleet.includes("<code>"));
982
983
  }));
984
+
985
+
986
+ test("new controllers keep secrets out of default documentation", () => runFx((fx) => {
987
+ assert.equal(model.load(fx.state).policies.plaintext_documentation, false);
988
+ const [info, plan, result] = fx.runSpec();
989
+ assert.equal(result.status, "completed", JSON.stringify(result));
990
+ const remote = join(fx.target, "app-a");
991
+ const local = join(fx.state, "hosts", "node-a", "deployments", "app-a-prod");
992
+ for (const path of [join(remote,"README.md"), join(remote,"OPERATIONS.md"), join(local,"README.md"), join(local,"OPERATIONS.md"), join(fx.state,"FLEET-DEPLOYMENTS.md"), join(fx.target,"DEPLOYMENTS.md")]) {
993
+ assert.ok(!readFileSync(path,"utf8").includes(PASSWORD), path);
994
+ }
995
+ assert.match(readFileSync(join(local,"OPERATIONS.md"),"utf8"), /secret_ref:.*app-auth@1/);
996
+ assert.ok(readFileSync(join(remote,"env","app.env"),"utf8").includes(PASSWORD));
997
+ assert.equal(plan.operations.find((o) => o.path?.endsWith("env/app.env")).mode, 0o600);
998
+ if (process.platform !== "win32") assert.equal(statSync(join(remote,"env","app.env")).mode & 0o777, 0o600);
999
+ assert.equal(readJson(join(local,"docs-receipt.json")).status, "both-sides-verified");
1000
+ }));
1001
+
1002
+ test("legacy absent policy retains previous explicit plaintext behavior without migration", () => {
1003
+ const status = emptyStatus(); delete status.policies.plaintext_documentation;
1004
+ assert.equal(docs.plaintextDocumentation(status), true);
1005
+ status.policies.plaintext_documentation = false;
1006
+ assert.equal(docs.plaintextDocumentation(status), false);
1007
+ });
1008
+
1009
+ test("credential-bearing config rejects permissive explicit file modes", () => runFx((fx) => {
1010
+ const spec = fx.spec();
1011
+ spec.deployments[0].files.push({path:"config/auth.conf",content:"{{credential:app-auth@1:password}}",mode:0o644});
1012
+ assert.throws(() => fx.plan(spec), /secret-bearing|private|0600|permission/i);
1013
+ }));
@@ -397,7 +397,7 @@ export function emptyStatus() {
397
397
  allocations: {},
398
398
  bindings: {},
399
399
  releases: {},
400
- policies: { server_readme_credentials: false, server_operations: true, strict_docker_root: true },
400
+ policies: { server_readme_credentials: false, server_operations: true, strict_docker_root: true, plaintext_documentation: false },
401
401
  updated_at: null,
402
402
  };
403
403
  }
@@ -274,13 +274,13 @@ export function deploymentReadme(status, dep, runId, generatedAt, { ledger = nul
274
274
  out.push("## 同项目部署位置\n", replicas.sort((a, b) => a.deployment_id < b.deployment_id ? -1 : 1).map((d) => `- ${d.host_id} / ${d.deployment_id}:${code(d.root)}`).join("\n") + "\n");
275
275
  if (controller) out.push("\n本目录是部署机的配置、凭据、运行证据和远端文档记录;不声称自动复制了远端业务数据。数据备份需要独立的备份策略和回执。\n");
276
276
  if (includeCredentials) out.push(credentialsText(credentialRefs(status, dep), ledger));
277
- else out.push("\n## 凭据记录\n\n本 README 默认不写密码。获授权管理员读取本项目 OPERATIONS.md(启用时)或部署机明文手册;密码未知时必须补齐,不能编造。\n");
277
+ else out.push("\n## 凭据记录\n\nsecret_ref: " + credentialRefs(status, dep).map(code).join(", ") + "\n\n本文件默认不写密码。凭据按 ID@版本定位受限 private/credentials.json;真实值仅在批准 plaintext_documentation 后导出。未知值不得编造。\n");
278
278
  return out.join("\n") + "\n";
279
279
  }
280
280
 
281
281
  export function hostReadme(status, hid, runId, at, { ledger = null, full = false, controller = false } = {}) {
282
282
  const host = status.hosts[hid];
283
- const includeCredentials = Boolean(full && controller && ledger);
283
+ const includeCredentials = Boolean(full && controller && ledger && plaintextDocumentation(status));
284
284
  const rows = overviewRows(status, hid, { ledger, includeCredentials });
285
285
  const out = [
286
286
  `# 主机 ${host.display_name} / ${hid}\n`,
@@ -368,6 +368,11 @@ const KNOWLEDGE_INDEX = `# 共享知识索引
368
368
  只收录经用户确认、带来源和最后验证时间的知识,不存密码。
369
369
  `;
370
370
 
371
+ // Legacy absent field retains existing deliberate behavior; init seeds false. Changing policy requires a new approved plan.
372
+ export function plaintextDocumentation(status) {
373
+ return status.policies.plaintext_documentation ?? true;
374
+ }
375
+
371
376
  export function deliveryBundle(state, plan, status, ledger, verifiedIds) {
372
377
  const at = now();
373
378
  const rid = plan.run_id;
@@ -381,9 +386,9 @@ export function deliveryBundle(state, plan, status, ledger, verifiedIds) {
381
386
  for (const did of [...new Set(verifiedIds)].sort()) {
382
387
  const d = status.deployments[did];
383
388
  const hid = d.host_id;
384
- const readme = deploymentReadme(status, d, rid, at, { ledger, includeCredentials: status.policies.server_readme_credentials });
389
+ const readme = deploymentReadme(status, d, rid, at, { ledger, includeCredentials: Boolean(status.policies.server_readme_credentials && plaintextDocumentation(status)) });
385
390
  addRemote(hid, pathFor(status, d, "README.md"), readme);
386
- const operations = deploymentReadme(status, d, rid, at, { ledger, includeCredentials: true });
391
+ const operations = deploymentReadme(status, d, rid, at, { ledger, includeCredentials: plaintextDocumentation(status) });
387
392
  if (status.policies.server_operations) addRemote(hid, pathFor(status, d, "OPERATIONS.md"), operations);
388
393
  addRemote(hid, pathFor(status, d, "project.yaml"), JSON.stringify(d, null, 2) + "\n");
389
394
  addRemote(hid, pathFor(status, d, "run/release-state.json"), JSON.stringify({
@@ -397,7 +402,7 @@ export function deliveryBundle(state, plan, status, ledger, verifiedIds) {
397
402
  addRemote(hid, targetJoin({ ...status.hosts[hid], root: projectroot }, "README.md"), text);
398
403
  }
399
404
  const prefix = `hosts/${hid}/deployments/${did}`;
400
- local[prefix + "/README.md"] = deploymentReadme(status, d, rid, at, { ledger, includeCredentials: true, controller: true });
405
+ local[prefix + "/README.md"] = deploymentReadme(status, d, rid, at, { ledger, includeCredentials: plaintextDocumentation(status), controller: true });
401
406
  local[prefix + "/OPERATIONS.md"] = operations;
402
407
  local[prefix + "/deployment.json"] = JSON.stringify(d, null, 2) + "\n";
403
408
  local[prefix + "/server/README.md"] = readme;
@@ -420,8 +425,8 @@ export function deliveryBundle(state, plan, status, ledger, verifiedIds) {
420
425
  local[`hosts/${hid}/knowledge/host-services.json`] = hostServicesDocument(h);
421
426
  if (ingressJson) local[`hosts/${hid}/knowledge/public-ingress.json`] = ingressJson;
422
427
  }
423
- local["FLEET-DEPLOYMENTS.md"] = fleetDocument(status, Object.keys(status.hosts), rid, at, { ledger, includeCredentials: true });
424
- local["README.md"] = "# OPS 控制端\n\n主机记录位于 hosts/<host_id>/;项目关联位于 status.json;项目部署记录位于 hosts/<host_id>/deployments/<deployment_id>/;完整明文总册位于 FLEET-DEPLOYMENTS.md;凭据账本位于 private/credentials.json;执行证据按 host runs / release 保存。主机级入口见 hosts/<id>/knowledge/host-services.json,跨主机公网入口见 knowledge/public-ingress.json。\n\n不得清理此运行态目录来替换静态 workflow。双边文档完成由每次运行 docs-receipt.json 证明。\n";
428
+ local["FLEET-DEPLOYMENTS.md"] = fleetDocument(status, Object.keys(status.hosts), rid, at, { ledger, includeCredentials: plaintextDocumentation(status) });
429
+ local["README.md"] = "# OPS 控制端\n\n主机记录位于 hosts/<host_id>/;项目关联位于 status.json;项目部署记录位于 hosts/<host_id>/deployments/<deployment_id>/;总册位于 FLEET-DEPLOYMENTS.md(新安装默认仅引用凭据版本);凭据账本位于 private/credentials.json;执行证据按 host runs / release 保存。主机级入口见 hosts/<id>/knowledge/host-services.json,跨主机公网入口见 knowledge/public-ingress.json。\n\n不得清理此运行态目录来替换静态 workflow。双边文档完成由每次运行 docs-receipt.json 证明。\n";
425
430
  local["docs/standards/DEPLOYMENT-STANDARD.md"] = STANDARD;
426
431
  if (!existsSync(join(state, "knowledge", "INDEX.md"))) {
427
432
  local["knowledge/INDEX.md"] = "# 共享知识索引\n\n只收录经用户批准、带来源与最后验证日期的通用知识。运行时环境和密码不自动提升为共享知识。跨主机入口规范见各主机 knowledge/public-ingress.json。\n";
@@ -237,6 +237,7 @@ export function compilePlan(state, specPath) {
237
237
  const current = load(state);
238
238
  if (current.controller == null) throw new OpsError("initialize the controller first");
239
239
  const after = structuredClone(current);
240
+ if (spec.plaintext_documentation !== undefined) after.policies.plaintext_documentation = spec.plaintext_documentation;
240
241
  const ledger = ledgerLoad(state);
241
242
  const revisions = spec.resource_updates || {};
242
243
  for (const [group, idkey, fixed] of [["hosts", "host_id", ["host_id", "root", "identity", "platform"]], ["projects", "project_id", ["project_id", "kind", "service_type"]]]) {
@@ -277,7 +278,12 @@ export function compilePlan(state, specPath) {
277
278
  return op;
278
279
  };
279
280
  const fileOp = (hostId, did, path, { content = undefined, binary = undefined, mode = undefined } = {}) => {
280
- const data = { path, mode: mode ?? defaultFileMode(path) };
281
+ const secretBearing = credentialsIn(content ?? "").size > 0;
282
+ const selectedMode = mode ?? (secretBearing ? 0o600 : defaultFileMode(path));
283
+ if ((secretBearing || /(?:^|[\\/])env[\\/]/.test(path)) && (selectedMode & 0o077)) {
284
+ throw new OpsError("secret-bearing config/env mode must be 0600 or stricter; use explicit service ownership, not world-readable secrets");
285
+ }
286
+ const data = { path, mode: selectedMode };
281
287
  if (content !== undefined) data.content = content;
282
288
  else data.content_b64 = Buffer.from(binary).toString("base64");
283
289
  return add(hostId, did, "write", data);
@@ -3,7 +3,7 @@ id: specdev/goal-plan
3
3
  type: workflow-entry
4
4
  workflow: specdev
5
5
  name: Goal 规划与执行
6
- description: 为一个或多个 Ready change 规划、执行或恢复 Goal;只在用户要求交付编排或已有 map 需推进时使用,不代替需求探索和 Ticket 编写。
6
+ description: 为单个已拆票 change 或多个 Ready change 规划、执行或恢复 Goal;只在用户要求交付编排或已有 map 需推进时使用,不代替需求探索和 Ticket 编写。
7
7
  keywords: [goal, 目标, plan, run, resume, replan, verify, Lead]
8
8
  ---
9
9
 
@@ -27,6 +27,8 @@ keywords: [goal, 目标, plan, run, resume, replan, verify, Lead]
27
27
 
28
28
  单 change 的 `<Path>{roots.state}/specdev/changes/{change}/tickets-map.md</Path>` 是用户总控入口;Ticket frontmatter 仍是单票状态、依赖、路径的权威,Goal Plan 拥有跨票 Gate、Wave 和授权引用。少量线性票不强制增加厚重计划。
29
29
 
30
+ 单 change 的 Map 结构就绪、Goal 全局执行门与逐票 DoR 分开判断。等待上游真实产物的未来票可保持 draft,并记录缺口和重审条件;独立 Ready 票仍可进入 frontier。具体门禁读取 `<Path>{roots.workflows}/specdev/P-goal-plan/planning-modes.md</Path>`。
31
+
30
32
  多 change 复用现有父 Implementation Map/Implementation Plan 和组合 DAG,不迁走活动状态。父 `<Path>{roots.state}/specdev/changes/{change}/tickets-map.md</Path>` 仅作无状态入口,模板为 `<Path>{roots.workflows}/specdev/P-goal-plan/references/goal-tickets-map-template.md</Path>`。父成员至少两个;只有一个 change 时使用单 change 模式。
31
33
 
32
34
  ## 执行底线
@@ -47,4 +49,4 @@ keywords: [goal, 目标, plan, run, resume, replan, verify, Lead]
47
49
  node <Path>{roots.workflows}/specdev/common/tools/ticket-control.mjs</Path> --map <map-path> --repo <project-root>
48
50
  ```
49
51
 
50
- 再按单 change 的 `--stage goal-plan` 或父 change 的 `--stage goal-plan` 运行 `<Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path>`。完成时回读真实源、map、Ticket 状态和 Evidence,报告完成/阻塞/失效票、整体验收、实际交付数量、验证命令、未执行项与恢复路径。票全 done 不等于 Goal 自动完成。
52
+ 再以 `--stage goal-plan` 运行 `<Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path>`:单 change 要求自己的 Goal Plan,合法 draft 可通过结构校验;存在任一父 Map、Plan 或父入口时校验完整父编排,残缺父工件不得回退单 change。校验通过不授予执行权限。完成时回读真实源、map、Ticket 状态和 Evidence,报告完成/阻塞/失效票、整体验收、实际交付数量、验证命令、未执行项与恢复路径。票全 done 不等于 Goal 自动完成。
@@ -55,6 +55,8 @@ ready_for_execution: false
55
55
 
56
56
  ### Ticket Quick Reference
57
57
 
58
+ 未来 draft 票在其未决问题中记录待交付的上游产物、生产者 Ticket、所需证据和 DoR 重审条件;此表投影当前状态,不以 Goal Ready 代替逐票 Ready。
59
+
58
60
  | ID | 可观察产出 | Dependencies | Workspace | Implementation owner | E2E disposition | Evidence |
59
61
  |---|---|---|---|---|---|---|
60
62
  | T-01 | ... | — | `current`(required 模式为 `specdev-worktree/<change>/T-01`) | Lead / dynamic dispatch | required / not-required: reason | `<Path>{roots.state}/specdev/changes/{change}/evidence/T-01.md</Path>` |
@@ -2,23 +2,26 @@
2
2
 
3
3
  规划模式描述 Goal Plan 需要额外解决的工程问题。Goal Plan 创建时单独询问 Ticket 是否开启 worktree,默认使用当前 workspace;worktree 与 direct-parent/candidate-merge 由该次 Goal Plan 固定。
4
4
 
5
+ 本文件定义单 change 的规划与全局执行门。多 change 父编排创建仍要求成员全员 Ready,遵守 `<Path>{roots.workflows}/specdev/common/rules/parent-implementation-orchestration.md</Path>`。
6
+
5
7
  ## 1. 输入门禁
6
8
 
7
9
  开始规划前穷尽检查:
8
10
 
9
11
  - Spec `ready_for_tickets: true`,或上游工件已等价覆盖范围、合同与验收;
10
- - Tickets Map 与全部 Ticket 存在、Ready、DAG 无环;
12
+ - Tickets Map 与全部计划内 Ticket 存在,范围、合同覆盖、DAG 和所有权完整,DAG 无环;Map Ready 表示结构就绪,不要求所有未来 Ticket 同时 Ready;
13
+ - 等待真实上游产物的未来 Ticket 保持 `status: draft`、`ready: false`,在未决问题中记录缺口、生产者 Ticket、所需证据和 DoR 重审条件;不编造版本、路径或验证入口;
11
14
  - 每个验收合同被 Ticket 覆盖;
12
15
  - writable/shared path 有唯一 owner,Wave 候选无写冲突;
13
16
  - config schema v5,`max_implementation_agents` 与 `max_integration_attempts` 为正整数;UI 设计候选范围读取 planning 配置;
14
- - 父分支可定位,implementation commit 与本地 integration 已获授权;
15
- - Deep Ticket 的迁移、兼容、监控、恢复和不可逆批准点完整。
17
+ - 父分支可定位,implementation commit 与本地 integration 的授权状态已记录;`plan` 可以列出待批准项并保持 `ready_for_execution: false`;
18
+ - 待执行 Deep Ticket 的迁移、兼容、监控、恢复和不可逆批准点完整。
16
19
  - Ticket 与 `<Path>{roots.state}/specdev/changes/{change}/spec.md</Path>`、`<Path>{roots.state}/specdev/changes/{change}/ADR.md</Path>`、`<Path>{roots.state}/specdev/adr/</Path>`、`<Path>{roots.state}/specdev/context/</Path>` 和当前代码事实不存在未处理冲突;
17
20
  - 项目声明的验证命令真实存在,并能观察目标行为;不可运行项有替代证据或明确 blocker;
18
21
  - 当前源码基线、父分支、工作区状态和现有用户改动已经实测;
19
22
  - 外部合同、标准、参考实现或依赖版本已经固定,不使用浮动的“最新”描述。
20
23
 
21
- 缺失上游事实返回其 owner;非 v5 Goal Plan 必须按当前合同重新规划,不能只修改版本号。
24
+ 影响整体范围、DAG、合同覆盖或所有权的上游事实缺失时返回其 owner。已定位到未来 Ticket 的局部缺口只阻塞该票及其依赖闭包,不阻塞独立 Ready 生产者。非 v6 Goal Plan 必须按当前合同重新规划,不能只修改版本号。
22
25
 
23
26
  ## 2. 可组合模式
24
27
 
@@ -41,7 +44,7 @@
41
44
 
42
45
  ## 4. Ready 停止条件
43
46
 
44
- 存在以下任一情况时 `ready_for_execution: false`:
47
+ Goal 全局执行门与 Ticket DoR 分开判断。以下任一全局条件未满足时 `ready_for_execution: false`;局部 Ticket 的缺口、资源冲突或未通过 DoR 由逐票检查阻塞对应依赖闭包,不升级成全员停止:
45
48
 
46
49
  - Goal Plan 工作区选择未记录;
47
50
  - `current` 模式下 Ticket 无法串行排序或当前 workspace 不是唯一项目写入 owner;
@@ -49,12 +52,14 @@
49
52
  - 当前模式所需的 implementation commit 或 direct-parent/candidate integration 授权缺失;
50
53
  - shared path 没有唯一 owner;
51
54
  - E2E 是否需要会改变验收结论但尚未确定;
52
- - 项目验证命令不能执行或无法观察目标行为,且没有批准的替代证据;
55
+ - 整体目标的验证命令不能执行或无法观察目标行为,且没有批准的替代证据;
53
56
  - 当前源码/工作区基线未实测,或外部合同版本仍然浮动;
54
- - Ticket 与 Spec、ADR、`<Path>{roots.state}/specdev/adr/</Path>`、`<Path>{roots.state}/specdev/context/</Path>` 或代码事实存在未处理冲突;
55
- - 迁移、发布、不可逆动作或恢复存在高影响未知项;
57
+ - 影响整体目标的 Ticket 与 Spec、ADR、`<Path>{roots.state}/specdev/adr/</Path>`、`<Path>{roots.state}/specdev/context/</Path>` 或代码事实冲突未处理;
58
+ - 整体迁移、发布、不可逆动作或恢复存在高影响未知项;
56
59
  - 实现 agent 或 integration attempt 上限超过 config 或平台能力。
57
60
 
61
+ 全局门通过后仍只调度自身 Ready、依赖成功完成、owner 明确且资源/并发条件满足的 Ticket。上游 done 不自动修改消费者 Ready;Lead 回读真实产物,按 `<Path>{roots.workflows}/specdev/T-tickets/ticket-readiness.md</Path>` 重审后才能更新该票。frontier 为空且仍有未完成票时报告精确缺口与重审条件。
62
+
58
63
  ## 5. 固定执行拓扑
59
64
 
60
65
  - `orchestration: lead-directed`;
@@ -13,7 +13,7 @@ Lead 将输出保存到调用方自己的既有 Evidence 位置;不创建独
13
13
  ## 每轮循环
14
14
 
15
15
  1. 重读当前 map、Ticket frontmatter、Gate 与 owner;机器检查依赖、Skill、语义资源和路径,Lead 核验真实授权及当前 Git 事实。
16
- 2. 只有依赖成功满足、ready、owner 可判定且无冲突的票才能进入 dispatch。cancelled 不是成功交付:下游必须重规划依赖,不能自动视为 satisfied。
16
+ 2. 只有依赖成功满足、ready、owner 可判定且无冲突的票才能进入 dispatch。单 change 的未来 draft 票只阻塞自身及依赖闭包;上游 done 后仍需 Lead 按真实产物重审 DoR,不自动提升 Ready。cancelled 不是成功交付:下游必须重规划依赖,不能自动视为 satisfied。
17
17
  3. current 策略串行;required 仅在 config/宿主允许且写集、语义资源、integration queue 无冲突时并行。不同文件可能共享 API、数据表、锁文件或公共契约,因此不能只比较文件名。
18
18
  4. 按票的调用阶段读取并实际执行必需 Skill;按项目协议调用的“技能”可以是宿主技能调用,也可以是完整执行该 SKILL 的程序步骤,但必须记录对应步骤/工具轨迹与输出,不得仅记录阅读完成。
19
19
  5. I 返回后核对实现、Skill 执行记录、验证矩阵、实际交付数量和集成证据。失败保留 blocker;不能用减少测试或替换工具来“修好”状态。
@@ -50,14 +50,14 @@ Goal Plan 只拥有单个 Ticket 无法独立决定的事情:整体 Outcome、
50
50
 
51
51
  加载 `<Path>{roots.workflows}/specdev/P-goal-plan/planning-modes.md</Path>`:
52
52
 
53
- 1. 验证 Spec、Tickets、合同覆盖、DAG、路径所有权和 Deep Ticket 完整性;
53
+ 1. 验证 Spec、Tickets、合同覆盖、DAG、路径所有权和待执行 Deep Ticket 完整性;未来 draft 票按规划输入门记录局部缺口;
54
54
  2. 只读探索影响调度的代码与项目事实;
55
55
  3. 识别 migration、high-assurance、reference-conformance、release-coordination 等适用模式;
56
56
  4. 从 config 读取 `max_implementation_agents` 与 `max_integration_attempts`,将实际值快照到 `implementation_agent_limit` 与 `integration_attempt_limit`;本计划可以降低但不得超过 config 或平台能力,Lead 不计入;
57
57
  5. 根据 workspace 策略记录实现 commit 与 direct-parent/candidate integration 授权事实;缺失时仍可完成 plan 文档,但 ready_for_execution 保持 false,并列为 run 的阻塞条件;
58
58
  6. 只询问无法发现且会改变 Gate、Wave、owner、迁移、批准或验收的问题。
59
59
 
60
- **完成标准**:所有计划内 Ticket Ready;Lead、授权、实现并发上限和父分支可判定;没有用 Goal Plan 掩盖上游缺口。
60
+ **完成标准**:计划内 Ticket、范围、覆盖、DAG 与所有权完整;未来 draft 票已记录真实缺口、生产者证据和 DoR 重审条件;Lead、授权状态、实现并发上限和父分支可判定。缺执行授权时可交付规划,但 `ready_for_execution` 保持 `false`。
61
61
 
62
62
  ### 2. 构建 Outcome、DAG、Wave 与 Gate
63
63
 
@@ -133,6 +133,8 @@ Goal Plan 不复制 Ticket 的局部施工路线、全部文件预测或逐项
133
133
  ## 完成标准
134
134
 
135
135
  - Goal Plan schema v6 且 `ready_for_execution` 与状态一致;
136
+ - 规划完成与执行就绪分别报告;允许交付 `draft / ready_for_execution: false`,单 change 不创建父 Implementation Map/Plan;
137
+ - 等待上游产物的未来票保持 draft,局部缺口只阻塞依赖闭包;进入执行的票必须重新通过 DoR,上游完成不自动提升 Ready;
136
138
  - Lead 唯一,implementation subagent 上限来自 config/平台能力,review/research agent 不受 SpecDev 数字限制;
137
139
  - 每个实现 Ticket 都有 workspace、commit、对应 integration gate 和 Evidence 出口;
138
140
  - current 模式不创建 source/candidate worktree,适用 E2E 由 Lead 在 current workspace 运行;required 模式保持 source/parent-candidate 边界;
@@ -47,7 +47,7 @@ CLI 初始化和刷新保持原 namespace、三方配置合并、schema migrator
47
47
 
48
48
  ## 场景路由
49
49
 
50
- 不清晰的大需求选 W,单 change 的高影响决定选 G,Ready Spec 拆票选 T-tickets,一个或多个 Ready change 的 Goal 选 P-goal-plan;来源冻结/远程回写/完成后记账/尚未成 Change 的记事项才选 T-triage。精确条件与全部出口见 `<Path>{roots.workflows}/specdev/common/rules/workflow-routing.md</Path>`。
50
+ 不清晰的大需求选 W,单 change 的高影响决定选 G,Ready Spec 拆票选 T-tickets,单个已拆票 change 或多个 Ready change 的 Goal 选 P-goal-plan;来源冻结/远程回写/完成后记账/尚未成 Change 的记事项才选 T-triage。精确条件与全部出口见 `<Path>{roots.workflows}/specdev/common/rules/workflow-routing.md</Path>`。
51
51
 
52
52
  ## Work 条目
53
53
 
@@ -61,7 +61,7 @@ CLI 初始化和刷新保持原 namespace、三方配置合并、schema migrator
61
61
  - **I-implement** — 实现与验收:执行已授权的 Ready Ticket 或获批 Direct Spec,产生可回读实现和验收证据;不从模糊需求直接写代码。
62
62
  - **I-init-setup** — 初始化设置:初始化 SpecDev 的语言、配置、全局状态、本地 change 追踪、领域知识布局、验证命令和并发治理。
63
63
  - **L-learn-change** — Change 学习:在开发完成后围绕当前 SpecDev change 回答问题,并用面向零专业背景读者的 Markdown 与 ASCII 图解持续记录理解。
64
- - **P-goal-plan** — Goal 规划与执行:为一个或多个 Ready change 规划、执行或恢复 Goal;只在用户要求交付编排或已有 map 需推进时使用,不代替需求探索和 Ticket 编写。
64
+ - **P-goal-plan** — Goal 规划与执行:为单个已拆票 change 或多个 Ready change 规划、执行或恢复 Goal;只在用户要求交付编排或已有 map 需推进时使用,不代替需求探索和 Ticket 编写。
65
65
  - **P-prototype** — UI 设计原型:检测现有项目的 UI 事实,按产品任务推荐并逐步选择设计风格,生成持久化设计系统文档、多风格 HTML 对照和可运行 HTML/CSS/JS 原型。
66
66
  - **R-review-architecture** — 架构审查:从用户指定范围或 Git 热点扫描代码库中的结构性坏味道、代码 judo 机会和维护性风险,以中文 Markdown 记录高置信候选,并对用户选择的一个方案运行设计树访谈。
67
67
  - **S-spec** — 编写 Spec:综合已知事实、设计决定、诊断与代码现状,产出以外部行为和验收合同为权威的 Ready Spec。
@@ -116,3 +116,9 @@ SpecDev 不假定运行时一定提供项目根变量。Ticket、Evidence、诊
116
116
  - 目录引用是否以 `/` 结束且文件引用是否不以 `/` 结束。
117
117
 
118
118
  路径检查通过仅表示引用结构正确,不代表工件语义、需求、设计或实现正确。
119
+
120
+ ## 9. 与标准 Skill 的分发边界
121
+
122
+ 本文件是 SpecDev 内部动态工件协议,不是通用 Agent Skills 的文件格式规范。独立标准 Skill 的静态 references/assets 使用相对路径;workflow 私有 Skill 声明 compatibility 后继续使用本 Path 合同。已有持久化引用不批量迁移。
123
+
124
+ 实际执行前可以使用只读 `speculo resolve <project-root> --path '<Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path>'` 展开单个指针。解析器先打开字面 workspace 路径,拒绝未知 alias、未替换变量、通配符、越界路径与链接跳转;不执行 shell、不授予文件写入权限。完整命令由已获授权调用方使用参数数组构造,不把 Path 标记或用户文本直接交给 shell。
@@ -14,7 +14,7 @@
14
14
  | 固定点 diff、branch 或 PR review | C-code-review | completed / T / S / G |
15
15
  | 外部行为已清楚 | S-spec | T-tickets |
16
16
  | Ready Spec 需要垂直切片 | T-tickets | P-goal-plan / I |
17
- | 单个或多个 Ready change 的 Goal 规划、持续实现与恢复 | P-goal-plan | I / verify / completed / blocked |
17
+ | 单个已拆票 change 或多个 Ready change 的 Goal 规划、持续实现与恢复 | P-goal-plan | I / verify / completed / blocked |
18
18
  | 旧 O 调用或恢复键 | P-goal-plan | 统一 P 多 change 模式 |
19
19
  | Ready 执行 | I-implement | Triage / A / blocked / deviation |
20
20
  | 开发完成后需要理解当前 change 或追问实现 | L-learn-change | 返回用户 / 继续提问 / Triage / A |
@@ -23,4 +23,4 @@
23
23
  同 change 下一阶段需要当前一手推理且上下文健康时继续;切换 repo/person/harness 或旁路时使用 `<Path>{roots.commands}/handoff.md</Path>`;严格限定且可独立派单时使用 Dispatch Packet;其他长上下文以权威工件路径恢复。平台不支持 clear/compact 时不虚构操作。
24
24
 
25
25
 
26
- 本地已清晰请求不强制绕行 Triage。完成后要记账才选 publish;尚未成 Change、只想先留 inbox 记录才选 capture。publish、reconcile 与 capture 写不同远程对象,可先后执行。G 是单 change Grill,P-prototype 是 UI 设计,不与统一 P-goal-plan 混淆。W 发现候选后逐个交 G/S/T;只有清晰且 Ready 的 child 才交 P。原缺陷诊断 D、风险分诊、远程 reconcile、完成后发布投影和记事项捕获职责不删除。
26
+ 本地已清晰请求不强制绕行 Triage。完成后要记账才选 publish;尚未成 Change、只想先留 inbox 记录才选 capture。publish、reconcile 与 capture 写不同远程对象,可先后执行。G 是单 change Grill,P-prototype 是 UI 设计,不与统一 P-goal-plan 混淆。W 发现候选后逐个交 G/S/T;单 change 拆票后可交 P 规划,多个 child 创建父 Goal 前仍须全部 Ready。原缺陷诊断 D、风险分诊、远程 reconcile、完成后发布投影和记事项捕获职责不删除。
@@ -1,6 +1,8 @@
1
1
  ---
2
- name: specdev-code-review
2
+ name: code-review
3
3
  description: 从不可变固定点对本地 diff 执行隔离的标准轴与规范轴审查,供独立 C Work 和 I Work 最终审查共同调用。
4
+ compatibility: Requires the owning Speculo workflow and its workspace path resolver.
5
+ metadata: {"speculo-legacy-name": "specdev-code-review"}
4
6
  ---
5
7
 
6
8
  # SpecDev Code Review
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: dev-worktree
3
3
  description: 管理 SpecDev required Ticket 的 Git worktree;创建隔离来源 workspace、固定实现 commit,并由 Lead 在 parent-candidate checkout 验证后推进父分支。
4
+ compatibility: Requires the owning Speculo workflow and its workspace path resolver.
4
5
  ---
5
6
 
6
7
  # Dev Worktree
@@ -1,6 +1,8 @@
1
1
  ---
2
- name: specdev-plan-quality-review
2
+ name: plan-quality-review
3
3
  description: T 发布计划型 Ticket 或 P 发布/重规划 Goal 前检查决策完备、Skill 调用和执行门禁;不执行代码审查或代替用户批准。
4
+ compatibility: Requires the owning Speculo workflow and its workspace path resolver.
5
+ metadata: {"speculo-legacy-name": "specdev-plan-quality-review"}
4
6
  ---
5
7
 
6
8
  # Plan Quality Review
@@ -1,6 +1,8 @@
1
1
  ---
2
- name: specdev-research
2
+ name: research
3
3
  description: 为调用 Work 的具体决定研究外部技术、依赖、标准或未知行为,逐声明返回一手来源、限制与不确定性,由调用方写入其拥有的工件。
4
+ compatibility: Requires the owning Speculo workflow and its workspace path resolver.
5
+ metadata: {"speculo-legacy-name": "specdev-research"}
4
6
  ---
5
7
 
6
8
  # SpecDev Research
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: subagent-delivery
3
3
  description: 仅在 Lead 规划派单合同、发出受限任务或验收其返回时调用;不是通用实现入口,外部网页交付仍为 ZIP-only。
4
+ compatibility: Requires the owning Speculo workflow and its workspace path resolver.
4
5
  ---
5
6
 
6
7
  # Subagent Delivery
@@ -9,7 +9,11 @@ node <Path>{roots.workflows}/specdev/common/tools/validate-specdev.mjs</Path> \
9
9
  <Path>{roots.state}/specdev/changes/{change}</Path>
10
10
  ```
11
11
 
12
- `--stage` 只要求该阶段已经拥有的工件;所有已经存在的工件仍会验证。`goal-plan` 还会读取父 change 的 sibling 成员,要求每个成员已有 Ready Spec/Tickets,校验组合 Ticket DAG、唯一父归属、serialization、跨 Ticket 写路径、全局 workspace/实现配额和完成门。省略 stage 时验证当前存在的工件,不会因未来 Work 尚未运行而报错。`--repo` 可选;提供后会把状态中的 SHA、祖先关系、当前分支和完成时 clean 状态与真实 Git 仓库交叉验证。
12
+ `--stage` 只要求该阶段已经拥有的工件;所有已经存在的工件仍会验证。单 change 的 `goal-plan` 要求自己的 Goal Plan,允许合法的 `draft / ready_for_execution: false`;Tickets 阶段可尚无 Goal。Goal 的就绪字段必须是布尔值并与状态对应。
13
+
14
+ 存在 Implementation Map、Implementation Plan 或 goal-tickets-map 入口中的任一项即走父编排校验;缺少另一父工件会报错,不回退单 change,也不额外要求单 change Goal。父编排读取 sibling 成员,创建时要求每个成员已有 Ready Spec/Tickets,继续校验组合 Ticket DAG、唯一父归属、serialization、跨 Ticket 写路径、全局 workspace/实现配额和完成门。已有父 Map/Plan 尚未补建无状态入口时仍可验证。
15
+
16
+ 省略 stage 时验证当前存在的工件,不会因未来 Work 尚未运行而报错。`--repo` 可选;提供后会把状态中的 SHA、祖先关系、当前分支和完成时 clean 状态与真实 Git 仓库交叉验证。
13
17
 
14
18
  校验 workspace 捕获账本(文件必须已存在;缺失时不要为了校验去创建):
15
19
 
@@ -34,6 +38,8 @@ node <Path>{roots.workflows}/specdev/common/tools/ticket-control.mjs</Path> --ma
34
38
 
35
39
  接受普通 tickets-map、父 goal-tickets-map 入口或旧 Implementation Map。可选 --previous 读取上轮 JSON 输出作漂移检查;该输出应保存到调用方已有 Evidence,不建立第二套权威状态。输出 frontier、blocked、deferred、in_flight、invalidated 与逐票契约摘要。
36
40
 
41
+ 单 change 的 Goal Ready 不要求所有未来票同时 Ready。合法 draft 票保留缺口与重审条件,只阻塞自身及依赖闭包;`ready/in_progress/review` 却没有 `ready=true` 是逐票错误。frontier 只包含自身 Ready、依赖成功完成、owner/资源/并发条件满足的票;上游 done 不自动修改消费者状态。全局执行门未满足时无可派单票,空 frontier 的 blocked/deferred 说明恢复条件。多 change 父创建的全员 Ready 输入门保持不变。
42
+
37
43
  只读工具不调用 Skill、不执行实现、不检查真实授权/正式记忆网关,不获取锁或自动解锁;Lead 必须在 dispatch 前完成这些检查。结构错误返回非零;局部错误仍可能带独立 frontier,调用方必须检查节点和全局 diagnostics。结构检查成功的退出码 0 不代表任务完成,且业务门禁可能仍阻塞全部票。eligible_for_final_verification 只表示可进入最终验收,不是 Goal completed。
38
44
 
39
45
  `<Path>{roots.workflows}/specdev/common/tools/plan-contract.mjs</Path>` 是两个工具共用的只读校验库:新 Ticket 的真实 Skill 名称/摘要、必需调用证据、Map 数量合同、父入口与 W 候选图。普通 --repo 仍支持原 Git 事实验证;存在未完成票的真实项目 Skill 绑定时必须提供项目根。旧票可读,开始新实现前必须显式补齐 Plan 合同;已完成历史证据不要求当前 Skill 包仍与历史版本相同。