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
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The small set of checks the declaration and the decision record share.
|
|
3
|
-
*
|
|
4
|
-
* Every check appends to a problem list instead of throwing, so a caller sees
|
|
5
|
-
* every defect of a record at once — `validateDeclaration` › "reports every
|
|
6
|
-
* problem at once rather than stopping at the first" in
|
|
7
|
-
* `packages/cli/test/policy-declaration.test.ts`. Each message that refuses an
|
|
8
|
-
* enumerated value quotes the value, because a refusal that names the field
|
|
9
|
-
* and not the word leaves the caller guessing which of two spellings it sent.
|
|
10
|
-
*/
|
|
11
|
-
export const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
-
/**
|
|
13
|
-
* Whether an outside record CARRIES a field — own and enumerable, which is
|
|
14
|
-
* exactly the set `Object.keys` walks, and which `JSON.stringify` writes out
|
|
15
|
-
* whenever the value is serialisable.
|
|
16
|
-
*
|
|
17
|
-
* ⚠ The second half is a "whenever", not an equivalence, and the earlier
|
|
18
|
-
* wording claimed the equivalence. `{ downgradeReason: undefined }` is own and
|
|
19
|
-
* enumerable, so this returns `true`, while `JSON.stringify` drops it — which
|
|
20
|
-
* is why a `SUPPORTED` row written that way is refused for carrying a reason
|
|
21
|
-
* even though its serialisation carries none. The refusal is the conservative
|
|
22
|
-
* direction, but a reader who took "the rule is what `JSON.stringify` sees"
|
|
23
|
-
* literally would predict acceptance. `Object.keys` is the set this actually
|
|
24
|
-
* implements; that is the half to reason from.
|
|
25
|
-
*
|
|
26
|
-
* 🔴 The two halves are one rule, and each was a real false pass. A field found
|
|
27
|
-
* on the PROTOTYPE let a hook serialising as `{}` be read as running the
|
|
28
|
-
* generated command, and let an evidence row own nothing and still validate; a
|
|
29
|
-
* field that is own but NOT ENUMERABLE let a row validate and then serialise
|
|
30
|
-
* without the pointer that made it pass. Both are the same defect stated twice:
|
|
31
|
-
* something was accepted as evidence that no serialisation of the value
|
|
32
|
-
* carries. `unknownKeys` already judges a record by `Object.keys`, so reading
|
|
33
|
-
* by any wider notion made the closed-shape check and the field reads disagree
|
|
34
|
-
* about what the record even contains — and the reads were the wider of the
|
|
35
|
-
* two.
|
|
36
|
-
*
|
|
37
|
-
* Held over both readers at once: `packages/cli/test/policy-coverage.test.ts`
|
|
38
|
-
* (absent in a generated rig) › "refuses a hook entry whose command is only
|
|
39
|
-
* inherited, because the entry itself carries no command" and › "refuses a row
|
|
40
|
-
* whose evidence pointer is own but not enumerable, because the rule is what
|
|
41
|
-
* JSON.stringify sees".
|
|
42
|
-
*/
|
|
43
|
-
export const carriesField = (input, field) => Object.prototype.propertyIsEnumerable.call(input, field);
|
|
44
|
-
/**
|
|
45
|
-
* Read one field the way the record's own serialisation would carry it, or
|
|
46
|
-
* `undefined` when the record does not carry it at all.
|
|
47
|
-
*
|
|
48
|
-
* Presence and value travel through the same predicate on purpose: a caller
|
|
49
|
-
* that tested presence one way and read the value another is how the two
|
|
50
|
-
* came apart the first time.
|
|
51
|
-
*/
|
|
52
|
-
export const ownField = (input, field) => carriesField(input, field) ? input[field] : undefined;
|
|
53
|
-
/**
|
|
54
|
-
* A value as it appeared, escaped, for a message a person reads. Exported
|
|
55
|
-
* because every module here that puts OUTSIDE data into a diagnostic must put
|
|
56
|
-
* it through the same escaping — a raw newline or ANSI sequence in a matcher
|
|
57
|
-
* can otherwise forge a line of the report it lands in (`./probe.ts`).
|
|
58
|
-
*/
|
|
59
|
-
export const quote = (value) => {
|
|
60
|
-
try {
|
|
61
|
-
return JSON.stringify(value) ?? String(value);
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return String(value);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const list = (vocabulary) => vocabulary.map(quote).join(', ');
|
|
68
|
-
/**
|
|
69
|
-
* Refuse a key the shape does not declare — the shape is closed on purpose.
|
|
70
|
-
* A nested shape passes its own field name as `prefix`, so the problem names
|
|
71
|
-
* `verdict.severity` rather than a bare `severity` the caller cannot place.
|
|
72
|
-
*/
|
|
73
|
-
export const unknownKeys = (problems, input, known, prefix = '') => {
|
|
74
|
-
for (const key of Object.keys(input)) {
|
|
75
|
-
if (!known.includes(key)) {
|
|
76
|
-
problems.push({ field: prefix === '' ? key : `${prefix}.${key}`, message: 'unknown field' });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
/** Refuse a string that is absent, not a string, or empty. */
|
|
81
|
-
export const nonEmptyString = (problems, field, value) => {
|
|
82
|
-
if (typeof value !== 'string' || value === '') {
|
|
83
|
-
problems.push({ field, message: `must be a non-empty string, got ${quote(value)}` });
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return true;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Refuse a string that is absent, not a string, or has no non-space character.
|
|
90
|
-
*
|
|
91
|
-
* Stricter than `nonEmptyString` in exactly one place — a value of whitespace
|
|
92
|
-
* only — and a separate helper rather than a tightening of that one, because
|
|
93
|
-
* the shapes already validated by it are not in this change's scope. Where a
|
|
94
|
-
* field is a fact a later reader has to act on (an exact version, a pointer to
|
|
95
|
-
* evidence), a blank is the same defect as an absence and is refused as one.
|
|
96
|
-
*/
|
|
97
|
-
export const nonBlankString = (problems, field, value) => {
|
|
98
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
99
|
-
problems.push({ field, message: `must be a non-blank string, got ${quote(value)}` });
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
return true;
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* A real calendar date, `T`, time to the second (fractions allowed), and an
|
|
106
|
-
* explicit zone.
|
|
107
|
-
*
|
|
108
|
-
* One spelling of one fact (`rules/invariants.md`, "One mechanism, one
|
|
109
|
-
* implementation"). Its three readers are `./decision-record.ts`
|
|
110
|
-
* (`recordedAt`), `./evidence-matrix.ts` (`observedAt`) and `./coverage.ts`
|
|
111
|
-
* (`verifiedAt`, through `requireTimestamp`), so a bare date is refused the
|
|
112
|
-
* same way whichever of them is validating — including lexically shaped but
|
|
113
|
-
* impossible dates — `packages/cli/test/policy-coverage.test.ts`
|
|
114
|
-
* › "refuses the probe timestamp %j, which is exactly what the shared ISO-8601
|
|
115
|
-
* pattern refuses" imports this pattern rather than restating it, so the two
|
|
116
|
-
* sides cannot drift apart. A timestamp is always supplied by the caller —
|
|
117
|
-
* nothing under this directory reads a clock.
|
|
118
|
-
*/
|
|
119
|
-
const ISO_8601_SHAPE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/;
|
|
120
|
-
/**
|
|
121
|
-
* A date-time shape whose `test` also proves the named calendar instant exists.
|
|
122
|
-
* Keeping the semantic check behind the same exported predicate prevents the
|
|
123
|
-
* coverage, decision-record and evidence-row validators from drifting apart.
|
|
124
|
-
*/
|
|
125
|
-
export const ISO_8601 = {
|
|
126
|
-
test(value) {
|
|
127
|
-
const match = ISO_8601_SHAPE.exec(value);
|
|
128
|
-
if (match === null)
|
|
129
|
-
return false;
|
|
130
|
-
const [, yearText, monthText, dayText, hourText, minuteText, secondText, zoneHour, zoneMinute] = match;
|
|
131
|
-
const year = Number(yearText);
|
|
132
|
-
const month = Number(monthText);
|
|
133
|
-
const day = Number(dayText);
|
|
134
|
-
const hour = Number(hourText);
|
|
135
|
-
const minute = Number(minuteText);
|
|
136
|
-
const second = Number(secondText);
|
|
137
|
-
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
138
|
-
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
139
|
-
return (month >= 1 &&
|
|
140
|
-
month <= 12 &&
|
|
141
|
-
day >= 1 &&
|
|
142
|
-
day <= daysInMonth[month - 1] &&
|
|
143
|
-
hour <= 23 &&
|
|
144
|
-
minute <= 59 &&
|
|
145
|
-
second <= 59 &&
|
|
146
|
-
(zoneHour === undefined || (Number(zoneHour) <= 23 && Number(zoneMinute) <= 59)));
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
/** Refuse a string outside a closed vocabulary, quoting the offending value. */
|
|
150
|
-
export const member = (problems, field, value, vocabulary) => {
|
|
151
|
-
if (typeof value === 'string' && vocabulary.includes(value))
|
|
152
|
-
return true;
|
|
153
|
-
problems.push({ field, message: `${quote(value)} is not one of ${list(vocabulary)}` });
|
|
154
|
-
return false;
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* Refuse a list that is not an array, carries a value outside the vocabulary,
|
|
158
|
-
* repeats one, or — when `nonEmpty` — is empty.
|
|
159
|
-
*/
|
|
160
|
-
export const members = (problems, field, value, vocabulary, { nonEmpty }) => {
|
|
161
|
-
if (!Array.isArray(value)) {
|
|
162
|
-
problems.push({ field, message: `must be a list, got ${quote(value)}` });
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
let clean = true;
|
|
166
|
-
if (nonEmpty && value.length === 0) {
|
|
167
|
-
problems.push({ field, message: 'must not be empty' });
|
|
168
|
-
clean = false;
|
|
169
|
-
}
|
|
170
|
-
const seen = new Set();
|
|
171
|
-
for (const entry of value) {
|
|
172
|
-
if (!member(problems, field, entry, vocabulary)) {
|
|
173
|
-
clean = false;
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
if (seen.has(entry)) {
|
|
177
|
-
problems.push({ field, message: `${quote(entry)} is listed twice` });
|
|
178
|
-
clean = false;
|
|
179
|
-
}
|
|
180
|
-
seen.add(entry);
|
|
181
|
-
}
|
|
182
|
-
return clean;
|
|
183
|
-
};
|
|
184
|
-
/** Refuse a string that does not match the pattern, saying what shape was expected. */
|
|
185
|
-
export const matching = (problems, field, value, pattern, expected) => {
|
|
186
|
-
if (typeof value === 'string' && pattern.test(value))
|
|
187
|
-
return true;
|
|
188
|
-
problems.push({ field, message: `must be ${expected}, got ${quote(value)}` });
|
|
189
|
-
return false;
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* A version that names one build rather than a set of them.
|
|
193
|
-
*
|
|
194
|
-
* One spelling of one fact: `./evidence-matrix.ts` refuses a matrix row on it
|
|
195
|
-
* and `./coverage.ts` refuses a surface identity on it, so "the exact version
|
|
196
|
-
* observed" means the same thing wherever it is written.
|
|
197
|
-
*
|
|
198
|
-
* 🔴 This is an ALLOWLIST, and it replaced a denylist that could not be
|
|
199
|
-
* finished. The denylist refused five vague words, the range operator
|
|
200
|
-
* characters, a wildcard component and two npm range spellings — and accepted
|
|
201
|
-
* `main`, `master`, `stable`, `next`, `nightly`, `dev`, `edge`, `canary` and
|
|
202
|
-
* `1.2.3 or 2.0.0`, because none of them is any of those things. A moving label
|
|
203
|
-
* is not a shape you can enumerate: every branch name a harness ever publishes
|
|
204
|
-
* from is a new entry, added by whoever notices, which is nobody. So the check
|
|
205
|
-
* asks what a build identifier LOOKS LIKE instead of what a moving target is
|
|
206
|
-
* called, and a word the grammar does not describe is refused whether or not
|
|
207
|
-
* anyone anticipated it.
|
|
208
|
-
*
|
|
209
|
-
* Two shapes are accepted, and they are the two this rig actually reads:
|
|
210
|
-
*
|
|
211
|
-
* - a build NUMBER — a dotted numeric version, optionally `v`-prefixed, with an
|
|
212
|
-
* optional pre-release or build-metadata suffix after `-` or `+`. That covers
|
|
213
|
-
* `2.0.14`, `v2.0.14`, `1.104.2`, a date build id like `2026-09-05`, a plain
|
|
214
|
-
* build id like `20260904.3`, and a suffix carrying any letter at all,
|
|
215
|
-
* including `1.0.0-X` and `0.0.0-fixture`;
|
|
216
|
-
* - a build ID — 7 to 64 hex characters, which is a git object id at every
|
|
217
|
-
* length git itself abbreviates to.
|
|
218
|
-
*
|
|
219
|
-
* The distinction that costs the most to get wrong is the one between a bare
|
|
220
|
-
* channel word and a suffix: `beta` names whatever is on that channel today and
|
|
221
|
-
* is refused, while `1.0.0-beta.2` names one build and is accepted. The grammar
|
|
222
|
-
* draws that line by requiring the number first — a suffix cannot stand alone.
|
|
223
|
-
*
|
|
224
|
-
* The value is matched AS GIVEN, with no trim. An earlier version validated
|
|
225
|
-
* `value.trim()` while both callers stored the value verbatim, so `" 2.0.14 "`,
|
|
226
|
-
* `"2.0.14\r\n"` and a BOM-prefixed form were accepted and then persisted with
|
|
227
|
-
* their padding: two rows for one build that compare unequal, and a version
|
|
228
|
-
* carrying a newline sitting in a field a report will one day render. What is
|
|
229
|
-
* checked and what is stored are now the same string: ›
|
|
230
|
-
* "refuses an evidence row whose harness version carries %s, because the row
|
|
231
|
-
* would store what it was not validated on" and › "refuses to probe against a
|
|
232
|
-
* harness version carrying %s, so two maps of one build cannot compare
|
|
233
|
-
* unequal", with › "still accepts the same build once %s is gone, because it is
|
|
234
|
-
* the padding that is refused and not the version" holding the other side.
|
|
235
|
-
*
|
|
236
|
-
* Refused, and now by construction rather than by enumeration: the vague words,
|
|
237
|
-
* every moving branch label, range OPERATORS, wildcard components, both npm
|
|
238
|
-
* range spellings, and any text carrying whitespace or a comma — which is what
|
|
239
|
-
* `1.2.3 or 2.0.0` and `1.2.3, 2.0.0` are. Both readers are pinned in
|
|
240
|
-
* `packages/cli/test/policy-coverage.test.ts` (absent in a generated rig) ›
|
|
241
|
-
* "refuses the harness version %j, because it names a moving label or more than
|
|
242
|
-
* one build" and › "refuses to probe against the harness version %j, because it
|
|
243
|
-
* names a moving label or more than one build", with the other direction held
|
|
244
|
-
* so the grammar cannot swallow a real build id: › "still accepts the harness
|
|
245
|
-
* version %j, because it names one build" and › "still probes against the
|
|
246
|
-
* harness version %j, because it names one build".
|
|
247
|
-
*/
|
|
248
|
-
const BUILD_NUMBER = /^v?\d+(?:\.\d+)*(?:[-+][0-9A-Za-z][0-9A-Za-z.+-]*)?$/;
|
|
249
|
-
const BUILD_ID = /^[0-9a-fA-F]{7,64}$/;
|
|
250
|
-
/**
|
|
251
|
-
* What a refusal says is expected — one spelling, read by this module and by
|
|
252
|
-
* `./coverage.ts`, so the two cannot come to describe different grammars.
|
|
253
|
-
*/
|
|
254
|
-
export const EXACT_VERSION_EXPECTED = 'must name one immutable build: a version number like 2.0.14, ' +
|
|
255
|
-
'optionally v-prefixed and optionally carrying a -pre-release or +build suffix, ' +
|
|
256
|
-
'or a 7-to-64-character hex build id';
|
|
257
|
-
export const isExactVersion = (value) => BUILD_NUMBER.test(value) || BUILD_ID.test(value);
|
|
258
|
-
/**
|
|
259
|
-
* Refuse a version the grammar does not describe, quoting the value and naming
|
|
260
|
-
* the two shapes that are accepted.
|
|
261
|
-
*
|
|
262
|
-
* The message says what would be accepted rather than what was wrong, because
|
|
263
|
-
* the check is an allowlist: it also refuses `1.0.0.beta` and `2026_09_05`,
|
|
264
|
-
* which are neither a range nor a moving target, and the earlier message told
|
|
265
|
-
* their author they had written one. `./probe.ts` states the principle this
|
|
266
|
-
* trips over — a refusal naming a cause that did not occur sends an operator
|
|
267
|
-
* looking for something that is not there.
|
|
268
|
-
*/
|
|
269
|
-
export const exactVersion = (problems, field, value) => {
|
|
270
|
-
if (typeof value !== 'string' || value.trim() === '')
|
|
271
|
-
return;
|
|
272
|
-
if (!isExactVersion(value)) {
|
|
273
|
-
problems.push({ field, message: `${EXACT_VERSION_EXPECTED}; got ${quote(value)}` });
|
|
274
|
-
}
|
|
275
|
-
};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The closed vocabularies of the policy declaration (RP-76).
|
|
3
|
-
*
|
|
4
|
-
* Every value a declaration or a decision record may carry in an enumerated
|
|
5
|
-
* field is listed here and nowhere else. Adding a value is a schema edit — a
|
|
6
|
-
* change to this file plus the test that pins the list — never a string a
|
|
7
|
-
* caller invents at runtime. That is what makes a record auditable: an unknown
|
|
8
|
-
* word is refused rather than read as something close to a known one.
|
|
9
|
-
*
|
|
10
|
-
* Harness-neutral by construction: nothing here names a harness, a vendor, a
|
|
11
|
-
* native tool or a native path. The per-harness spellings live in
|
|
12
|
-
* `../harness/`, and `test/template/policy-declaration.test.ts` › "no file
|
|
13
|
-
* under src/policy/core mentions a harness, a vendor, a native tool or a native
|
|
14
|
-
* path" is what keeps them out of here.
|
|
15
|
-
*/
|
|
16
|
-
const closed = (values) => Object.freeze(values);
|
|
17
|
-
/**
|
|
18
|
-
* Whether a declared policy can actually be enforced on a given harness
|
|
19
|
-
* surface. `UNSUPPORTED` and `INTEGRATION-FAILED` must never yield a silent
|
|
20
|
-
* pass: a decision record carrying either has to qualify its verdict
|
|
21
|
-
* `UNVERIFIABLE` (`./decision-record.ts`). The four states are defined here.
|
|
22
|
-
*
|
|
23
|
-
* ⚠ That is the rule, and the enforcement behind it is narrower than the rule
|
|
24
|
-
* — see `UNENFORCEABLE_STATES` below, which states the gap once for every
|
|
25
|
-
* reader of this file.
|
|
26
|
-
*/
|
|
27
|
-
export const CAPABILITY_STATES = closed([
|
|
28
|
-
'SUPPORTED',
|
|
29
|
-
'DEGRADED',
|
|
30
|
-
'UNSUPPORTED',
|
|
31
|
-
'INTEGRATION-FAILED',
|
|
32
|
-
]);
|
|
33
|
-
/**
|
|
34
|
-
* The states under which no question was actually put to a working mechanism.
|
|
35
|
-
*
|
|
36
|
-
* One spelling of one fact (`rules/invariants.md`, "One mechanism, one
|
|
37
|
-
* implementation"): `./decision-record.ts` refuses an unqualified verdict
|
|
38
|
-
* carrying one of these, and `./coverage.ts` › `qualifierFor` returns
|
|
39
|
-
* `UNVERIFIABLE` for exactly the same set. Two copies would disagree, and the
|
|
40
|
-
* one nobody is looking at would be the one that let a silent pass through.
|
|
41
|
-
*
|
|
42
|
-
* ⚠ **"Refuses" now holds against a hand-built prototype too** — this note used
|
|
43
|
-
* to say the opposite, and this is the file a reader auditing that question
|
|
44
|
-
* lands on first, so it is corrected here rather than only where the fix landed.
|
|
45
|
-
* `./decision-record.ts` used to decide whether a verdict carries a qualifier
|
|
46
|
-
* with the `in` operator, so a verdict INHERITING one satisfied the check and
|
|
47
|
-
* then serialised without it. RP-153 closed that: every field of both
|
|
48
|
-
* `./decision-record.ts` and `./declaration.ts` is read through
|
|
49
|
-
* `carriesField`/`ownField`, the way `./probe.ts` and `./evidence-matrix.ts`
|
|
50
|
-
* already read theirs, and both uncarried shapes are pinned in
|
|
51
|
-
* `packages/cli/test/policy-declaration.test.ts` › "refuses an UNSUPPORTED
|
|
52
|
-
* record whose verdict qualifier is %s, because what it writes out is a silent
|
|
53
|
-
* pass" — `%s` as the `it.each` case declares it, so one grep lands on it.
|
|
54
|
-
*
|
|
55
|
-
* What remains is narrower and is NOT this sentence's subject: a value carrying
|
|
56
|
-
* a live accessor is validated on one read and serialised from another (RP-157),
|
|
57
|
-
* and an array HOLE serialises as `null` while the two `forEach` loops in
|
|
58
|
-
* `./decision-record.ts` skip it (RP-161) — those two only, since `members` in
|
|
59
|
-
* `./validation.ts` and `./probe.ts` iterate with `for…of`, which sees a hole as
|
|
60
|
-
* `undefined` and refuses it. `docs/decisions/capability-coverage.md`, "What
|
|
61
|
-
* this does NOT do", carries the current limits at length.
|
|
62
|
-
*
|
|
63
|
-
* It is deliberately NOT derived from a rank or an ordering. `coverage.ts`
|
|
64
|
-
* carries an enforcement ordering for deciding what counts as a downgrade;
|
|
65
|
-
* keying verdict qualification off that would mean a future re-rank silently
|
|
66
|
-
* changed which verdicts are unverifiable.
|
|
67
|
-
*/
|
|
68
|
-
export const UNENFORCEABLE_STATES = closed(['UNSUPPORTED', 'INTEGRATION-FAILED']);
|
|
69
|
-
/**
|
|
70
|
-
* The two ways a capability status is established, and the only two.
|
|
71
|
-
*
|
|
72
|
-
* `probe` is one active read of the surface's own wiring, taken when the
|
|
73
|
-
* surface changes (`PROBE_TRIGGERS`). `traffic` is passive: an operation that
|
|
74
|
-
* was expected to produce an observable signal, and what it actually produced.
|
|
75
|
-
* There is deliberately no third source meaning "time passed" — silence is
|
|
76
|
-
* absence of evidence, not evidence of absence.
|
|
77
|
-
*/
|
|
78
|
-
export const VERIFICATION_SOURCES = closed(['probe', 'traffic']);
|
|
79
|
-
/**
|
|
80
|
-
* The occasions on which a surface is probed. Every member is an event on the
|
|
81
|
-
* surface; none of them is an interval.
|
|
82
|
-
*
|
|
83
|
-
* What this vocabulary does, exactly: `./coverage.ts` › `coverageFromProbe`
|
|
84
|
-
* takes a trigger as a required argument and refuses a word outside this list,
|
|
85
|
-
* so a probe must NAME its occasion and cannot name a schedule — ›
|
|
86
|
-
* "refuses the trigger %j, because the coverage contract accepts only a
|
|
87
|
-
* declared surface-change trigger".
|
|
88
|
-
*
|
|
89
|
-
* ⚠ What it does NOT do, stated because an earlier draft of this comment
|
|
90
|
-
* claimed it: nothing here stops a caller passing `'upgrade'` on a timer. The
|
|
91
|
-
* check refuses a LABEL outside the vocabulary, not the practice of probing
|
|
92
|
-
* periodically. `docs/decisions/capability-coverage.md` §1 says the same, and
|
|
93
|
-
* this file used to contradict it.
|
|
94
|
-
*/
|
|
95
|
-
export const PROBE_TRIGGERS = closed(['install', 'upgrade', 'registration', 'reconnect']);
|
|
96
|
-
/** The autonomy tiers of `rules/autonomy.md`; `never` is the tier the guards enforce. */
|
|
97
|
-
export const AUTONOMY_TIERS = closed(['tier-0', 'tier-1', 'tier-2', 'never']);
|
|
98
|
-
/** The operations a policy can apply to, named by what the agent does, not by a tool. */
|
|
99
|
-
export const OPERATIONS = closed(['file-edit', 'shell-command']);
|
|
100
|
-
/** When the mechanism decides, relative to the operation it judges. */
|
|
101
|
-
export const ENFORCEMENT_TIMINGS = closed(['before-operation']);
|
|
102
|
-
/** What a harness must provide for the mechanism to run at all. */
|
|
103
|
-
export const HARNESS_CAPABILITIES = closed(['pre-operation-hook']);
|
|
104
|
-
/**
|
|
105
|
-
* The three outcomes a guard can reach: allow, block, or refuse to inspect —
|
|
106
|
-
* the third being neither a match nor an error (`rules/invariants.md`,
|
|
107
|
-
* "Refusing to inspect is a third outcome").
|
|
108
|
-
*/
|
|
109
|
-
export const DECISION_OUTCOMES = closed(['allow', 'block', 'refuse-to-inspect']);
|
|
110
|
-
/** What a mechanism does when it cannot decide: let the operation through, or stop it. */
|
|
111
|
-
export const FAILURE_SEMANTICS = closed(['fail-open', 'fail-closed']);
|
|
112
|
-
/** The kinds of evidence a decision record may carry, and a policy may require. */
|
|
113
|
-
export const EVIDENCE_KINDS = closed(['exit-code', 'diagnostic-text', 'test-pointer']);
|
|
114
|
-
/** How a mechanism treats what it matched when it reports: verbatim, or omitted. */
|
|
115
|
-
export const REDACTION_RULES = closed(['none', 'omit-matched-values']);
|
|
116
|
-
/** Where a policy is in its life; a `retired` policy is no longer offered by the registry. */
|
|
117
|
-
export const LIFECYCLE_STATES = closed(['active', 'deprecated', 'retired']);
|
|
118
|
-
/**
|
|
119
|
-
* The two ways a verdict can say "this word is weaker than it looks": the
|
|
120
|
-
* question could not be put (`UNVERIFIABLE`), or nothing backs the answer
|
|
121
|
-
* (`UNMEASURED`). Either one must carry a reason.
|
|
122
|
-
*/
|
|
123
|
-
export const VERDICT_QUALIFIERS = closed(['UNVERIFIABLE', 'UNMEASURED']);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Claude Code adapter: a declaration → the native hook surface Claude Code
|
|
3
|
-
* wires for it. The authoring surface of this rulebook is Claude-shaped
|
|
4
|
-
* (`CLAUDE.md`, "One operating system, two harnesses"), so the hook files
|
|
5
|
-
* themselves live in the historical directory `./shared-hooks.ts` names and are
|
|
6
|
-
* shared by every harness.
|
|
7
|
-
*
|
|
8
|
-
* What is native here and nowhere in the core: the `PreToolUse` event, the
|
|
9
|
-
* tool names in the matchers, and the snapshot path. The matcher strings are
|
|
10
|
-
* the ones `.claude/settings.json` carries, and the correspondence test holds
|
|
11
|
-
* the two to the SAME tool set, not a subset: a tool dropped here or gained
|
|
12
|
-
* there is reported for this adapter —
|
|
13
|
-
* `test/template/policy-declaration.test.ts` › "reports the no-verify policy
|
|
14
|
-
* on %s when the shell matcher loses PowerShell (mutation: matcher)", › "reports
|
|
15
|
-
* the no-verify policy on %s when the snapshot gains a tool the adapter does
|
|
16
|
-
* not name (mutation: widened snapshot)" and › "reports a policy on %s whose
|
|
17
|
-
* adapter matcher drops a tool the snapshot still wires (mutation: narrowed
|
|
18
|
-
* adapter)". The shell matcher's tool set is owned by `shell-tools.mjs` in the
|
|
19
|
-
* shipped scripts, and `test/template/shell-tools.test.ts` holds that
|
|
20
|
-
* correspondence.
|
|
21
|
-
*/
|
|
22
|
-
import { SHARED_HOOK_ROOT_ENV, SHARED_HOOKS_DIR } from './shared-hooks.js';
|
|
23
|
-
const EVENT_OF = {
|
|
24
|
-
'before-operation': 'PreToolUse',
|
|
25
|
-
};
|
|
26
|
-
const MATCHER_OF = {
|
|
27
|
-
'file-edit': 'Write|Edit|MultiEdit|NotebookEdit|apply_patch',
|
|
28
|
-
'shell-command': 'Bash|PowerShell',
|
|
29
|
-
};
|
|
30
|
-
export const nativeSurfaceOf = (policy) => ({
|
|
31
|
-
event: EVENT_OF[policy.timing],
|
|
32
|
-
matcher: policy.operations.map((operation) => MATCHER_OF[operation]).join('|'),
|
|
33
|
-
hookPath: `${SHARED_HOOKS_DIR}/${policy.mechanism}.mjs`,
|
|
34
|
-
// The exact command this harness generates for a hook. The probe compares
|
|
35
|
-
// against this rather than parsing what it finds, so this string and the one
|
|
36
|
-
// in the shipped snapshot must agree — pinned in both directions by
|
|
37
|
-
// `test/template/policy-coverage.test.ts` (absent in a generated rig) ›
|
|
38
|
-
// "the %s snapshot wires %s with exactly the command that adapter generates".
|
|
39
|
-
commands: {
|
|
40
|
-
command: [`node "$${SHARED_HOOK_ROOT_ENV}/${SHARED_HOOKS_DIR}/${policy.mechanism}.mjs"`],
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
export const claudeAdapter = Object.freeze({
|
|
44
|
-
harness: 'claude',
|
|
45
|
-
surfaceFile: '.claude/settings.json',
|
|
46
|
-
nativeSurfaceOf,
|
|
47
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Codex adapter: a declaration → the native hook surface Codex wires for
|
|
3
|
-
* it. Codex's hook wiring (`.codex/hooks.json`) is DERIVED from the authoring
|
|
4
|
-
* harness's snapshot by `scripts/sync-codex-adapter.mjs`
|
|
5
|
-
* (`docs/decisions/codex-adapter.md`): it keeps the authoring harness's matcher
|
|
6
|
-
* spellings, adds the canonical edit tool `apply_patch`, and runs the same hook
|
|
7
|
-
* files from the shared hooks directory. So the strings below coincide with the
|
|
8
|
-
* other adapter's today — by derivation, not by accident — and each adapter
|
|
9
|
-
* still owns its own spelling. The correspondence test holds this adapter's
|
|
10
|
-
* matcher and the derived snapshot's to the SAME tool set, so a tool the
|
|
11
|
-
* snapshot gains or loses, or one this adapter drops, is reported for this
|
|
12
|
-
* adapter alone — `test/template/policy-declaration.test.ts` › "reports the
|
|
13
|
-
* no-verify policy on %s when the shell matcher loses PowerShell (mutation:
|
|
14
|
-
* matcher)", › "reports the no-verify policy on %s when the snapshot gains a
|
|
15
|
-
* tool the adapter does not name (mutation: widened snapshot)" and › "reports
|
|
16
|
-
* a policy on %s whose adapter matcher drops a tool the snapshot still wires
|
|
17
|
-
* (mutation: narrowed adapter)".
|
|
18
|
-
*
|
|
19
|
-
* The shared hooks directory is imported from `./shared-hooks.ts` rather than
|
|
20
|
-
* restated, for the same reason the snapshot is derived rather than
|
|
21
|
-
* hand-written: one spelling of one fact.
|
|
22
|
-
*/
|
|
23
|
-
import { SHARED_HOOK_ROOT_ENV, SHARED_HOOKS_DIR } from './shared-hooks.js';
|
|
24
|
-
const EVENT_OF = {
|
|
25
|
-
'before-operation': 'PreToolUse',
|
|
26
|
-
};
|
|
27
|
-
const MATCHER_OF = {
|
|
28
|
-
'file-edit': 'Write|Edit|MultiEdit|NotebookEdit|apply_patch',
|
|
29
|
-
'shell-command': 'Bash|PowerShell',
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* The Windows spelling this harness generates for a hook.
|
|
33
|
-
*
|
|
34
|
-
* ⚠ A SECOND implementation of the wrapper that `scripts/sync-codex-adapter.mjs`
|
|
35
|
-
* writes into the shipped file, and deliberately so: that script is a build tool
|
|
36
|
-
* outside the published package, and this module may not import it. The two are
|
|
37
|
-
* held equal by a correspondence check that goes red in BOTH directions —
|
|
38
|
-
* `test/template/policy-coverage.test.ts` (absent in a generated rig) › "the %s
|
|
39
|
-
* snapshot wires %s with exactly the spelling that adapter generates, in every
|
|
40
|
-
* field it generates one for" — which is what `rules/invariants.md` ("One
|
|
41
|
-
* mechanism, one implementation") requires of a copy that has to stay.
|
|
42
|
-
*
|
|
43
|
-
* The wrapper exists because PowerShell owns its stdin, so the hook would
|
|
44
|
-
* receive an empty stream; it copies the original bytes into the child instead
|
|
45
|
-
* of re-encoding them.
|
|
46
|
-
*/
|
|
47
|
-
const windowsCommand = (hookPath) => {
|
|
48
|
-
const script = [
|
|
49
|
-
"$ErrorActionPreference = 'Stop'",
|
|
50
|
-
'$repoRoot = git rev-parse --show-toplevel',
|
|
51
|
-
'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }',
|
|
52
|
-
`$env:${SHARED_HOOK_ROOT_ENV} = $repoRoot`,
|
|
53
|
-
`$hookPath = Join-Path $repoRoot '${hookPath}'`,
|
|
54
|
-
'$startInfo = New-Object System.Diagnostics.ProcessStartInfo',
|
|
55
|
-
"$startInfo.FileName = 'node'",
|
|
56
|
-
"$startInfo.Arguments = '\"' + $hookPath + '\"'",
|
|
57
|
-
'$startInfo.UseShellExecute = $false',
|
|
58
|
-
'$startInfo.RedirectStandardInput = $true',
|
|
59
|
-
'$child = [System.Diagnostics.Process]::Start($startInfo)',
|
|
60
|
-
'[Console]::OpenStandardInput().CopyTo($child.StandardInput.BaseStream)',
|
|
61
|
-
'$child.StandardInput.Close()',
|
|
62
|
-
'$child.WaitForExit()',
|
|
63
|
-
'exit $child.ExitCode',
|
|
64
|
-
].join('; ');
|
|
65
|
-
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
66
|
-
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
|
|
67
|
-
};
|
|
68
|
-
export const nativeSurfaceOf = (policy) => ({
|
|
69
|
-
event: EVENT_OF[policy.timing],
|
|
70
|
-
matcher: policy.operations.map((operation) => MATCHER_OF[operation]).join('|'),
|
|
71
|
-
hookPath: `${SHARED_HOOKS_DIR}/${policy.mechanism}.mjs`,
|
|
72
|
-
// Both commands this harness generates. It runs the first on POSIX and the
|
|
73
|
-
// second on Windows, so the probe has to know both: replacing only the
|
|
74
|
-
// Windows spelling in the shipped file used to leave every policy reading
|
|
75
|
-
// SUPPORTED while the guard no longer ran there.
|
|
76
|
-
commands: {
|
|
77
|
-
command: [
|
|
78
|
-
`repoRoot="$(git rev-parse --show-toplevel)" && ${SHARED_HOOK_ROOT_ENV}="$repoRoot" node "$repoRoot/${SHARED_HOOKS_DIR}/${policy.mechanism}.mjs"`,
|
|
79
|
-
],
|
|
80
|
-
commandWindows: [windowsCommand(`${SHARED_HOOKS_DIR}/${policy.mechanism}.mjs`)],
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
export const codexAdapter = Object.freeze({
|
|
84
|
-
harness: 'codex',
|
|
85
|
-
surfaceFile: '.codex/hooks.json',
|
|
86
|
-
nativeSurfaceOf,
|
|
87
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The harness adapters this generator knows. Adding a harness is one new
|
|
3
|
-
* module beside these two and one entry in the list below — nothing in
|
|
4
|
-
* `../core/` changes, which `test/template/policy-declaration.test.ts` ›
|
|
5
|
-
* "codex is named only by its own adapter and the adapter index" pins by
|
|
6
|
-
* naming exactly the files that may mention each harness.
|
|
7
|
-
*/
|
|
8
|
-
import { claudeAdapter } from './claude.js';
|
|
9
|
-
import { codexAdapter } from './codex.js';
|
|
10
|
-
export { claudeAdapter, codexAdapter };
|
|
11
|
-
export { SHARED_HOOKS_DIR } from './shared-hooks.js';
|
|
12
|
-
export const HARNESS_ADAPTERS = Object.freeze([
|
|
13
|
-
claudeAdapter,
|
|
14
|
-
codexAdapter,
|
|
15
|
-
]);
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one directory every harness runs its hook files from. The rulebook's
|
|
3
|
-
* `.claude/` directory keeps its historical name but holds the shared rules,
|
|
4
|
-
* hooks, scripts and agent specifications for both harnesses (`CLAUDE.md`,
|
|
5
|
-
* "One operating system, two harnesses"), so a hook path is the same string
|
|
6
|
-
* whichever adapter names it. Stated once, here, and imported by each adapter
|
|
7
|
-
* — one spelling of one fact (`rules/invariants.md`, "One mechanism, one
|
|
8
|
-
* implementation").
|
|
9
|
-
*/
|
|
10
|
-
export const SHARED_HOOKS_DIR = '.claude/hooks';
|
|
11
|
-
/**
|
|
12
|
-
* The environment variable a shared hook reads when it needs the repository
|
|
13
|
-
* root, and therefore the one each harness sets when it runs one.
|
|
14
|
-
*
|
|
15
|
-
* Not every hook needs it: of the eight this rig ships, `guard-rulebook` and
|
|
16
|
-
* `guard-secret-file` read it, both falling back to the working directory.
|
|
17
|
-
* The variable is still part of the wiring contract, because the harness sets
|
|
18
|
-
* it for whichever hook it runs.
|
|
19
|
-
*
|
|
20
|
-
* It carries a harness's name for the same historical reason `.claude/hooks`
|
|
21
|
-
* does — the hooks are shared, so both harnesses speak this one variable — and
|
|
22
|
-
* this module is the one adapter-side file allowed to spell that name for both
|
|
23
|
-
* (`test/template/policy-declaration.test.ts` › "claude is named only by its
|
|
24
|
-
* own adapter, the shared hooks directory and the adapter index"). Stating it
|
|
25
|
-
* here is what lets the other harness's adapter build its generated command
|
|
26
|
-
* without naming a harness that is not its own.
|
|
27
|
-
*/
|
|
28
|
-
export const SHARED_HOOK_ROOT_ENV = 'CLAUDE_PROJECT_DIR';
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The policy declaration, registry, decision-record schema and harness
|
|
3
|
-
* adapters (RP-76), plus the capability & degradation contract that says what
|
|
4
|
-
* each of them is worth on a given surface (RP-36).
|
|
5
|
-
*
|
|
6
|
-
* Library surface only — nothing here is reached by the CLI commands yet;
|
|
7
|
-
* emitting decision records at runtime, and rendering the coverage report in
|
|
8
|
-
* `doctor`, are separate tasks.
|
|
9
|
-
*/
|
|
10
|
-
export * from './core/vocabulary.js';
|
|
11
|
-
export * from './core/declaration.js';
|
|
12
|
-
export * from './core/registry.js';
|
|
13
|
-
export * from './core/decision-record.js';
|
|
14
|
-
export * from './core/probe.js';
|
|
15
|
-
export * from './core/coverage.js';
|
|
16
|
-
export * from './core/evidence-matrix.js';
|
|
17
|
-
export * from './harness/index.js';
|