create-agent-rig 0.9.1 → 0.10.1
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 +462 -3
- package/README.md +267 -305
- 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 +111 -50
- package/packages/cli/dist/commands/integrations.js +468 -0
- 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 +508 -101
- package/packages/cli/dist/index.js +658 -47
- 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 +64 -5
- 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 +45 -12
- 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/gate-stop-dod.mjs +4 -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 +59 -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/state.mjs +2 -2
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +4 -3
- 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 +45 -12
- 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 +112 -40
- 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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { access, open, stat } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DECLARATION_REL, MAX_DECLARATION_BYTES, parseDeclaration } from './declaration.js';
|
|
6
|
+
import { readMcpConfig } from './mcp-json.js';
|
|
7
|
+
import { REGISTRY } from './registry.js';
|
|
8
|
+
import { resolveReadableInside } from '../lib/safe-path.js';
|
|
9
|
+
const MCP_REL = '.mcp.json';
|
|
10
|
+
const CODEX_REL = '.codex/config.toml';
|
|
11
|
+
const MAX_WIRING_BYTES = 64 * 1024;
|
|
12
|
+
function sha256(value) {
|
|
13
|
+
const input = Buffer.isBuffer(value) ? value : JSON.stringify(value);
|
|
14
|
+
return createHash('sha256').update(input).digest('hex');
|
|
15
|
+
}
|
|
16
|
+
/** Read only regular, in-repository files and cap every allocation before I/O. */
|
|
17
|
+
export async function readBounded(repoDir, rel, max) {
|
|
18
|
+
const resolved = await resolveReadableInside(repoDir, rel, 'file');
|
|
19
|
+
if (resolved.status === 'absent')
|
|
20
|
+
return { status: 'absent' };
|
|
21
|
+
if (resolved.status !== 'ok')
|
|
22
|
+
return { status: 'unsafe' };
|
|
23
|
+
const flags = constants.O_RDONLY |
|
|
24
|
+
(process.platform === 'win32' ? 0 : constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
25
|
+
let handle;
|
|
26
|
+
try {
|
|
27
|
+
handle = await open(resolved.path, flags);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { status: 'unsafe' };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const info = await handle.stat();
|
|
34
|
+
if (!info.isFile())
|
|
35
|
+
return { status: 'unsafe' };
|
|
36
|
+
if (info.size > max)
|
|
37
|
+
return { status: 'too-large' };
|
|
38
|
+
const bytes = Buffer.alloc(max + 1);
|
|
39
|
+
let offset = 0;
|
|
40
|
+
while (offset < bytes.length) {
|
|
41
|
+
const { bytesRead } = await handle.read(bytes, offset, bytes.length - offset, null);
|
|
42
|
+
if (bytesRead === 0)
|
|
43
|
+
break;
|
|
44
|
+
offset += bytesRead;
|
|
45
|
+
}
|
|
46
|
+
if (offset > max)
|
|
47
|
+
return { status: 'too-large' };
|
|
48
|
+
return { status: 'ok', bytes: bytes.subarray(0, offset) };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { status: 'unsafe' };
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await handle.close().catch(() => undefined);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function decode(bytes) {
|
|
58
|
+
try {
|
|
59
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function defaultLocateLauncher(name, env = process.env) {
|
|
66
|
+
const variable = env.PATH ?? env.Path;
|
|
67
|
+
if (variable === undefined)
|
|
68
|
+
return null;
|
|
69
|
+
for (const directory of variable.split(path.delimiter)) {
|
|
70
|
+
if (!path.isAbsolute(directory))
|
|
71
|
+
continue;
|
|
72
|
+
const candidate = path.join(directory, process.platform === 'win32' ? `${name}.exe` : name);
|
|
73
|
+
try {
|
|
74
|
+
// Follow a machine-installed launcher symlink, but never execute it.
|
|
75
|
+
if (!(await stat(candidate)).isFile())
|
|
76
|
+
continue;
|
|
77
|
+
await access(candidate, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// A PATH member not present on this machine is not a diagnostic failure.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function verification(wiring, launcher) {
|
|
87
|
+
return {
|
|
88
|
+
wiring,
|
|
89
|
+
launcher,
|
|
90
|
+
runtime: 'unverified',
|
|
91
|
+
connectivity: 'not-observed',
|
|
92
|
+
trust: 'not-observed',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function mcpServerName(id) {
|
|
96
|
+
if (id === 'figma-mcp')
|
|
97
|
+
return 'figma';
|
|
98
|
+
if (id === 'atlassian-mcp')
|
|
99
|
+
return 'atlassian';
|
|
100
|
+
if (id === 'basic-memory')
|
|
101
|
+
return 'basic-memory';
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Pure doctor surface for Rig-owned wiring. It deliberately neither starts a
|
|
106
|
+
* launcher nor connects to a provider, so launcher presence is only observed
|
|
107
|
+
* and provider runtime/connectivity/trust remain unverified.
|
|
108
|
+
*/
|
|
109
|
+
export async function verifyIntegrations(options) {
|
|
110
|
+
const declaration = await readBounded(options.repoDir, DECLARATION_REL, MAX_DECLARATION_BYTES);
|
|
111
|
+
if (declaration.status === 'absent')
|
|
112
|
+
return { integrations: [], issues: [{ scope: 'declaration', status: 'missing' }] };
|
|
113
|
+
if (declaration.status !== 'ok')
|
|
114
|
+
return { integrations: [], issues: [{ scope: 'declaration', status: 'invalid' }] };
|
|
115
|
+
const declarationText = decode(declaration.bytes);
|
|
116
|
+
if (declarationText === null)
|
|
117
|
+
return { integrations: [], issues: [{ scope: 'declaration', status: 'invalid' }] };
|
|
118
|
+
const parsed = parseDeclaration(declarationText, REGISTRY);
|
|
119
|
+
if (parsed.status === 'invalid')
|
|
120
|
+
return { integrations: [], issues: [{ scope: 'declaration', status: 'invalid' }] };
|
|
121
|
+
const issues = parsed.rejected.map(({ reason }) => ({
|
|
122
|
+
scope: 'integration',
|
|
123
|
+
status: 'rejected',
|
|
124
|
+
reason,
|
|
125
|
+
}));
|
|
126
|
+
const claudeFile = await readBounded(options.repoDir, MCP_REL, MAX_WIRING_BYTES);
|
|
127
|
+
let claudeServers;
|
|
128
|
+
if (claudeFile.status === 'ok') {
|
|
129
|
+
const text = decode(claudeFile.bytes);
|
|
130
|
+
if (text !== null) {
|
|
131
|
+
try {
|
|
132
|
+
claudeServers = readMcpConfig(text).mcpServers;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// The status below reports drift without echoing untrusted JSON.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const codexFile = await readBounded(options.repoDir, CODEX_REL, MAX_WIRING_BYTES);
|
|
140
|
+
const codexHash = codexFile.status === 'ok' ? sha256(codexFile.bytes) : undefined;
|
|
141
|
+
const locator = options.locateLauncher ?? ((name) => defaultLocateLauncher(name, options.env));
|
|
142
|
+
const integrations = [];
|
|
143
|
+
for (const entry of parsed.entries) {
|
|
144
|
+
const harnesses = {};
|
|
145
|
+
for (const harness of entry.harnesses ?? []) {
|
|
146
|
+
if (harness === 'claude-code') {
|
|
147
|
+
const entryHash = entry.targets?.['claude-code']?.entryHash;
|
|
148
|
+
const name = mcpServerName(entry.id);
|
|
149
|
+
let wiring;
|
|
150
|
+
if (entryHash === undefined)
|
|
151
|
+
wiring = 'unowned';
|
|
152
|
+
else if (claudeFile.status === 'absent')
|
|
153
|
+
wiring = 'missing';
|
|
154
|
+
else if (claudeServers === undefined)
|
|
155
|
+
wiring = 'unreadable';
|
|
156
|
+
else if (name === undefined)
|
|
157
|
+
wiring = 'unowned';
|
|
158
|
+
else if (!(name in claudeServers))
|
|
159
|
+
wiring = 'missing';
|
|
160
|
+
else
|
|
161
|
+
wiring = sha256(claudeServers[name]) === entryHash ? 'healthy' : 'drift';
|
|
162
|
+
let launcher = 'not-applicable';
|
|
163
|
+
if (entry.id === 'basic-memory' && wiring === 'healthy')
|
|
164
|
+
launcher = (await locator('uvx')) === null ? 'missing' : 'observed';
|
|
165
|
+
harnesses[harness] = verification(wiring, launcher);
|
|
166
|
+
if (wiring === 'unreadable')
|
|
167
|
+
harnesses[harness].reason =
|
|
168
|
+
claudeFile.status === 'ok' ? 'invalid-config' : 'unsafe-or-too-large';
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const expectedHash = parsed.targets?.codex?.fileHash;
|
|
172
|
+
const wiring = expectedHash === undefined
|
|
173
|
+
? 'unowned'
|
|
174
|
+
: codexFile.status === 'absent'
|
|
175
|
+
? 'missing'
|
|
176
|
+
: codexFile.status !== 'ok'
|
|
177
|
+
? 'unreadable'
|
|
178
|
+
: codexHash === expectedHash
|
|
179
|
+
? 'healthy'
|
|
180
|
+
: 'drift';
|
|
181
|
+
const launcher = entry.id === 'basic-memory' && wiring === 'healthy'
|
|
182
|
+
? (await locator('uvx')) === null
|
|
183
|
+
? 'missing'
|
|
184
|
+
: 'observed'
|
|
185
|
+
: 'not-applicable';
|
|
186
|
+
harnesses[harness] = verification(wiring, launcher);
|
|
187
|
+
if (wiring === 'unreadable')
|
|
188
|
+
harnesses[harness].reason = 'unsafe-or-too-large';
|
|
189
|
+
}
|
|
190
|
+
integrations.push({ id: entry.id, harnesses });
|
|
191
|
+
}
|
|
192
|
+
return { integrations, issues };
|
|
193
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Fixed Windows-only supervisor. The payload is constructed by a compiled
|
|
2
|
+
// adapter; repository declarations never reach this script as executable code.
|
|
3
|
+
export const WINDOWS_JOB_SUPERVISOR = String.raw `
|
|
4
|
+
$ErrorActionPreference = 'Stop'
|
|
5
|
+
$source = @'
|
|
6
|
+
using System;
|
|
7
|
+
using System.ComponentModel;
|
|
8
|
+
using System.Runtime.InteropServices;
|
|
9
|
+
using System.Text;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
|
|
12
|
+
public static class RigJob
|
|
13
|
+
{
|
|
14
|
+
const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
|
|
15
|
+
const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
|
|
16
|
+
const int JobObjectExtendedLimitInformation = 9;
|
|
17
|
+
const int JobObjectBasicAccountingInformation = 1;
|
|
18
|
+
const int JobObjectAssociateCompletionPortInformation = 7;
|
|
19
|
+
static readonly IntPtr PROC_THREAD_ATTRIBUTE_JOB_LIST = (IntPtr)0x0002000D;
|
|
20
|
+
const uint INFINITE = 0xffffffff;
|
|
21
|
+
const uint JOB_OBJECT_MSG_NEW_PROCESS = 6;
|
|
22
|
+
const uint JOB_OBJECT_MSG_EXIT_PROCESS = 7;
|
|
23
|
+
const uint JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8;
|
|
24
|
+
|
|
25
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
26
|
+
struct STARTUPINFO
|
|
27
|
+
{
|
|
28
|
+
public int cb;
|
|
29
|
+
public string lpReserved, lpDesktop, lpTitle;
|
|
30
|
+
public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
|
|
31
|
+
public short wShowWindow, cbReserved2;
|
|
32
|
+
public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
36
|
+
struct STARTUPINFOEX
|
|
37
|
+
{
|
|
38
|
+
public STARTUPINFO StartupInfo;
|
|
39
|
+
public IntPtr lpAttributeList;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
43
|
+
struct PROCESS_INFORMATION
|
|
44
|
+
{
|
|
45
|
+
public IntPtr hProcess, hThread;
|
|
46
|
+
public int dwProcessId, dwThreadId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
50
|
+
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
51
|
+
{
|
|
52
|
+
public long PerProcessUserTimeLimit, PerJobUserTimeLimit;
|
|
53
|
+
public uint LimitFlags;
|
|
54
|
+
public UIntPtr MinimumWorkingSetSize, MaximumWorkingSetSize;
|
|
55
|
+
public uint ActiveProcessLimit;
|
|
56
|
+
public IntPtr Affinity;
|
|
57
|
+
public uint PriorityClass, SchedulingClass;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
61
|
+
struct IO_COUNTERS
|
|
62
|
+
{
|
|
63
|
+
public ulong ReadOperationCount, WriteOperationCount, OtherOperationCount;
|
|
64
|
+
public ulong ReadTransferCount, WriteTransferCount, OtherTransferCount;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
68
|
+
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
69
|
+
{
|
|
70
|
+
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
71
|
+
public IO_COUNTERS IoInfo;
|
|
72
|
+
public UIntPtr ProcessMemoryLimit, JobMemoryLimit, PeakProcessMemoryUsed, PeakJobMemoryUsed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
76
|
+
struct JOBOBJECT_BASIC_ACCOUNTING_INFORMATION
|
|
77
|
+
{
|
|
78
|
+
public long TotalUserTime, TotalKernelTime, ThisPeriodTotalUserTime, ThisPeriodTotalKernelTime;
|
|
79
|
+
public uint TotalPageFaultCount, TotalProcesses, ActiveProcesses, TotalTerminatedProcesses;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
83
|
+
struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT
|
|
84
|
+
{
|
|
85
|
+
public IntPtr CompletionKey;
|
|
86
|
+
public IntPtr CompletionPort;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
90
|
+
static extern IntPtr CreateJobObject(IntPtr attributes, string name);
|
|
91
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
92
|
+
static extern bool SetInformationJobObject(IntPtr job, int infoClass, IntPtr info, int length);
|
|
93
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
94
|
+
static extern bool QueryInformationJobObject(IntPtr job, int infoClass,
|
|
95
|
+
out JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info, int length, IntPtr returnLength);
|
|
96
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
97
|
+
static extern IntPtr CreateIoCompletionPort(IntPtr fileHandle, IntPtr existingPort,
|
|
98
|
+
IntPtr completionKey, uint threads);
|
|
99
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
100
|
+
static extern bool GetQueuedCompletionStatus(IntPtr port, out uint bytes,
|
|
101
|
+
out IntPtr completionKey, out IntPtr overlapped, uint milliseconds);
|
|
102
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
103
|
+
static extern bool InitializeProcThreadAttributeList(IntPtr list, int count, int flags, ref IntPtr size);
|
|
104
|
+
[DllImport("kernel32.dll")]
|
|
105
|
+
static extern void DeleteProcThreadAttributeList(IntPtr list);
|
|
106
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
107
|
+
static extern bool UpdateProcThreadAttribute(IntPtr list, uint flags, IntPtr attribute,
|
|
108
|
+
IntPtr value, IntPtr size, IntPtr previous, IntPtr returnSize);
|
|
109
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
110
|
+
static extern bool CreateProcess(string application, StringBuilder commandLine,
|
|
111
|
+
IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint flags,
|
|
112
|
+
IntPtr environment, string currentDirectory, ref STARTUPINFOEX startupInfo,
|
|
113
|
+
out PROCESS_INFORMATION processInformation);
|
|
114
|
+
[DllImport("kernel32.dll")]
|
|
115
|
+
static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
116
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
117
|
+
static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);
|
|
118
|
+
[DllImport("kernel32.dll")]
|
|
119
|
+
static extern bool CloseHandle(IntPtr handle);
|
|
120
|
+
|
|
121
|
+
static void Check(bool value)
|
|
122
|
+
{
|
|
123
|
+
if (!value) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public static string Quote(string value)
|
|
127
|
+
{
|
|
128
|
+
var result = new StringBuilder("\"");
|
|
129
|
+
int slashes = 0;
|
|
130
|
+
foreach (char character in value)
|
|
131
|
+
{
|
|
132
|
+
if (character == '\\') { slashes++; continue; }
|
|
133
|
+
result.Append('\\', character == '"' ? slashes * 2 + 1 : slashes);
|
|
134
|
+
result.Append(character);
|
|
135
|
+
slashes = 0;
|
|
136
|
+
}
|
|
137
|
+
result.Append('\\', slashes * 2);
|
|
138
|
+
result.Append('"');
|
|
139
|
+
return result.ToString();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static IntPtr CreateJob(IntPtr port)
|
|
143
|
+
{
|
|
144
|
+
IntPtr job = CreateJobObject(IntPtr.Zero, null);
|
|
145
|
+
if (job == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
146
|
+
var limits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
|
147
|
+
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
148
|
+
int size = Marshal.SizeOf(limits);
|
|
149
|
+
IntPtr buffer = Marshal.AllocHGlobal(size);
|
|
150
|
+
try
|
|
151
|
+
{
|
|
152
|
+
Marshal.StructureToPtr(limits, buffer, false);
|
|
153
|
+
Check(SetInformationJobObject(job, JobObjectExtendedLimitInformation, buffer, size));
|
|
154
|
+
var association = new JOBOBJECT_ASSOCIATE_COMPLETION_PORT();
|
|
155
|
+
association.CompletionKey = (IntPtr)1;
|
|
156
|
+
association.CompletionPort = port;
|
|
157
|
+
int associationSize = Marshal.SizeOf(association);
|
|
158
|
+
IntPtr associationBuffer = Marshal.AllocHGlobal(associationSize);
|
|
159
|
+
try
|
|
160
|
+
{
|
|
161
|
+
Marshal.StructureToPtr(association, associationBuffer, false);
|
|
162
|
+
Check(SetInformationJobObject(job, JobObjectAssociateCompletionPortInformation,
|
|
163
|
+
associationBuffer, associationSize));
|
|
164
|
+
}
|
|
165
|
+
finally { Marshal.FreeHGlobal(associationBuffer); }
|
|
166
|
+
return job;
|
|
167
|
+
}
|
|
168
|
+
catch
|
|
169
|
+
{
|
|
170
|
+
CloseHandle(job);
|
|
171
|
+
throw;
|
|
172
|
+
}
|
|
173
|
+
finally { Marshal.FreeHGlobal(buffer); }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static int DrainEvents(IntPtr port, int members)
|
|
177
|
+
{
|
|
178
|
+
uint message;
|
|
179
|
+
IntPtr key, process;
|
|
180
|
+
while (GetQueuedCompletionStatus(port, out message, out key, out process, 0))
|
|
181
|
+
{
|
|
182
|
+
if (message == JOB_OBJECT_MSG_NEW_PROCESS) members++;
|
|
183
|
+
if (message == JOB_OBJECT_MSG_EXIT_PROCESS ||
|
|
184
|
+
message == JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS) members--;
|
|
185
|
+
}
|
|
186
|
+
return members;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The JOB_LIST attribute is attached to CreateProcess itself, so there is
|
|
190
|
+
// no created-but-unassigned provider interval for a helper kill to expose.
|
|
191
|
+
static PROCESS_INFORMATION CreateInJob(IntPtr job, string executable, string command, string cwd)
|
|
192
|
+
{
|
|
193
|
+
IntPtr attributeList = IntPtr.Zero;
|
|
194
|
+
IntPtr jobList = IntPtr.Zero;
|
|
195
|
+
bool initialized = false;
|
|
196
|
+
try
|
|
197
|
+
{
|
|
198
|
+
IntPtr attributeBytes = IntPtr.Zero;
|
|
199
|
+
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref attributeBytes);
|
|
200
|
+
if (attributeBytes == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
201
|
+
attributeList = Marshal.AllocHGlobal(attributeBytes);
|
|
202
|
+
Check(InitializeProcThreadAttributeList(attributeList, 1, 0, ref attributeBytes));
|
|
203
|
+
initialized = true;
|
|
204
|
+
jobList = Marshal.AllocHGlobal(IntPtr.Size);
|
|
205
|
+
Marshal.WriteIntPtr(jobList, job);
|
|
206
|
+
Check(UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST,
|
|
207
|
+
jobList, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero));
|
|
208
|
+
var startup = new STARTUPINFOEX();
|
|
209
|
+
startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
|
|
210
|
+
startup.lpAttributeList = attributeList;
|
|
211
|
+
PROCESS_INFORMATION process;
|
|
212
|
+
Check(CreateProcess(executable, new StringBuilder(command), IntPtr.Zero, IntPtr.Zero,
|
|
213
|
+
true, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, cwd, ref startup, out process));
|
|
214
|
+
return process;
|
|
215
|
+
}
|
|
216
|
+
finally
|
|
217
|
+
{
|
|
218
|
+
if (initialized) DeleteProcThreadAttributeList(attributeList);
|
|
219
|
+
if (attributeList != IntPtr.Zero) Marshal.FreeHGlobal(attributeList);
|
|
220
|
+
if (jobList != IntPtr.Zero) Marshal.FreeHGlobal(jobList);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public static int Run(string executable, string command, string cwd)
|
|
225
|
+
{
|
|
226
|
+
IntPtr port = CreateIoCompletionPort((IntPtr)(-1), IntPtr.Zero, IntPtr.Zero, 1);
|
|
227
|
+
if (port == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
228
|
+
IntPtr job = IntPtr.Zero;
|
|
229
|
+
int members = 0;
|
|
230
|
+
try
|
|
231
|
+
{
|
|
232
|
+
job = CreateJob(port);
|
|
233
|
+
PROCESS_INFORMATION process = CreateInJob(job, executable, command, cwd);
|
|
234
|
+
try
|
|
235
|
+
{
|
|
236
|
+
WaitForSingleObject(process.hProcess, INFINITE);
|
|
237
|
+
members = DrainEvents(port, members);
|
|
238
|
+
uint exitCode;
|
|
239
|
+
Check(GetExitCodeProcess(process.hProcess, out exitCode));
|
|
240
|
+
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting;
|
|
241
|
+
do
|
|
242
|
+
{
|
|
243
|
+
Check(QueryInformationJobObject(job, JobObjectBasicAccountingInformation,
|
|
244
|
+
out accounting, Marshal.SizeOf(typeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION)),
|
|
245
|
+
IntPtr.Zero));
|
|
246
|
+
if (accounting.ActiveProcesses != 0) Thread.Sleep(10);
|
|
247
|
+
} while (accounting.ActiveProcesses != 0);
|
|
248
|
+
// Give job notifications already queued by a just-exited root
|
|
249
|
+
// one bounded scheduling turn before accepting active-zero.
|
|
250
|
+
Thread.Sleep(20);
|
|
251
|
+
members = DrainEvents(port, members);
|
|
252
|
+
return members == 0 ? unchecked((int)exitCode) : 125;
|
|
253
|
+
}
|
|
254
|
+
finally
|
|
255
|
+
{
|
|
256
|
+
CloseHandle(process.hThread);
|
|
257
|
+
CloseHandle(process.hProcess);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
finally
|
|
261
|
+
{
|
|
262
|
+
if (job != IntPtr.Zero) CloseHandle(job);
|
|
263
|
+
CloseHandle(port);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
'@
|
|
268
|
+
Add-Type -TypeDefinition $source -Language CSharp
|
|
269
|
+
$payload = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:RIG_WINDOWS_JOB_PAYLOAD)) | ConvertFrom-Json
|
|
270
|
+
$exe = [string]$payload.executable
|
|
271
|
+
$cwd = [string]$payload.cwd
|
|
272
|
+
$parts = @($payload.args | ForEach-Object { [RigJob]::Quote([string]$_) })
|
|
273
|
+
$command = ([RigJob]::Quote($exe)) + (($parts | ForEach-Object { ' ' + $_ }) -join '')
|
|
274
|
+
exit [RigJob]::Run($exe, $command, $cwd)
|
|
275
|
+
`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirrors `parseElevatedPaths` in
|
|
3
|
+
* `templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs` —
|
|
4
|
+
* the gate sweep's own parser for the fenced ```elevated-paths block a
|
|
5
|
+
* rulebook file declares. Kept in step with it by a correspondence test
|
|
6
|
+
* (`packages/cli/test/elevated-paths-correspondence.test.ts`) that feeds the
|
|
7
|
+
* same literal fixtures to both and fails the moment they disagree, rather
|
|
8
|
+
* than by a shared import — the `.mjs` is the gate sweep's own copy, shipped
|
|
9
|
+
* into every generated rig, and this file is the generator's own, used only
|
|
10
|
+
* to DECIDE whether `upgrade` can trust an on-disk AGENTS.md as a rulebook
|
|
11
|
+
* (round 5, design ruling replacing round 4's verdict-only rule). Two
|
|
12
|
+
* different consumers, one semantics, proven equal rather than assumed so.
|
|
13
|
+
*
|
|
14
|
+
* Bounded, one forward pass: a hostile or merely enormous AGENTS.md cannot
|
|
15
|
+
* make this cost more than a fixed amount of work. The cap is applied BEFORE
|
|
16
|
+
* the regex ever runs, so the single `matchAll` pass is always over a string
|
|
17
|
+
* no longer than {@link ELEVATED_PATHS_SCAN_CAP}, never proportional to an
|
|
18
|
+
* attacker-chosen input size.
|
|
19
|
+
*/
|
|
20
|
+
export const ELEVATED_PATHS_SCAN_CAP = 1_000_000;
|
|
21
|
+
/**
|
|
22
|
+
* Normalise a path so both sides of a comparison agree — identical to the
|
|
23
|
+
* `.mjs` sweep's own `normalizePath`, restated here because the correspondence
|
|
24
|
+
* test proves the two produce the same answer rather than sharing an import.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeElevatedPath(path) {
|
|
27
|
+
return String(path ?? '')
|
|
28
|
+
.replace(/\\/g, '/')
|
|
29
|
+
.replace(/\/{2,}/g, '/')
|
|
30
|
+
.replace(/^\.\//, '')
|
|
31
|
+
.replace(/^\//, '');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Every declared path across every ```elevated-paths block in `markdown`, or
|
|
35
|
+
* `null` when there is no such block at all. An empty array (block present,
|
|
36
|
+
* nothing left after stripping comments and blank lines) is a real, distinct
|
|
37
|
+
* answer from `null` — round 5's own trigger: a hostile or stale
|
|
38
|
+
* `elevated-paths` block with only a comment ("Everything is Tier 0") parses
|
|
39
|
+
* to `[]`, and {@link isReadableRulebook} treats that exactly like no block
|
|
40
|
+
* at all, never like a real declaration.
|
|
41
|
+
*
|
|
42
|
+
* State limit, tested rather than merely claimed: content beyond
|
|
43
|
+
* {@link ELEVATED_PATHS_SCAN_CAP} characters is never read at all — a block
|
|
44
|
+
* that starts after the cap, or a path line that straddles it, is invisible
|
|
45
|
+
* to this function. `elevated-paths-correspondence.test.ts` pins this pair:
|
|
46
|
+
* a fixture within the cap agrees with the `.mjs` sweep (which has no cap),
|
|
47
|
+
* and the cap's own behaviour on an over-limit fixture is tested against
|
|
48
|
+
* this function alone, never against the `.mjs` (which would legitimately
|
|
49
|
+
* disagree there — the cap is this file's own limit, not a shared one).
|
|
50
|
+
*/
|
|
51
|
+
export function parseElevatedPaths(markdown) {
|
|
52
|
+
const text = String(markdown ?? '');
|
|
53
|
+
const bounded = text.length > ELEVATED_PATHS_SCAN_CAP ? text.slice(0, ELEVATED_PATHS_SCAN_CAP) : text;
|
|
54
|
+
const blocks = [...bounded.matchAll(/```elevated-paths\r?\n([\s\S]*?)```/g)];
|
|
55
|
+
if (blocks.length === 0)
|
|
56
|
+
return null;
|
|
57
|
+
return blocks.flatMap((block) => (block[1] ?? '')
|
|
58
|
+
.split('\n')
|
|
59
|
+
// An inline comment after the path is a comment, not part of the path.
|
|
60
|
+
.map((line) => normalizeElevatedPath(line.replace(/\s+#.*$/, '').trim()))
|
|
61
|
+
.filter((line) => line.length > 0 && !line.startsWith('#')));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Round 5's content-based hold-back rule (replacing round 4's verdict-only
|
|
65
|
+
* rule): whether `content` can serve as the rulebook AGENTS.md is meant to
|
|
66
|
+
* be — at least one non-empty, non-comment path declared in a fenced
|
|
67
|
+
* ```elevated-paths block. Absence of the FILE is decided by the caller
|
|
68
|
+
* (`upgrade.ts`'s `deleted` verdict); this only ever judges bytes that are
|
|
69
|
+
* actually present.
|
|
70
|
+
*/
|
|
71
|
+
export function isReadableRulebook(content) {
|
|
72
|
+
const parsed = parseElevatedPaths(content);
|
|
73
|
+
return parsed !== null && parsed.length > 0;
|
|
74
|
+
}
|
|
@@ -1,44 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { listTreeEntries, mapConcurrent } from './copy-tree.js';
|
|
3
|
-
import { substituteContent, substituteFileName } from './substitute.js';
|
|
4
|
-
import { agentOsStackDir, agentOsUniversalDir } from '../templates.js';
|
|
5
|
-
/**
|
|
6
|
-
* The agent-os layers of a target, in composition order. One definition, read
|
|
7
|
-
* by `create` when it copies them and by `upgrade` when it refreshes them —
|
|
8
|
-
* two lists would drift, and the one nobody looks at would be the wrong one.
|
|
9
|
-
*/
|
|
10
|
-
export function agentOsLayerDirs(stacks) {
|
|
11
|
-
return [
|
|
12
|
-
{ name: 'agent-os/universal', dir: agentOsUniversalDir() },
|
|
13
|
-
...stacks.map((stack) => ({ name: `agent-os/stack/${stack}`, dir: agentOsStackDir(stack) })),
|
|
14
|
-
];
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Exactly the agent-os bytes `create` writes, for a given target and project.
|
|
18
|
-
*
|
|
19
|
-
* The skeleton is deliberately absent: after generation the code is the user's
|
|
20
|
-
* project, not the rig's — `upgrade` refreshes the process layer and nothing
|
|
21
|
-
* else. Layers claim disjoint paths (`composition.ts` refuses otherwise), so
|
|
22
|
-
* this is a plain concatenation.
|
|
23
|
-
*
|
|
24
|
-
* 🔴 Two assumptions, both load-bearing and both true today: the layer is all
|
|
25
|
-
* **text** (this reads every file as UTF-8; a template test pins it for the
|
|
26
|
-
* process half) and none of it is **executable** (`upgrade` writes with a bare
|
|
27
|
-
* `writeFile` and does not carry the mode across the way `copyTree` does —
|
|
28
|
-
* nothing in `templates/agent-os` currently has a mode to carry). Adding the
|
|
29
|
-
* first binary asset or executable script here means teaching both.
|
|
30
|
-
*/
|
|
31
|
-
export async function agentOsInstallSet(stacks, ctx) {
|
|
32
|
-
const files = [];
|
|
33
|
-
for (const layer of agentOsLayerDirs(stacks)) {
|
|
34
|
-
const entries = await listTreeEntries(layer.dir, {
|
|
35
|
-
transformName: (name) => substituteFileName(name, ctx),
|
|
36
|
-
});
|
|
37
|
-
files.push(...(await mapConcurrent(entries, 16, async (entry) => ({
|
|
38
|
-
rel: entry.rel,
|
|
39
|
-
source: entry.source,
|
|
40
|
-
content: substituteContent(await readFile(entry.source, 'utf8'), ctx),
|
|
41
|
-
}))));
|
|
42
|
-
}
|
|
43
|
-
return files;
|
|
44
|
-
}
|
|
1
|
+
export {};
|