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.
- package/CHANGELOG.md +466 -3
- package/README.md +267 -299
- package/package.json +8 -14
- package/packages/cli/dist/commands/create.js +56 -88
- package/packages/cli/dist/commands/doctor.js +213 -0
- package/packages/cli/dist/commands/init.js +151 -54
- package/packages/cli/dist/commands/integrations.js +468 -0
- package/packages/cli/dist/commands/memory.js +63 -4
- package/packages/cli/dist/commands/setup-wizard.js +61 -0
- package/packages/cli/dist/commands/uninstall.js +1223 -0
- package/packages/cli/dist/commands/upgrade.js +489 -89
- package/packages/cli/dist/index.js +657 -49
- package/packages/cli/dist/integrations/declaration.js +158 -0
- package/packages/cli/dist/integrations/doctor-guards.js +126 -0
- package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
- package/packages/cli/dist/integrations/mcp-json.js +107 -0
- package/packages/cli/dist/integrations/memory-doctor.js +124 -0
- package/packages/cli/dist/integrations/registry.js +22 -0
- package/packages/cli/dist/integrations/spawn.js +228 -0
- package/packages/cli/dist/integrations/spec-kit.js +280 -0
- package/packages/cli/dist/integrations/verify.js +193 -0
- package/packages/cli/dist/integrations/windows-job.js +275 -0
- package/packages/cli/dist/lib/elevated-paths.js +74 -0
- package/packages/cli/dist/lib/install-set.js +1 -44
- package/packages/cli/dist/lib/manifest.js +86 -9
- package/packages/cli/dist/lib/prompts.js +0 -23
- package/packages/cli/dist/lib/safe-path.js +193 -2
- package/packages/cli/dist/lib/safe-text.js +78 -0
- package/packages/cli/dist/lib/substitute.js +1 -52
- package/packages/cli/dist/lib/summary.js +5 -5
- package/packages/cli/dist/templates.js +4 -14
- package/scripts/prepare.mjs +28 -12
- package/templates/agent-os/subagent-routing.json +4 -4
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
- package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
- package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
- package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
- package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
- package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
- package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
- package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +43 -0
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +18 -1
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +273 -35
- package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
- package/templates/agent-os/universal/.claude/settings.json +0 -8
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
- package/templates/agent-os/universal/.codex/hooks.json +0 -10
- package/templates/agent-os/universal/AGENTS.md +208 -107
- package/templates/agent-os/universal/CLAUDE.md +15 -162
- package/templates/agent-os/universal/PLAN.md +17 -10
- package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
- package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
- package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
- package/templates/agent-os/universal/layers.json +25 -30
- package/templates/hash-history.json +73 -24
- package/templates/release-ledger.json +3 -1
- package/packages/cli/dist/lib/composition.js +0 -20
- package/packages/cli/dist/lib/targets.js +0 -28
- package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
- package/packages/cli/dist/policy/core/adapter.js +0 -18
- package/packages/cli/dist/policy/core/coverage.js +0 -253
- package/packages/cli/dist/policy/core/decision-record.js +0 -287
- package/packages/cli/dist/policy/core/declaration.js +0 -127
- package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
- package/packages/cli/dist/policy/core/probe.js +0 -442
- package/packages/cli/dist/policy/core/registry.js +0 -115
- package/packages/cli/dist/policy/core/validation.js +0 -275
- package/packages/cli/dist/policy/core/vocabulary.js +0 -123
- package/packages/cli/dist/policy/harness/claude.js +0 -47
- package/packages/cli/dist/policy/harness/codex.js +0 -87
- package/packages/cli/dist/policy/harness/index.js +0 -15
- package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
- package/packages/cli/dist/policy/index.js +0 -17
- package/templates/agent-os/init/AGENTS.md +0 -201
- package/templates/agent-os/init/CLAUDE.md +0 -201
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
- package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
- package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
- package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
- package/templates/skeleton/aws-serverless/README.md +0 -181
- package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
- package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
- package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
- package/templates/skeleton/aws-serverless/gitignore +0 -56
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
- package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
- package/templates/skeleton/aws-serverless/infra/package.json +0 -18
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
- package/templates/skeleton/aws-serverless/package.json +0 -30
- package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
- package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
- package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
- package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
- package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
- package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
- package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
- package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
- package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
- package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
- package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
- package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
- package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
- package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
- package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
- package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
- package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
- package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
- package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
- package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
- package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
- package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
- package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
- package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
- package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
- package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
- package/templates/skeleton/node-service/README.md +0 -104
- package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/node-service/apps/web/package.json +0 -19
- package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
- package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/node-service/eslint.config.mjs +0 -20
- package/templates/skeleton/node-service/gitignore +0 -54
- package/templates/skeleton/node-service/package.json +0 -30
- package/templates/skeleton/node-service/packages/core/package.json +0 -11
- package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
- package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
- package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
- package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/node-service/packages/db/package.json +0 -12
- package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
- package/templates/skeleton/node-service/packages/shared/package.json +0 -11
- package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
- package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
- package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
- package/templates/skeleton/node-service/services/api/package.json +0 -17
- package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
- package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
- package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
- package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
- package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
- package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
- package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
- package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
- package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
- package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
- package/templates/skeleton/node-service/services/worker/package.json +0 -16
- package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
- package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
- package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
- package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
- package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
- package/templates/skeleton/node-service/tsconfig.base.json +0 -15
- package/templates/skeleton/node-service/tsconfig.json +0 -13
- 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 {
|
|
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
|
|
45
|
-
*
|
|
46
|
-
* `
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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` 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.
|
|
51
87
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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
|
|
61
|
-
const files =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
sha256(await readFile(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
169
|
-
* somebody else's — claiming it
|
|
170
|
-
* user's own document with the rig's.
|
|
171
|
-
*
|
|
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:
|
|
209
|
-
//
|
|
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
|
}
|