create-agent-rig 0.9.0 → 0.10.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.
Files changed (237) hide show
  1. package/CHANGELOG.md +466 -3
  2. package/README.md +267 -299
  3. package/package.json +8 -14
  4. package/packages/cli/dist/commands/create.js +56 -88
  5. package/packages/cli/dist/commands/doctor.js +213 -0
  6. package/packages/cli/dist/commands/init.js +151 -54
  7. package/packages/cli/dist/commands/integrations.js +468 -0
  8. package/packages/cli/dist/commands/memory.js +63 -4
  9. package/packages/cli/dist/commands/setup-wizard.js +61 -0
  10. package/packages/cli/dist/commands/uninstall.js +1223 -0
  11. package/packages/cli/dist/commands/upgrade.js +489 -89
  12. package/packages/cli/dist/index.js +657 -49
  13. package/packages/cli/dist/integrations/declaration.js +158 -0
  14. package/packages/cli/dist/integrations/doctor-guards.js +126 -0
  15. package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
  16. package/packages/cli/dist/integrations/mcp-json.js +107 -0
  17. package/packages/cli/dist/integrations/memory-doctor.js +124 -0
  18. package/packages/cli/dist/integrations/registry.js +22 -0
  19. package/packages/cli/dist/integrations/spawn.js +228 -0
  20. package/packages/cli/dist/integrations/spec-kit.js +280 -0
  21. package/packages/cli/dist/integrations/verify.js +193 -0
  22. package/packages/cli/dist/integrations/windows-job.js +275 -0
  23. package/packages/cli/dist/lib/elevated-paths.js +74 -0
  24. package/packages/cli/dist/lib/install-set.js +1 -44
  25. package/packages/cli/dist/lib/manifest.js +86 -9
  26. package/packages/cli/dist/lib/prompts.js +0 -23
  27. package/packages/cli/dist/lib/safe-path.js +193 -2
  28. package/packages/cli/dist/lib/safe-text.js +78 -0
  29. package/packages/cli/dist/lib/substitute.js +1 -52
  30. package/packages/cli/dist/lib/summary.js +5 -5
  31. package/packages/cli/dist/templates.js +4 -14
  32. package/scripts/prepare.mjs +28 -12
  33. package/templates/agent-os/subagent-routing.json +4 -4
  34. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
  35. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
  36. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
  37. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
  38. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
  39. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
  40. package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
  41. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
  42. package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
  43. package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
  44. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
  45. package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
  46. package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
  47. package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
  48. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
  49. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
  50. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
  51. package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
  52. package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +43 -0
  53. package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +18 -1
  54. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
  55. package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +273 -35
  56. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
  57. package/templates/agent-os/universal/.claude/settings.json +0 -8
  58. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
  59. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
  60. package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
  61. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
  62. package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
  63. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
  64. package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
  65. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
  66. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
  67. package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
  68. package/templates/agent-os/universal/.codex/hooks.json +0 -10
  69. package/templates/agent-os/universal/AGENTS.md +208 -107
  70. package/templates/agent-os/universal/CLAUDE.md +15 -162
  71. package/templates/agent-os/universal/PLAN.md +17 -10
  72. package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
  73. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
  74. package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
  75. package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
  76. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
  77. package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
  78. package/templates/agent-os/universal/layers.json +25 -30
  79. package/templates/hash-history.json +73 -24
  80. package/templates/release-ledger.json +3 -1
  81. package/packages/cli/dist/lib/composition.js +0 -20
  82. package/packages/cli/dist/lib/targets.js +0 -28
  83. package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
  84. package/packages/cli/dist/policy/core/adapter.js +0 -18
  85. package/packages/cli/dist/policy/core/coverage.js +0 -253
  86. package/packages/cli/dist/policy/core/decision-record.js +0 -287
  87. package/packages/cli/dist/policy/core/declaration.js +0 -127
  88. package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
  89. package/packages/cli/dist/policy/core/probe.js +0 -442
  90. package/packages/cli/dist/policy/core/registry.js +0 -115
  91. package/packages/cli/dist/policy/core/validation.js +0 -275
  92. package/packages/cli/dist/policy/core/vocabulary.js +0 -123
  93. package/packages/cli/dist/policy/harness/claude.js +0 -47
  94. package/packages/cli/dist/policy/harness/codex.js +0 -87
  95. package/packages/cli/dist/policy/harness/index.js +0 -15
  96. package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
  97. package/packages/cli/dist/policy/index.js +0 -17
  98. package/templates/agent-os/init/AGENTS.md +0 -201
  99. package/templates/agent-os/init/CLAUDE.md +0 -201
  100. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
  101. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
  102. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
  103. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
  104. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
  105. package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
  106. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
  107. package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
  108. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
  109. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
  110. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
  111. package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
  112. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
  113. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
  114. package/templates/skeleton/aws-serverless/README.md +0 -181
  115. package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
  116. package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
  117. package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
  118. package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
  119. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
  120. package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
  121. package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
  122. package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
  123. package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
  124. package/templates/skeleton/aws-serverless/gitignore +0 -56
  125. package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
  126. package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
  127. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
  128. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
  129. package/templates/skeleton/aws-serverless/infra/package.json +0 -18
  130. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
  131. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
  132. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
  133. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
  134. package/templates/skeleton/aws-serverless/package.json +0 -30
  135. package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
  136. package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
  137. package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
  138. package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
  139. package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
  140. package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
  141. package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
  142. package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
  143. package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
  144. package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
  145. package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
  146. package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
  147. package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
  148. package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
  149. package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
  150. package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
  151. package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
  152. package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
  153. package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
  154. package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
  155. package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
  156. package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
  157. package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
  158. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
  159. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
  160. package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
  161. package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
  162. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
  163. package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
  164. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
  165. package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
  166. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
  167. package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
  168. package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
  169. package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
  170. package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
  171. package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
  172. package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
  173. package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
  174. package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
  175. package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
  176. package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
  177. package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
  178. package/templates/skeleton/node-service/README.md +0 -104
  179. package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
  180. package/templates/skeleton/node-service/apps/web/package.json +0 -19
  181. package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
  182. package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
  183. package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
  184. package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
  185. package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
  186. package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
  187. package/templates/skeleton/node-service/eslint.config.mjs +0 -20
  188. package/templates/skeleton/node-service/gitignore +0 -54
  189. package/templates/skeleton/node-service/package.json +0 -30
  190. package/templates/skeleton/node-service/packages/core/package.json +0 -11
  191. package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
  192. package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
  193. package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
  194. package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
  195. package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
  196. package/templates/skeleton/node-service/packages/db/package.json +0 -12
  197. package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
  198. package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
  199. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
  200. package/templates/skeleton/node-service/packages/shared/package.json +0 -11
  201. package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
  202. package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
  203. package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
  204. package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
  205. package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
  206. package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
  207. package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
  208. package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
  209. package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
  210. package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
  211. package/templates/skeleton/node-service/services/api/package.json +0 -17
  212. package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
  213. package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
  214. package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
  215. package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
  216. package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
  217. package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
  218. package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
  219. package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
  220. package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
  221. package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
  222. package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
  223. package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
  224. package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
  225. package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
  226. package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
  227. package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
  228. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
  229. package/templates/skeleton/node-service/services/worker/package.json +0 -16
  230. package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
  231. package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
  232. package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
  233. package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
  234. package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
  235. package/templates/skeleton/node-service/tsconfig.base.json +0 -15
  236. package/templates/skeleton/node-service/tsconfig.json +0 -13
  237. package/templates/skeleton/node-service/vitest.config.ts +0 -12
@@ -1,23 +1,60 @@
1
- import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
1
+ import { access, lstat, mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { settingsForInstalledHooks } from '../lib/init-settings.js';
4
4
  import { mapConcurrent } from '../lib/copy-tree.js';
5
- import { readManifest, sha256, writeManifest } from '../lib/manifest.js';
5
+ import { ALL_LAYERS, DEFAULT_LAYERS, MANIFEST_REL, readManifest, sha256, writeManifest, } from '../lib/manifest.js';
6
+ import { resolveWritableInside } from '../lib/safe-path.js';
6
7
  import { substituteContent } from '../lib/substitute.js';
7
- import { agentOsInitDir, agentOsUniversalDir } from '../templates.js';
8
+ import { agentOsUniversalDir } from '../templates.js';
8
9
  import { packageVersion } from '../lib/version.js';
9
10
  /** A user-facing failure: message is printed as-is, no stack trace. */
10
11
  export class InitError extends Error {
11
12
  }
12
13
  /** What `--force` answers with now that `upgrade` owns the case it stood in for. */
13
14
  export const FORCE_DEPRECATED = 'deprecated — init --force replaced only CLAUDE.md; run create-agent-rig upgrade instead';
15
+ /**
16
+ * The layers this install writes, given what a previous run (if any)
17
+ * recorded and whether this run opted in.
18
+ *
19
+ * Never narrows what a previous run already installed: `--layer workflow` is
20
+ * additive, and a rig that already carries the workflow layer keeps it on a
21
+ * plain re-run with no flag (RP-180's "existing dogfood repositories can
22
+ * explicitly retain the layer" applies to `init` re-runs, not only to
23
+ * `upgrade`).
24
+ */
25
+ function effectiveLayers(previous, withWorkflow) {
26
+ const wantsWorkflow = withWorkflow || (previous?.layers.includes('workflow') ?? false);
27
+ return wantsWorkflow ? [...ALL_LAYERS] : [...DEFAULT_LAYERS];
28
+ }
14
29
  const SETTINGS = '.claude/settings.json';
15
30
  const CODEX_HOOKS = '.codex/hooks.json';
16
31
  const MAPS = ['CLAUDE.md', 'AGENTS.md'];
32
+ /**
33
+ * Plain, static files this install always ships alongside the process layer,
34
+ * beside the two generated wiring files and the two maps above — neither
35
+ * architecture-specific (RP-177 retired that group entirely) nor subject to
36
+ * the maps' overwrite refusal, so they are appended here rather than folded
37
+ * into either list.
38
+ */
39
+ const STATIC_EXTRAS = ['.codex/config.toml'];
17
40
  async function loadManifest() {
18
41
  const raw = await readFile(path.join(agentOsUniversalDir(), 'layers.json'), 'utf8');
19
42
  return JSON.parse(raw);
20
43
  }
44
+ /**
45
+ * Exactly `layers.json`'s own array for one layer — never the always-added
46
+ * extras (`SETTINGS`, `CODEX_HOOKS`, `STATIC_EXTRAS`, `MAPS`) `initManifest`
47
+ * appends to every layer regardless of which one was asked for. A caller
48
+ * that wants to know whether a SPECIFIC layer's own files are on disk (RP-180
49
+ * round 3, `commands/upgrade.ts`'s `detectLayersOnDisk`) needs this
50
+ * distinction: those extras exist for any rig at all, Core-only included, so
51
+ * checking `initManifest([layer])`'s full output against disk would report
52
+ * every layer as "present" always.
53
+ */
54
+ export async function layerOnlyPaths(layer) {
55
+ const manifest = await loadManifest();
56
+ return manifest[layer];
57
+ }
21
58
  async function exists(p) {
22
59
  try {
23
60
  await access(p);
@@ -41,30 +78,25 @@ export function projectNameFor(repoDir) {
41
78
  return slug === '' ? 'project' : slug;
42
79
  }
43
80
  /**
44
- * `init` installs only the PROCESS layer (hooks-and-reach brief §3/§4): rules
45
- * that assume nothing about the codebase shape. Architecture rules reference
46
- * `packages/core` and friends installing them into an arbitrary repo would
47
- * describe a structure that does not exist, which is worse than no rule.
48
- *
49
- * It also installs two things the process manifest does not name, because both
50
- * are meaningless in the generated shape and load-bearing here:
81
+ * `init` installs the PROCESS layer (hooks-and-reach brief §3/§4): rules that
82
+ * assume nothing about the codebase shape. Since RP-177 it is the ONLY
83
+ * flavour `create-agent-rig` shipsthe architecture group (rules describing
84
+ * `packages/core` and friends) was retired outright, not merely excluded here,
85
+ * so there is no wider install for `create` to fall back to and no override
86
+ * layer left to shadow a universal file.
51
87
  *
52
- * - `CLAUDE.md` the map, taken from the init override layer, which describes
53
- * the rig this command installs rather than the generated monorepo;
54
- * - `.claude/settings.json` — the wiring, derived from the shipped settings so
55
- * it names exactly the hooks that travelled.
88
+ * `.claude/settings.json` and `.codex/hooks.json` are generated rather than
89
+ * copied: derived from the shipped settings so they name exactly the hooks
90
+ * that travelled.
56
91
  */
57
- export async function initManifest() {
92
+ export async function initManifest(layers = DEFAULT_LAYERS) {
58
93
  const manifest = await loadManifest();
59
94
  const universal = agentOsUniversalDir();
60
- const override = agentOsInitDir();
61
- const files = await mapConcurrent([...manifest.process, ...MAPS], 16, async (rel) => {
62
- const overridden = path.join(override, rel);
63
- return {
64
- rel,
65
- source: (await exists(overridden)) ? overridden : path.join(universal, rel),
66
- };
67
- });
95
+ const layerFiles = layers.flatMap((layer) => manifest[layer]);
96
+ const files = [...layerFiles, ...STATIC_EXTRAS, ...MAPS].map((rel) => ({
97
+ rel,
98
+ source: path.join(universal, rel),
99
+ }));
68
100
  files.push({ rel: SETTINGS, source: null });
69
101
  files.push({ rel: CODEX_HOOKS, source: null });
70
102
  return files;
@@ -78,14 +110,12 @@ export async function initManifest() {
78
110
  * `__PROJECT_NAME__` in `stop-flag.mjs` is a kill switch that silently never
79
111
  * fires.
80
112
  */
81
- export async function initFileContents(repoDir, project) {
113
+ export async function initFileContents(repoDir, project, layers = DEFAULT_LAYERS) {
82
114
  const projectName = project?.name ?? projectNameFor(repoDir);
83
115
  const ctx = {
84
116
  projectName,
85
- projectScope: project?.scope ?? projectName,
86
- region: project?.region ?? '',
87
117
  };
88
- const files = await initManifest();
118
+ const files = await initManifest(layers);
89
119
  const contents = new Map();
90
120
  const sourceFiles = files.filter((file) => file.source !== null);
91
121
  const rendered = await mapConcurrent(sourceFiles, 16, async ({ rel, source }) => ({
@@ -107,13 +137,15 @@ export async function initFileContents(repoDir, project) {
107
137
  return contents;
108
138
  }
109
139
  /** The process layer as a set of {@link InstalledFile}s — what `upgrade` reads. */
110
- export async function initInstallSet(repoDir, project) {
111
- const files = await initManifest();
112
- const contents = await initFileContents(repoDir, project);
140
+ export async function initInstallSet(repoDir, project, layers = DEFAULT_LAYERS) {
141
+ const files = await initManifest(layers);
142
+ const contents = await initFileContents(repoDir, project, layers);
113
143
  return files.map(({ rel, source }) => ({ rel, source, content: contents.get(rel) ?? '' }));
114
144
  }
115
- export async function planInit(repoDir) {
116
- const files = (await initManifest()).map((f) => f.rel);
145
+ export async function planInit(repoDir, options = {}) {
146
+ const previous = await readManifest(repoDir);
147
+ const layers = effectiveLayers(previous, options.withWorkflow === true);
148
+ const files = (await initManifest(layers)).map((f) => f.rel);
117
149
  const conflicts = (await mapConcurrent(files, 16, async (rel) => (await exists(path.join(repoDir, rel))) ? rel : null)).filter((rel) => rel !== null);
118
150
  return { files: files.map((p) => ({ path: p })), conflicts };
119
151
  }
@@ -123,28 +155,46 @@ export async function initProject(repoDir, options) {
123
155
  // file from the manifest instead of overriding one refusal wholesale.
124
156
  if (options.force)
125
157
  throw new InitError(FORCE_DEPRECATED);
126
- const files = (await initManifest()).map((f) => f.rel);
127
158
  const previous = await readManifest(repoDir);
159
+ const layers = effectiveLayers(previous, options.withWorkflow === true);
160
+ const files = (await initManifest(layers)).map((f) => f.rel);
161
+ // Resolve the whole write set before the first edit. A lexical child can
162
+ // still escape through a symlink at the leaf or in any existing parent, and
163
+ // discovering that after another payload file was written would leave a
164
+ // partial install. The manifest is a write too, even on an otherwise-empty
165
+ // re-run, so it belongs in the same preflight.
166
+ const destinations = new Map();
167
+ for (const rel of [...files, MANIFEST_REL]) {
168
+ const dest = await resolveWritableInside(repoDir, rel);
169
+ if (dest === null) {
170
+ throw new InitError(`Refusing to write "${rel}" through a symlink or outside ${repoDir}.`);
171
+ }
172
+ destinations.set(rel, dest);
173
+ }
128
174
  // Refuse to clobber an existing CLAUDE.md — init edits someone's working
129
175
  // repository (brief §4, non-negotiable).
130
176
  for (const map of MAPS) {
131
- if (files.includes(map) && (await exists(path.join(repoDir, map)))) {
132
- // A create rig whose CLAUDE.md was deleted is the legacy route into init.
133
- // Its generated AGENTS.md must not newly close that route, but only the
134
- // manifest can distinguish that file from a user's own Codex guidance.
135
- if (map === 'AGENTS.md' &&
136
- previous?.files[map] !== undefined &&
137
- sha256(await readFile(path.join(repoDir, map), 'utf8')) === previous.files[map]) {
177
+ const dest = destinations.get(map);
178
+ if (dest !== undefined && (await exists(dest))) {
179
+ // A map this rig wrote and that still matches its manifest is safe to
180
+ // skip on an idempotent re-run. An unrecorded or edited map remains the
181
+ // user's guidance and is still refused.
182
+ if (previous?.files[map] !== undefined &&
183
+ sha256(await readFile(dest)) === previous.files[map]) {
138
184
  continue;
139
185
  }
140
- throw new InitError(`This repo already has an ${map}. Refusing to overwrite it. ` +
186
+ // "AGENTS.md" starts with a vowel SOUND ("a" said as a letter, /eɪ/);
187
+ // "CLAUDE.md" does not — so the article is picked per file rather than
188
+ // hardcoded to "an", which read as "an CLAUDE.md".
189
+ const article = /^[aeiou]/i.test(map) ? 'an' : 'a';
190
+ throw new InitError(`This repo already has ${article} ${map}. Refusing to overwrite it. ` +
141
191
  'Merge the agent-os map in by hand, or run create-agent-rig upgrade to refresh a rig.');
142
192
  }
143
193
  }
144
- const contents = await initFileContents(repoDir);
194
+ const contents = await initFileContents(repoDir, options.project, layers);
145
195
  const plannedCount = files.length;
146
196
  const actions = await mapConcurrent(files, 16, async (rel) => {
147
- const dest = path.join(repoDir, rel);
197
+ const dest = destinations.get(rel);
148
198
  if (await exists(dest)) {
149
199
  // never overwrite a file init did not write (a user's own copy)
150
200
  return { rel, verdict: 'skipped' };
@@ -152,23 +202,51 @@ export async function initProject(repoDir, options) {
152
202
  if (options.dryRun)
153
203
  return { rel, verdict: 'planned' };
154
204
  await mkdir(path.dirname(dest), { recursive: true });
155
- await writeFile(dest, contents.get(rel) ?? '');
205
+ const checked = await resolveWritableInside(repoDir, rel);
206
+ if (checked === null) {
207
+ throw new InitError(`Refusing to write "${rel}" through a symlink or outside ${repoDir}.`);
208
+ }
209
+ await writeFile(checked, contents.get(rel) ?? '');
156
210
  return { rel, verdict: 'written' };
157
211
  });
158
212
  const written = actions.filter(({ verdict }) => verdict === 'written').map(({ rel }) => rel);
159
213
  const skipped = actions.filter(({ verdict }) => verdict === 'skipped').map(({ rel }) => rel);
160
- if (!options.dryRun)
161
- await recordInstall(repoDir, written, contents);
214
+ if (!options.dryRun) {
215
+ await recordInstall(repoDir, written, skipped, contents, layers, options.project);
216
+ }
162
217
  return { written, skipped, plannedCount };
163
218
  }
219
+ /**
220
+ * The text of a regular file, or `null` for anything else at that path — a
221
+ * directory, a symlink, a file this process cannot read. `kept` records only
222
+ * bytes that are the file itself: hashing through a link would put the hash of
223
+ * something outside the repository into a committed manifest, and a read that
224
+ * throws here would abort the install after every other file was written.
225
+ */
226
+ async function readRegularFile(abs) {
227
+ try {
228
+ if (!(await lstat(abs)).isFile())
229
+ return null;
230
+ return await readFile(abs);
231
+ }
232
+ catch {
233
+ return null;
234
+ }
235
+ }
164
236
  /**
165
237
  * Record what was installed, so a later `upgrade` can tell a file it wrote
166
238
  * from a file the user owns.
167
239
  *
168
- * Only files actually **written** are recorded. A file `init` kept is
169
- * somebody else's — claiming it here would let the next upgrade replace a
170
- * user's own document with the rig's. Earlier entries are preserved: a re-run
171
- * writes nothing and must not therefore un-remember everything.
240
+ * Only files actually **written** are recorded in `files`. A file `init` kept
241
+ * is somebody else's — claiming it there would let the next upgrade replace a
242
+ * user's own document with the rig's. It is recorded in `kept` instead, with
243
+ * the sha256 of the bytes found on disk (RP-182): not a claim of ownership, a
244
+ * record that the rig saw the file and left it — so the next upgrade can say
245
+ * "kept by init, unchanged since" or "edited since" rather than only "not a
246
+ * version this rig ever released". A path already in `files` is never moved to
247
+ * `kept` by a later run that skips it: the rig wrote those bytes. Earlier
248
+ * entries are preserved: a re-run writes nothing and must not therefore
249
+ * un-remember everything.
172
250
  *
173
251
  * 🔴 **`kind`, `project` and `stacks` are preserved, not rewritten.** Reached
174
252
  * inside a rig `create` produced, this used to stamp `kind: 'init'`,
@@ -196,21 +274,40 @@ export async function initProject(repoDir, options) {
196
274
  * leaves is a `create` rig whose `CLAUDE.md` was deleted, and this function is
197
275
  * what makes that case safe.
198
276
  */
199
- async function recordInstall(repoDir, written, contents) {
277
+ async function recordInstall(repoDir, written, skipped, contents, layers, project) {
200
278
  const previous = await readManifest(repoDir);
201
279
  const name = projectNameFor(repoDir);
202
280
  const files = { ...(previous?.files ?? {}) };
203
281
  for (const rel of written)
204
282
  files[rel] = sha256(contents.get(rel) ?? '');
283
+ const kept = { ...(previous?.kept ?? {}) };
284
+ for (const rel of written)
285
+ delete kept[rel];
286
+ for (const rel of skipped) {
287
+ if (files[rel] !== undefined)
288
+ continue; // the rig wrote it once; still its bytes to vouch for
289
+ const found = await readRegularFile(path.join(repoDir, rel));
290
+ if (found === null)
291
+ delete kept[rel];
292
+ else
293
+ kept[rel] = sha256(found);
294
+ }
205
295
  const manifest = {
206
296
  version: await packageVersion(),
207
297
  kind: previous?.kind ?? 'init',
208
- // No manifest: fall back to the directory name, which is all this module
209
- // reads. See the limit above `upgrade` can do better from the files
298
+ // No manifest: prefer the caller's already-validated identity (`create`,
299
+ // RP-177) and only fall back to slugging the directory name when there is
300
+ // none. See the limit above — `upgrade` can do better from the files
210
301
  // themselves, and `init` deliberately does not reach for it.
211
- project: previous?.project ?? { name, scope: name, region: '' },
302
+ project: previous?.project ?? project ?? { name, scope: name, region: '' },
212
303
  stacks: previous?.stacks ?? [],
304
+ // `layers` is what THIS run resolved (already unioned with whatever the
305
+ // previous manifest recorded — RP-180's `effectiveLayers`), not
306
+ // re-derived here: a second computation of the same union is exactly the
307
+ // "second copy that goes stale" `invariants.md` warns about.
308
+ layers: [...layers],
213
309
  files,
310
+ ...(Object.keys(kept).length > 0 ? { kept } : {}),
214
311
  };
215
312
  await writeManifest(repoDir, manifest);
216
313
  }