create-agent-rig 0.9.1 → 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 (234) hide show
  1. package/CHANGELOG.md +419 -3
  2. package/README.md +267 -305
  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 +111 -50
  7. package/packages/cli/dist/commands/integrations.js +468 -0
  8. package/packages/cli/dist/commands/setup-wizard.js +61 -0
  9. package/packages/cli/dist/commands/uninstall.js +1223 -0
  10. package/packages/cli/dist/commands/upgrade.js +472 -90
  11. package/packages/cli/dist/index.js +651 -46
  12. package/packages/cli/dist/integrations/declaration.js +158 -0
  13. package/packages/cli/dist/integrations/doctor-guards.js +126 -0
  14. package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
  15. package/packages/cli/dist/integrations/mcp-json.js +107 -0
  16. package/packages/cli/dist/integrations/memory-doctor.js +124 -0
  17. package/packages/cli/dist/integrations/registry.js +22 -0
  18. package/packages/cli/dist/integrations/spawn.js +228 -0
  19. package/packages/cli/dist/integrations/spec-kit.js +280 -0
  20. package/packages/cli/dist/integrations/verify.js +193 -0
  21. package/packages/cli/dist/integrations/windows-job.js +275 -0
  22. package/packages/cli/dist/lib/elevated-paths.js +74 -0
  23. package/packages/cli/dist/lib/install-set.js +1 -44
  24. package/packages/cli/dist/lib/manifest.js +64 -5
  25. package/packages/cli/dist/lib/prompts.js +0 -23
  26. package/packages/cli/dist/lib/safe-path.js +193 -2
  27. package/packages/cli/dist/lib/safe-text.js +78 -0
  28. package/packages/cli/dist/lib/substitute.js +1 -52
  29. package/packages/cli/dist/lib/summary.js +5 -5
  30. package/packages/cli/dist/templates.js +4 -14
  31. package/scripts/prepare.mjs +28 -12
  32. package/templates/agent-os/subagent-routing.json +4 -4
  33. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
  34. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
  35. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
  36. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
  37. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
  38. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
  39. package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
  40. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
  41. package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
  42. package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
  43. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
  44. package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
  45. package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
  46. package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
  47. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
  48. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
  49. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
  50. package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
  51. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
  52. package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +4 -3
  53. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
  54. package/templates/agent-os/universal/.claude/settings.json +0 -8
  55. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
  56. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
  57. package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
  58. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
  59. package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
  60. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
  61. package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
  62. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
  63. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
  64. package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
  65. package/templates/agent-os/universal/.codex/hooks.json +0 -10
  66. package/templates/agent-os/universal/AGENTS.md +208 -107
  67. package/templates/agent-os/universal/CLAUDE.md +15 -162
  68. package/templates/agent-os/universal/PLAN.md +17 -10
  69. package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
  70. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
  71. package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
  72. package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
  73. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
  74. package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
  75. package/templates/agent-os/universal/layers.json +25 -30
  76. package/templates/hash-history.json +8 -4
  77. package/templates/release-ledger.json +2 -1
  78. package/packages/cli/dist/lib/composition.js +0 -20
  79. package/packages/cli/dist/lib/targets.js +0 -28
  80. package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
  81. package/packages/cli/dist/policy/core/adapter.js +0 -18
  82. package/packages/cli/dist/policy/core/coverage.js +0 -253
  83. package/packages/cli/dist/policy/core/decision-record.js +0 -287
  84. package/packages/cli/dist/policy/core/declaration.js +0 -127
  85. package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
  86. package/packages/cli/dist/policy/core/probe.js +0 -442
  87. package/packages/cli/dist/policy/core/registry.js +0 -115
  88. package/packages/cli/dist/policy/core/validation.js +0 -275
  89. package/packages/cli/dist/policy/core/vocabulary.js +0 -123
  90. package/packages/cli/dist/policy/harness/claude.js +0 -47
  91. package/packages/cli/dist/policy/harness/codex.js +0 -87
  92. package/packages/cli/dist/policy/harness/index.js +0 -15
  93. package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
  94. package/packages/cli/dist/policy/index.js +0 -17
  95. package/templates/agent-os/init/AGENTS.md +0 -201
  96. package/templates/agent-os/init/CLAUDE.md +0 -201
  97. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
  98. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
  99. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
  100. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
  101. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
  102. package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
  103. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
  104. package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
  105. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
  106. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
  107. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
  108. package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
  109. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
  110. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
  111. package/templates/skeleton/aws-serverless/README.md +0 -181
  112. package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
  113. package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
  114. package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
  115. package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
  116. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
  117. package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
  118. package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
  119. package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
  120. package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
  121. package/templates/skeleton/aws-serverless/gitignore +0 -56
  122. package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
  123. package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
  124. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
  125. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
  126. package/templates/skeleton/aws-serverless/infra/package.json +0 -18
  127. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
  128. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
  129. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
  130. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
  131. package/templates/skeleton/aws-serverless/package.json +0 -30
  132. package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
  133. package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
  134. package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
  135. package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
  136. package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
  137. package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
  138. package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
  139. package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
  140. package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
  141. package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
  142. package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
  143. package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
  144. package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
  145. package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
  146. package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
  147. package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
  148. package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
  149. package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
  150. package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
  151. package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
  152. package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
  153. package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
  154. package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
  155. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
  156. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
  157. package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
  158. package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
  159. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
  160. package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
  161. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
  162. package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
  163. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
  164. package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
  165. package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
  166. package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
  167. package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
  168. package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
  169. package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
  170. package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
  171. package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
  172. package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
  173. package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
  174. package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
  175. package/templates/skeleton/node-service/README.md +0 -104
  176. package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
  177. package/templates/skeleton/node-service/apps/web/package.json +0 -19
  178. package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
  179. package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
  180. package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
  181. package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
  182. package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
  183. package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
  184. package/templates/skeleton/node-service/eslint.config.mjs +0 -20
  185. package/templates/skeleton/node-service/gitignore +0 -54
  186. package/templates/skeleton/node-service/package.json +0 -30
  187. package/templates/skeleton/node-service/packages/core/package.json +0 -11
  188. package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
  189. package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
  190. package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
  191. package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
  192. package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
  193. package/templates/skeleton/node-service/packages/db/package.json +0 -12
  194. package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
  195. package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
  196. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
  197. package/templates/skeleton/node-service/packages/shared/package.json +0 -11
  198. package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
  199. package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
  200. package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
  201. package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
  202. package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
  203. package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
  204. package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
  205. package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
  206. package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
  207. package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
  208. package/templates/skeleton/node-service/services/api/package.json +0 -17
  209. package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
  210. package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
  211. package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
  212. package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
  213. package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
  214. package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
  215. package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
  216. package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
  217. package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
  218. package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
  219. package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
  220. package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
  221. package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
  222. package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
  223. package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
  224. package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
  225. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
  226. package/templates/skeleton/node-service/services/worker/package.json +0 -16
  227. package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
  228. package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
  229. package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
  230. package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
  231. package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
  232. package/templates/skeleton/node-service/tsconfig.base.json +0 -15
  233. package/templates/skeleton/node-service/tsconfig.json +0 -13
  234. package/templates/skeleton/node-service/vitest.config.ts +0 -12
@@ -2,22 +2,59 @@ 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.
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` ships — the 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.
48
87
  *
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:
51
- *
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,13 +202,18 @@ 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, skipped, contents);
214
+ if (!options.dryRun) {
215
+ await recordInstall(repoDir, written, skipped, contents, layers, options.project);
216
+ }
162
217
  return { written, skipped, plannedCount };
163
218
  }
164
219
  /**
@@ -172,7 +227,7 @@ async function readRegularFile(abs) {
172
227
  try {
173
228
  if (!(await lstat(abs)).isFile())
174
229
  return null;
175
- return await readFile(abs, 'utf8');
230
+ return await readFile(abs);
176
231
  }
177
232
  catch {
178
233
  return null;
@@ -219,7 +274,7 @@ async function readRegularFile(abs) {
219
274
  * leaves is a `create` rig whose `CLAUDE.md` was deleted, and this function is
220
275
  * what makes that case safe.
221
276
  */
222
- async function recordInstall(repoDir, written, skipped, contents) {
277
+ async function recordInstall(repoDir, written, skipped, contents, layers, project) {
223
278
  const previous = await readManifest(repoDir);
224
279
  const name = projectNameFor(repoDir);
225
280
  const files = { ...(previous?.files ?? {}) };
@@ -240,11 +295,17 @@ async function recordInstall(repoDir, written, skipped, contents) {
240
295
  const manifest = {
241
296
  version: await packageVersion(),
242
297
  kind: previous?.kind ?? 'init',
243
- // No manifest: fall back to the directory name, which is all this module
244
- // 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
245
301
  // themselves, and `init` deliberately does not reach for it.
246
- project: previous?.project ?? { name, scope: name, region: '' },
302
+ project: previous?.project ?? project ?? { name, scope: name, region: '' },
247
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],
248
309
  files,
249
310
  ...(Object.keys(kept).length > 0 ? { kept } : {}),
250
311
  };