@haposoft/cafekit 0.16.7 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +0 -36
- package/bin/__tests__/codex-native.test.js +46 -88
- package/bin/__tests__/develop-contract.test.js +26 -57
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +100 -541
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +0 -27
- package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +9 -28
- package/bin/phases/codex-runtime.js +24 -0
- package/bin/phases/omp-runtime.js +22 -0
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +35 -50
- package/src/claude/agents/debugger.md +5 -79
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +2 -7
- package/src/claude/agents/implementer.md +6 -9
- package/src/claude/agents/inspector.md +2 -3
- package/src/claude/agents/project-manager.md +7 -15
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +11 -11
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/context.cjs +0 -89
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -28
- package/src/claude/hooks/spec-state.cjs +95 -1
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +30 -15
- package/src/claude/rules/ai-dev-rules.md +1 -1
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +10 -18
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -7
- package/src/claude/scripts/spec-resolver.cjs +1 -2
- package/src/claude/scripts/validate-spec-output.cjs +5 -32
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/code-review/SKILL.md +48 -17
- package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
- package/src/claude/skills/code-review/references/verification-gate.md +22 -27
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +27 -36
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +2 -0
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +8 -15
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +22 -3
- package/src/claude/skills/test/references/execution-strategy.md +83 -0
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
- package/src/codex/hooks/lib/spec-utils.cjs +0 -7
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +7 -18
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +2 -2
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -39,7 +39,7 @@ function boundaryTableHasRequiredRows(table) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const IMPLEMENTATION_READINESS_CLAUSES = {
|
|
42
|
-
noInvention: 'Before implementation handoff, apply the **no-invention gate**: if two implementations conform to the packet text yet can produce different externally observable output, state, error, security, or compatibility behavior, surface the missing choice as an explicit
|
|
42
|
+
noInvention: 'Before implementation handoff, apply the **no-invention gate**: if two implementations conform to the packet text yet can produce different externally observable output, state, error, security, or compatibility behavior, surface the missing choice as an explicit GATE-SCOPE or GATE-REVIEW question and block handoff.',
|
|
43
43
|
materialDefinition: 'A boundary is material when the task creates, changes, or depends on it and a different choice changes an external observation, security, durable data, compatibility, or proof reachability. Require only the matching material row; omit nonmaterial categories.',
|
|
44
44
|
exactBoundaryChoices: 'For every required row, name each listed choice exactly; labels such as “JSON”, “local path”, “locked”, or “timestamped” alone remain unresolved.',
|
|
45
45
|
proofPlanLines: [
|
|
@@ -58,11 +58,11 @@ const IMPLEMENTATION_READINESS_CLAUSES = {
|
|
|
58
58
|
disposableReviewControls: 'Run mutation or destructive negative controls only on disposable copies below a verified temporary root, never tracked worktree or canonical source bytes.',
|
|
59
59
|
failureSemantics: '`Crash` means abrupt unhandled termination before the claimed catch point; a catchable failure returns/raises an error or exits nonzero. Never use them interchangeably.',
|
|
60
60
|
privacyIdentifiers: 'Any privacy/security claim names the exact identifier surface at risk, such as an env var, header, path, token class, or field name; generic “sensitive data” is insufficient.',
|
|
61
|
-
freshReplay: 'After applying an accepted
|
|
61
|
+
freshReplay: 'After applying an accepted GATE-REVIEW finding, a fresh-context closure pass records and freshly replays its original counterexample after the repair under this exact review-log header:',
|
|
62
62
|
distinctRepairProof: '`Repaired at` cites the repair edit; `Proved at` must cite distinct evidence from the fresh replay, never the repair-edit citation.',
|
|
63
63
|
closureTransition: 'An accepted finding transitions `accepted → repaired → PASS|FAIL|UNKNOWN`.',
|
|
64
64
|
unknownBlocks: 'Only `PASS` closes it; `FAIL` remains open for the remaining paper-review round; `UNKNOWN` blocks implementation handoff.',
|
|
65
|
-
scopeReturnsToC1: 'A repair that adds user semantics or scope returns to
|
|
65
|
+
scopeReturnsToC1: 'A repair that adds user semantics or scope returns to GATE-SCOPE.',
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
const ADAPTIVE_COVERAGE_PROFILE_HEADER = [
|
|
@@ -77,7 +77,7 @@ const ADAPTIVE_COVERAGE_AMBIGUITY_ROWS = [
|
|
|
77
77
|
['State', 'Required action'],
|
|
78
78
|
['`none`', 'proceed'],
|
|
79
79
|
['`examples-needed`', 'add two or three examples only for an already decided rule; promote to `decision-needed` if an example changes observable behavior'],
|
|
80
|
-
['`decision-needed`', 'ask the user at
|
|
80
|
+
['`decision-needed`', 'ask the user at GATE-SCOPE/GATE-REVIEW and keep affected tasks blocked'],
|
|
81
81
|
['`design-needed`', 'after user-owned decisions settle, route material competing technical designs through Brainstorm'],
|
|
82
82
|
];
|
|
83
83
|
const ADAPTIVE_REVIEWER_ROWS = [
|
|
@@ -98,12 +98,12 @@ const ADAPTIVE_COVERAGE_CLAUSES = {
|
|
|
98
98
|
openKinds: 'Change kinds are multi-valued (`add`, `modify`, `fix`, `refactor`, `remove`, `migrate`, `integrate`), and unfamiliar kinds or surfaces use `other:<verbatim>` rather than disappearing.',
|
|
99
99
|
scopedUnion: 'Each task references its CP IDs; authoring, review, edge, and proof obligations union only inside affected rows/tasks.',
|
|
100
100
|
profileRederivation: 'Rederive affected CP rows after any accepted scope, outcome, criteria, ownership, dependency, risk, or proof delta before task status.',
|
|
101
|
-
plannedProof: '`Required proof` is a planned level set, not execution\nevidence: known but unrun proof may be `pending`; `UNKNOWN` reachability blocks\n`pending`; missing, failed, or unavailable required evidence blocks `done`/
|
|
101
|
+
plannedProof: '`Required proof` is a planned level set, not execution\nevidence: known but unrun proof may be `pending`; `UNKNOWN` reachability blocks\n`pending`; missing, failed, or unavailable required evidence blocks `done`/GATE-DONE.',
|
|
102
102
|
proofSeparation: 'Levels stay separate and never promote one another.',
|
|
103
103
|
liveLimit: 'Source/static checks prove the written contract, not live-model adherence.',
|
|
104
104
|
specMakerAuthority: 'they are the canonical risk and coverage authority. Do not duplicate\ntheir taxonomy here.',
|
|
105
105
|
specMakerAmbiguity: 'Apply the canonical ambiguity action; examples never decide observable behavior.',
|
|
106
|
-
specMakerRoute: 'Apply their risk-first route before
|
|
106
|
+
specMakerRoute: 'Apply their risk-first route before GATE-SCOPE and stop when the\nrequest qualifies for direct work; hand off when it requires Brainstorm-only exploration.',
|
|
107
107
|
reviewRisk: 'Keep Fact Checker as the baseline. Assign every remaining material CP risk to a\nnamed reviewer lens; a critical row includes both relevant security-adversary and\nfailure-mode coverage, and nonmaterial lenses are not added.',
|
|
108
108
|
reviewCapacity: 'Reviewer count is fixed by the table, not lens count. Give each reviewer a distinct primary lens; when material lenses exceed reviewers, combine related named lenses on one reviewer and keep every material lens assigned.',
|
|
109
109
|
};
|
|
@@ -126,19 +126,12 @@ const REQUIRED_PAYLOAD = [
|
|
|
126
126
|
'src/claude/rules/review-audit-self-decision.md',
|
|
127
127
|
'src/claude/rules/skill-domain-routing.md',
|
|
128
128
|
'src/claude/rules/skill-workflow-routing.md',
|
|
129
|
-
'src/claude/skills/route/SKILL.md',
|
|
130
|
-
'src/claude/skills/route/references/task-taxonomy.md',
|
|
131
|
-
'src/claude/skills/route/references/chaining-patterns.md',
|
|
132
|
-
'src/claude/skills/route/references/agent-timing.md',
|
|
133
129
|
'src/claude/skills/orca/SKILL.md',
|
|
134
130
|
'src/claude/skills/brainstorm/SKILL.md',
|
|
135
131
|
'src/claude/skills/brainstorm/references/question-framework.md',
|
|
136
132
|
'src/claude/agents/brainstormer.md',
|
|
137
133
|
'src/claude/skills/research/SKILL.md',
|
|
138
134
|
'src/claude/agents/researcher.md',
|
|
139
|
-
'src/claude/skills/loop/SKILL.md',
|
|
140
|
-
'src/claude/skills/loop/references/bounded-loop-protocol.md',
|
|
141
|
-
'src/claude/skills/loop/references/metric-and-guard-contract.md',
|
|
142
135
|
'src/claude/skills/docs/SKILL.md',
|
|
143
136
|
'src/claude/skills/docx/SKILL.md',
|
|
144
137
|
'src/claude/skills/pdf/SKILL.md',
|
|
@@ -179,20 +172,17 @@ const BRAINSTORM_SOURCE_RELATIVES = {
|
|
|
179
172
|
framework: 'src/claude/skills/brainstorm/references/question-framework.md',
|
|
180
173
|
agent: 'src/claude/agents/brainstormer.md',
|
|
181
174
|
};
|
|
182
|
-
const
|
|
175
|
+
const RESEARCH_SOURCE_RELATIVES = {
|
|
183
176
|
research: 'src/claude/skills/research/SKILL.md',
|
|
184
177
|
agent: 'src/claude/agents/researcher.md',
|
|
185
|
-
loop: 'src/claude/skills/loop/SKILL.md',
|
|
186
|
-
protocol: 'src/claude/skills/loop/references/bounded-loop-protocol.md',
|
|
187
|
-
metric: 'src/claude/skills/loop/references/metric-and-guard-contract.md',
|
|
188
178
|
workflow: 'src/claude/rules/skill-workflow-routing.md',
|
|
189
179
|
domain: 'src/claude/rules/skill-domain-routing.md',
|
|
190
180
|
};
|
|
191
181
|
const HOTFIX_SOURCE_RELATIVES = {
|
|
192
|
-
skill: 'src/claude/skills/
|
|
193
|
-
review: 'src/claude/skills/
|
|
194
|
-
parallel: 'src/claude/skills/
|
|
195
|
-
specialized: 'src/claude/skills/
|
|
182
|
+
skill: 'src/claude/skills/fix/SKILL.md',
|
|
183
|
+
review: 'src/claude/skills/fix/references/review-cycle.md',
|
|
184
|
+
parallel: 'src/claude/skills/fix/references/parallel-patterns.md',
|
|
185
|
+
specialized: 'src/claude/skills/fix/references/workflow-specialized.md',
|
|
196
186
|
};
|
|
197
187
|
const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
|
|
198
188
|
'adviser-gate-fallback',
|
|
@@ -208,7 +198,7 @@ const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
|
|
|
208
198
|
];
|
|
209
199
|
const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
|
|
210
200
|
{ group: 'direct-precedence', mutations: [
|
|
211
|
-
{ from: 'Route Direct first, then
|
|
201
|
+
{ from: 'Route Direct first, then apply controls only to requests that remain in Brainstorm.', to: 'Apply controls before Direct classification.', clause: 'Route Direct first, then apply controls only to requests that remain in Brainstorm.' },
|
|
212
202
|
] },
|
|
213
203
|
{ group: 'ordered-depth', mutations: [
|
|
214
204
|
{ from: 'With no Deep signal, use Standard.', to: 'Deep is always the default.', clause: 'With no Deep signal, use Standard. `--deep` raises Standard to Deep.' },
|
|
@@ -216,32 +206,32 @@ const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
|
|
|
216
206
|
{ group: 'leading-flags', mutations: [
|
|
217
207
|
{ from: 'Parse controls only from the leading consecutive token segment.', to: 'Parse flag-like tokens anywhere.', clause: 'Parse controls only from the leading consecutive token segment.' },
|
|
218
208
|
{ from: '`--deep`, `--visual`, and `--advice` in any order, each at most once.', to: '`--deep`, `--visual`, and `--advice` may repeat.', clause: '`--deep`, `--visual`, and `--advice` in any order, each at most once.' },
|
|
219
|
-
{ from: '`--` ends
|
|
209
|
+
{ from: '`--` ends the control segment.', to: '`--` is treated as another control.', clause: '`--` ends the control segment.' },
|
|
220
210
|
{ from: 'An unknown or duplicate `--*` inside the leading segment returns usage', to: 'An unknown or duplicate `--*` is ignored', clause: 'An unknown or duplicate `--*` inside the leading segment returns usage and performs no scout, question, tool call, write, or workflow action.' },
|
|
221
211
|
] },
|
|
222
212
|
{ group: 'lens-trigger-skip', mutations: [
|
|
223
|
-
{ from: 'failure isolation for partial or cascading failure
|
|
213
|
+
{ from: 'failure isolation for partial or cascading failure across boundaries', to: 'generic failure notes', clause: 'failure isolation for partial or cascading failure across boundaries' },
|
|
224
214
|
] },
|
|
225
215
|
{ group: 'evidence-semantics', mutations: [
|
|
226
|
-
{ from: 'Missing evidence
|
|
216
|
+
{ from: 'Missing evidence forces feasibility `unknown` and confidence `low`.', to: 'Missing evidence permits a confident guess.', clause: 'Missing evidence forces feasibility `unknown` and confidence `low`.' },
|
|
227
217
|
] },
|
|
228
218
|
{ group: 'numeric-estimates', mutations: [
|
|
229
|
-
{ from: 'A numeric estimate requires
|
|
219
|
+
{ from: 'A numeric estimate requires range, unit, basis, evidence, and assumptions; otherwise report `unknown`.', to: 'A numeric estimate may be a best-effort number.', clause: 'A numeric estimate requires range, unit, basis, evidence, and assumptions; otherwise report `unknown`.' },
|
|
230
220
|
] },
|
|
231
221
|
{ group: 'pre-tool-authority-redaction', mutations: [
|
|
232
|
-
{ from: 'Before an
|
|
233
|
-
{ from: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and
|
|
234
|
-
{ from: 'Durable or external
|
|
222
|
+
{ from: 'Before an external visual tool or adviser handoff, minimize context and redact secrets, credentials, private keys, access tokens, and unnecessary PII.', to: 'Forward full context to every external tool and adviser.', clause: 'Before an external visual tool or adviser handoff, minimize context and redact secrets, credentials, private keys, access tokens, and unnecessary PII.' },
|
|
223
|
+
{ from: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and falls back to equivalent text when rendering is unavailable.', to: '`--visual` fails when rendering is unavailable.', clause: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and falls back to equivalent text when rendering is unavailable.' },
|
|
224
|
+
{ from: 'Durable or external rendering requires explicit user authority before invocation.', to: 'Durable or external rendering may run without consent.', clause: 'Durable or external rendering requires explicit user authority before invocation.' },
|
|
235
225
|
] },
|
|
236
226
|
{ group: 'adviser-gate-fallback', mutations: [
|
|
237
|
-
{ from: '`--advice` invokes
|
|
238
|
-
{ from: 'if advice is unavailable or
|
|
227
|
+
{ from: '`--advice` invokes `brainstormer` only after the material-choice gate;', to: '`--advice` invokes `brainstormer` before routing;', clause: '`--advice` invokes `brainstormer` only after the material-choice gate;' },
|
|
228
|
+
{ from: 'if advice is unavailable or fails, label it unavailable and continue with controller analysis.', to: 'if advice is unavailable, stop the workflow.', clause: 'if advice is unavailable or fails, label it unavailable and continue with controller analysis.' },
|
|
239
229
|
] },
|
|
240
230
|
{ group: 'decision-brief', mutations: [
|
|
241
|
-
{ from: 'The first section records target
|
|
231
|
+
{ from: 'The first section records target identity, current source revision and worktree state or `[UNVERIFIED]`, an evidence-as-of value, and what change invalidates the brief.', to: 'The handoff has no revision or freshness binding.', clause: 'The first section records target identity, current source revision and worktree state or `[UNVERIFIED]`, an evidence-as-of value, and what change invalidates the brief.' },
|
|
242
232
|
] },
|
|
243
233
|
{ group: 'no-persistence-dispatch', mutations: [
|
|
244
|
-
{ from: 'Neither overlay
|
|
234
|
+
{ from: 'Neither overlay writes, approves, persists, dispatches, or completes work.', to: 'Overlays may persist, approve, dispatch, and complete work.', clause: 'Neither overlay writes, approves, persists, dispatches, or completes work.' },
|
|
245
235
|
] },
|
|
246
236
|
];
|
|
247
237
|
|
|
@@ -342,20 +332,15 @@ function runInstaller(installer, root, platforms, lang, extraArgs = []) {
|
|
|
342
332
|
return result;
|
|
343
333
|
}
|
|
344
334
|
|
|
345
|
-
function
|
|
335
|
+
function installedRoutingRuleFiles(project, platform) {
|
|
346
336
|
const runtimeRoot = platform === 'codex' ? '.codex' : '.claude';
|
|
347
|
-
const root = path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route');
|
|
348
337
|
return {
|
|
349
|
-
skill: path.join(root, 'SKILL.md'),
|
|
350
|
-
taxonomy: path.join(root, 'references/task-taxonomy.md'),
|
|
351
|
-
chaining: path.join(root, 'references/chaining-patterns.md'),
|
|
352
|
-
timing: path.join(root, 'references/agent-timing.md'),
|
|
353
338
|
workflow: path.join(project, runtimeRoot, 'rules/skill-workflow-routing.md'),
|
|
354
339
|
domain: path.join(project, runtimeRoot, 'rules/skill-domain-routing.md'),
|
|
355
340
|
};
|
|
356
341
|
}
|
|
357
342
|
|
|
358
|
-
function
|
|
343
|
+
function routingRuleIssues(files) {
|
|
359
344
|
const text = Object.fromEntries(Object.entries(files).map(([key, file]) => [
|
|
360
345
|
key, fs.readFileSync(file, 'utf8').replace(/\s+/g, ' '),
|
|
361
346
|
]));
|
|
@@ -363,56 +348,10 @@ function routeProjectionIssues(files) {
|
|
|
363
348
|
const requires = (issue, clauses) => {
|
|
364
349
|
if (clauses.some(([source, clause]) => !text[source].includes(clause))) issues.add(issue);
|
|
365
350
|
};
|
|
366
|
-
requires('direct-path', [
|
|
367
|
-
['skill', 'invoke that skill directly'],
|
|
368
|
-
['skill', 'use it directly without constructing a chain'],
|
|
369
|
-
['skill', 'Do not create a route chain or spawn agents merely to answer it'],
|
|
370
|
-
]);
|
|
371
|
-
requires('classification', [
|
|
372
|
-
['skill', 'final deliverable'], ['skill', 'highest-link risk'],
|
|
373
|
-
['skill', 'number of material domains'],
|
|
374
|
-
]);
|
|
375
|
-
requires('link-contract', [
|
|
376
|
-
['chaining', '**Entry:**'], ['chaining', '**Exit:**'], ['chaining', '**Owner:** exactly one'],
|
|
377
|
-
]);
|
|
378
|
-
requires('failure-stop', [
|
|
379
|
-
['chaining', '(link, owner, normalized cause)'],
|
|
380
|
-
['chaining', 'Two failures with the same failure key stop the chain'],
|
|
381
|
-
['chaining', 'Different normalized causes do not share a key'],
|
|
382
|
-
]);
|
|
383
|
-
requires('collapse-and-detour', [
|
|
384
|
-
['skill', 'remove every link whose output is already evidenced'],
|
|
385
|
-
['chaining', 'preserve its valid exit evidence, normalize the root cause, and choose one bounded detour'],
|
|
386
|
-
]);
|
|
387
|
-
requires('delegation-contract', [
|
|
388
|
-
['timing', '**Outcome** — observable result'],
|
|
389
|
-
['timing', '**Scope** — owned files, systems, or questions'],
|
|
390
|
-
['timing', '**Inputs** — current evidence and prerequisite artifacts'],
|
|
391
|
-
['timing', '**Constraints** — safety, compatibility, authority, and non-goals'],
|
|
392
|
-
['timing', '**Acceptance** — proof required for completion'],
|
|
393
|
-
['timing', '**Handoff** — expected returned artifact and destination'],
|
|
394
|
-
['timing', '**Status vocabulary** — `DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT`'],
|
|
395
|
-
]);
|
|
396
|
-
requires('agent-fallback', [
|
|
397
|
-
['timing', 'If the preferred agent is absent'],
|
|
398
|
-
['timing', 'never synthesize a role'],
|
|
399
|
-
['timing', 'DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT'],
|
|
400
|
-
]);
|
|
401
|
-
requires('authority', [
|
|
402
|
-
['skill', 'never expand it'],
|
|
403
|
-
['skill', 'diagnosis does not authorize repair'],
|
|
404
|
-
['skill', 'does not authorize commit, push, deploy, publish, or release'],
|
|
405
|
-
]);
|
|
406
|
-
requires('risk-gates', [
|
|
407
|
-
['skill', 'require independent review and user confirmation before advancing past the high-risk gate'],
|
|
408
|
-
]);
|
|
409
|
-
requires('proof-boundary', [
|
|
410
|
-
['skill', 'remains `[UNPROVEN]` until a separate host run observes it'],
|
|
411
|
-
]);
|
|
412
351
|
requires('rule-direct-path', [
|
|
413
352
|
['workflow', 'If the user names a valid installed skill, use it directly'],
|
|
414
353
|
['workflow', 'If one obvious low-risk installed skill covers the intent, use it directly'],
|
|
415
|
-
['workflow', 'do not
|
|
354
|
+
['workflow', 'do not spawn agents for ceremony'],
|
|
416
355
|
]);
|
|
417
356
|
requires('rule-live-catalog', [
|
|
418
357
|
['workflow', 'Resolve every abstract link against the current runtime catalog'],
|
|
@@ -774,30 +713,24 @@ function assertPackedBrainstormParity(project, platform) {
|
|
|
774
713
|
}
|
|
775
714
|
}
|
|
776
715
|
|
|
777
|
-
function
|
|
716
|
+
function packedResearchPaths(project, platform) {
|
|
778
717
|
return platform === 'codex'
|
|
779
718
|
? {
|
|
780
719
|
research: path.join(project, '.agents/skills/research/SKILL.md'),
|
|
781
720
|
agent: path.join(project, '.codex/agents/researcher.toml'),
|
|
782
|
-
loop: path.join(project, '.agents/skills/loop/SKILL.md'),
|
|
783
|
-
protocol: path.join(project, '.agents/skills/loop/references/bounded-loop-protocol.md'),
|
|
784
|
-
metric: path.join(project, '.agents/skills/loop/references/metric-and-guard-contract.md'),
|
|
785
721
|
workflow: path.join(project, '.codex/rules/skill-workflow-routing.md'),
|
|
786
722
|
domain: path.join(project, '.codex/rules/skill-domain-routing.md'),
|
|
787
723
|
}
|
|
788
724
|
: {
|
|
789
725
|
research: path.join(project, '.claude/skills/research/SKILL.md'),
|
|
790
726
|
agent: path.join(project, '.claude/agents/researcher.md'),
|
|
791
|
-
loop: path.join(project, '.claude/skills/loop/SKILL.md'),
|
|
792
|
-
protocol: path.join(project, '.claude/skills/loop/references/bounded-loop-protocol.md'),
|
|
793
|
-
metric: path.join(project, '.claude/skills/loop/references/metric-and-guard-contract.md'),
|
|
794
727
|
workflow: path.join(project, '.claude/rules/skill-workflow-routing.md'),
|
|
795
728
|
domain: path.join(project, '.claude/rules/skill-domain-routing.md'),
|
|
796
729
|
};
|
|
797
730
|
}
|
|
798
731
|
|
|
799
|
-
function
|
|
800
|
-
const paths =
|
|
732
|
+
function readPackedResearch(project, platform) {
|
|
733
|
+
const paths = packedResearchPaths(project, platform);
|
|
801
734
|
const values = Object.fromEntries(Object.entries(paths).map(([key, target]) => [
|
|
802
735
|
key, fs.readFileSync(target, 'utf8'),
|
|
803
736
|
]));
|
|
@@ -807,13 +740,12 @@ function readPackedResearchLoop(project, platform) {
|
|
|
807
740
|
return values;
|
|
808
741
|
}
|
|
809
742
|
|
|
810
|
-
function
|
|
811
|
-
const values = Object.fromEntries(Object.entries(
|
|
743
|
+
function packedResearchIssues(project, platform) {
|
|
744
|
+
const values = Object.fromEntries(Object.entries(readPackedResearch(project, platform)).map(
|
|
812
745
|
([key, value]) => [key, value.replace(/\s+/g, ' ').trim()]
|
|
813
746
|
));
|
|
814
747
|
const issues = new Set();
|
|
815
748
|
const publicResearch = platform === 'codex' ? 'name: cf-research' : 'name: cf:research';
|
|
816
|
-
const publicLoop = platform === 'codex' ? 'name: cf-loop' : 'name: cf:loop';
|
|
817
749
|
if (!values.research.includes(publicResearch)
|
|
818
750
|
|| !values.research.includes('Choose the smallest depth that can support the decision:')
|
|
819
751
|
|| !values.research.includes('Use delegated researchers only as optional acceleration')
|
|
@@ -826,35 +758,15 @@ function packedResearchLoopIssues(project, platform) {
|
|
|
826
758
|
|| !values.agent.includes('owns any authorized persistence.')) {
|
|
827
759
|
issues.add('research-agent-boundary');
|
|
828
760
|
}
|
|
829
|
-
if (
|
|
830
|
-
|
|
831
|
-
|| !values.loop.includes('Reject dirty in-scope state; record but never import or clean out-of-scope dirt.')
|
|
832
|
-
|| !values.loop.includes('separately approved external realpath')
|
|
833
|
-
|| !values.loop.includes('complete detached-worktree tracked/untracked manifest')
|
|
834
|
-
|| !values.loop.includes('Live-agent adherence to this written contract is `[UNPROVEN]` without a host run.')) {
|
|
835
|
-
issues.add('loop-safety');
|
|
836
|
-
}
|
|
837
|
-
if (!values.metric.includes('Guard is mandatory, fixed before baseline, and distinct from Metric.')
|
|
838
|
-
|| !values.metric.includes('`numeric_format`: exactly IEEE-754 binary64')
|
|
839
|
-
|| !values.metric.includes('nonzero exit')
|
|
840
|
-
|| !values.metric.includes('require median, noise, improvement, and required delta to remain finite.')) {
|
|
841
|
-
issues.add('metric-guard');
|
|
842
|
-
}
|
|
843
|
-
if (!values.protocol.includes('do not clean. Preserve the exact path, PID/process evidence, and ownership marker; return `BLOCKED`')
|
|
844
|
-
|| !values.protocol.includes('`base_oid`;')
|
|
845
|
-
|| !values.protocol.includes('patch byte length and lowercase SHA-256')) {
|
|
846
|
-
issues.add('failure-handoff');
|
|
847
|
-
}
|
|
848
|
-
if (!values.workflow.includes('numeric optimization capability remains explicit-only and must never be auto-routed.')
|
|
849
|
-
|| !values.domain.includes('explicit-only numeric optimization capability unless the user invokes it with its required bounded metric/guard contract.')) {
|
|
850
|
-
issues.add('explicit-routing');
|
|
761
|
+
if (/numeric optimization capability/.test(`${values.workflow}\n${values.domain}`)) {
|
|
762
|
+
issues.add('loop-removed');
|
|
851
763
|
}
|
|
852
764
|
return [...issues].sort();
|
|
853
765
|
}
|
|
854
766
|
|
|
855
|
-
function
|
|
856
|
-
const installed =
|
|
857
|
-
for (const [key, relative] of Object.entries(
|
|
767
|
+
function assertPackedResearchParity(project, platform) {
|
|
768
|
+
const installed = packedResearchPaths(project, platform);
|
|
769
|
+
for (const [key, relative] of Object.entries(RESEARCH_SOURCE_RELATIVES)) {
|
|
858
770
|
const sourcePath = path.join(PACKAGE_ROOT, relative);
|
|
859
771
|
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
860
772
|
const expected = platform === 'codex'
|
|
@@ -866,12 +778,15 @@ function assertPackedResearchLoopParity(project, platform) {
|
|
|
866
778
|
}
|
|
867
779
|
const manifest = JSON.parse(fs.readFileSync(path.join(project, RUNTIMES[platform].manifest), 'utf8'));
|
|
868
780
|
const prefixes = platform === 'codex'
|
|
869
|
-
? ['.agents/skills/research/'
|
|
870
|
-
: ['skills/research/'
|
|
781
|
+
? ['.agents/skills/research/']
|
|
782
|
+
: ['skills/research/'];
|
|
871
783
|
for (const prefix of prefixes) {
|
|
872
784
|
assert.ok(Object.keys(manifest.files).some((entry) => entry.startsWith(prefix)), `${platform} manifest missing ${prefix}`);
|
|
873
785
|
}
|
|
874
|
-
|
|
786
|
+
const removedLoop = platform === 'codex' ? '.agents/skills/loop/' : 'skills/loop/';
|
|
787
|
+
assert.equal(Object.keys(manifest.files).some((entry) => entry.startsWith(removedLoop)), false, `${platform} manifest still lists ${removedLoop}`);
|
|
788
|
+
assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/loop' : '.claude/skills/loop')), false, `${platform} still installs Loop`);
|
|
789
|
+
assert.deepEqual(packedResearchIssues(project, platform), []);
|
|
875
790
|
}
|
|
876
791
|
|
|
877
792
|
function packedBrainstormInstalledPaths(project, platform) {
|
|
@@ -1316,8 +1231,8 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
|
|
|
1316
1231
|
},
|
|
1317
1232
|
{
|
|
1318
1233
|
name: 'fresh-original-counterexample-replay', issue: 'repair-closure', source: 'review',
|
|
1319
|
-
from: 'After applying an accepted
|
|
1320
|
-
to: 'After applying an accepted
|
|
1234
|
+
from: 'After applying an accepted GATE-REVIEW finding, a fresh-context closure pass records and\nfreshly replays its original counterexample after the repair under this exact review-log header:',
|
|
1235
|
+
to: 'After applying an accepted GATE-REVIEW finding, record the repair under this review-log header:',
|
|
1321
1236
|
},
|
|
1322
1237
|
{
|
|
1323
1238
|
name: 'distinct-repair-and-proof-evidence', issue: 'repair-closure', source: 'review',
|
|
@@ -1423,7 +1338,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
|
|
|
1423
1338
|
['stale-profile-after-c2', 'templates', ADAPTIVE_COVERAGE_CLAUSES.profileRederivation,
|
|
1424
1339
|
'Keep existing coverage rows after accepted plan changes.', ['profile-lifecycle']],
|
|
1425
1340
|
['planned-proof-blocks-start', 'templates', ADAPTIVE_COVERAGE_CLAUSES.plannedProof,
|
|
1426
|
-
'`Required proof` is execution evidence: known but unrun proof blocks `pending`; `UNKNOWN` may proceed; missing evidence may still reach `done`/
|
|
1341
|
+
'`Required proof` is execution evidence: known but unrun proof blocks `pending`; `UNKNOWN` may proceed; missing evidence may still reach `done`/GATE-DONE.', ['proof-lifecycle']],
|
|
1427
1342
|
['source-promotes-live', 'templates', ADAPTIVE_COVERAGE_CLAUSES.proofSeparation,
|
|
1428
1343
|
'Source proof may promote installed and live proof.', ['proof-lifecycle']],
|
|
1429
1344
|
['unmapped-proof-level', 'templates', IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
|
|
@@ -1437,7 +1352,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
|
|
|
1437
1352
|
['spec-maker-examples-decide', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity,
|
|
1438
1353
|
'Use examples to settle every ambiguous observable behavior.', ['spec-maker-authority']],
|
|
1439
1354
|
['spec-maker-skips-brainstorm', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute,
|
|
1440
|
-
'Apply the risk-first route before
|
|
1355
|
+
'Apply the risk-first route before GATE-SCOPE and stop only for direct work.', ['spec-maker-authority']],
|
|
1441
1356
|
['static-proves-live', 'skill', ADAPTIVE_COVERAGE_CLAUSES.liveLimit,
|
|
1442
1357
|
'Source/static checks prove live-model adherence.', ['proof-lifecycle']],
|
|
1443
1358
|
];
|
|
@@ -1613,63 +1528,11 @@ function assertInstalledProvenance(root, platform, fixture) {
|
|
|
1613
1528
|
root,
|
|
1614
1529
|
platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
|
|
1615
1530
|
);
|
|
1616
|
-
const authorityDir = path.join(root, platform === 'claude' ? '.claude/hooks' : '.codex/hooks');
|
|
1617
|
-
const authority = path.join(authorityDir, 'completion-authority.cjs');
|
|
1618
|
-
const authorityCheck = path.join(authorityDir, 'completion-authority-check.cjs');
|
|
1619
|
-
const authorityState = path.join(authorityDir, 'completion-authority-state.cjs');
|
|
1620
|
-
const semanticAuthority = path.join(authorityDir, 'semantic-review-authority.cjs');
|
|
1621
1531
|
for (const file of [helper, policy, resolver, receipt, finalState, readiness]) {
|
|
1622
1532
|
assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
|
|
1623
1533
|
}
|
|
1624
1534
|
assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
|
|
1625
1535
|
|
|
1626
|
-
for (const file of [authority, authorityCheck, authorityState, semanticAuthority]) {
|
|
1627
|
-
assert.equal(fs.existsSync(file), true, `${platform} installed completion authority missing: ${file}`);
|
|
1628
|
-
}
|
|
1629
|
-
const finalStateBytes = fs.readFileSync(finalState);
|
|
1630
|
-
fs.rmSync(finalState);
|
|
1631
|
-
const missingFinalState = spawnSync(process.execPath, [authority, '--stop'], {
|
|
1632
|
-
cwd: root,
|
|
1633
|
-
env: { ...process.env, PROJECT_ROOT: root },
|
|
1634
|
-
input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
|
|
1635
|
-
encoding: 'utf8',
|
|
1636
|
-
});
|
|
1637
|
-
assert.equal(JSON.parse(missingFinalState.stdout).decision, 'block', `${platform} missing final-state closure must fail closed`);
|
|
1638
|
-
const escapedFinalState = path.join(root, 'escaped-spec-final-state.cjs');
|
|
1639
|
-
fs.writeFileSync(escapedFinalState, finalStateBytes);
|
|
1640
|
-
fs.symlinkSync(escapedFinalState, finalState);
|
|
1641
|
-
const symlinkFinalState = spawnSync(process.execPath, [authority, '--stop'], {
|
|
1642
|
-
cwd: root,
|
|
1643
|
-
env: { ...process.env, PROJECT_ROOT: root },
|
|
1644
|
-
input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
|
|
1645
|
-
encoding: 'utf8',
|
|
1646
|
-
});
|
|
1647
|
-
assert.equal(JSON.parse(symlinkFinalState.stdout).decision, 'block', `${platform} symlinked final-state closure must fail closed`);
|
|
1648
|
-
fs.rmSync(finalState);
|
|
1649
|
-
fs.writeFileSync(finalState, finalStateBytes);
|
|
1650
|
-
fs.rmSync(escapedFinalState);
|
|
1651
|
-
const authorityCheckBytes = fs.readFileSync(authorityCheck);
|
|
1652
|
-
fs.writeFileSync(authorityCheck, 'module.exports = {};\n');
|
|
1653
|
-
const malformedAuthority = spawnSync(process.execPath, [authority, '--stop'], {
|
|
1654
|
-
cwd: root,
|
|
1655
|
-
env: { ...process.env, PROJECT_ROOT: root },
|
|
1656
|
-
input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
|
|
1657
|
-
encoding: 'utf8',
|
|
1658
|
-
});
|
|
1659
|
-
assert.equal(malformedAuthority.status, 0, `${platform} malformed authority must fail closed`);
|
|
1660
|
-
assert.equal(JSON.parse(malformedAuthority.stdout).decision, 'block');
|
|
1661
|
-
fs.writeFileSync(authorityCheck, authorityCheckBytes);
|
|
1662
|
-
fs.rmSync(authorityCheck);
|
|
1663
|
-
const missingAuthority = spawnSync(process.execPath, [authority, '--stop'], {
|
|
1664
|
-
cwd: root,
|
|
1665
|
-
env: { ...process.env, PROJECT_ROOT: root },
|
|
1666
|
-
input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
|
|
1667
|
-
encoding: 'utf8',
|
|
1668
|
-
});
|
|
1669
|
-
assert.equal(missingAuthority.status, 0, `${platform} missing authority dependency must fail closed`);
|
|
1670
|
-
assert.equal(JSON.parse(missingAuthority.stdout).decision, 'block');
|
|
1671
|
-
fs.writeFileSync(authorityCheck, authorityCheckBytes);
|
|
1672
|
-
|
|
1673
1536
|
const helperRun = spawnSync(process.execPath, provenanceCliArgs(helper, root, fixture), {
|
|
1674
1537
|
cwd: root,
|
|
1675
1538
|
encoding: 'utf8',
|
|
@@ -1843,8 +1706,6 @@ function installedSemanticPaths(root, platform) {
|
|
|
1843
1706
|
semanticModel: path.join(runtimeRoot, 'scripts', 'spec-semantic-model.cjs'),
|
|
1844
1707
|
resolver: path.join(runtimeRoot, 'scripts', 'spec-resolver.cjs'),
|
|
1845
1708
|
provenance: path.join(runtimeRoot, 'scripts', 'provenance.cjs'),
|
|
1846
|
-
completion: path.join(runtimeRoot, 'hooks', 'completion-authority-check.cjs'),
|
|
1847
|
-
semanticAuthority: path.join(runtimeRoot, 'hooks', 'semantic-review-authority.cjs'),
|
|
1848
1709
|
stateTemplate: path.join(skillRoot, 'specs', 'templates', 'spec-state.json'),
|
|
1849
1710
|
};
|
|
1850
1711
|
for (const [name, target] of Object.entries(paths)) {
|
|
@@ -2133,125 +1994,16 @@ function initializeGit(root) {
|
|
|
2133
1994
|
}
|
|
2134
1995
|
}
|
|
2135
1996
|
|
|
2136
|
-
function
|
|
2137
|
-
const
|
|
2138
|
-
const
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2141
|
-
const
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
const feature = process.argv[6];
|
|
2147
|
-
const specFile = path.join(root, 'specs', feature, 'spec.json');
|
|
2148
|
-
const state = JSON.parse(fs.readFileSync(specFile, 'utf8'));
|
|
2149
|
-
state.status = 'done';
|
|
2150
|
-
fs.writeFileSync(specFile, JSON.stringify(state, null, 2) + '\\n');
|
|
2151
|
-
const context = provenance.deriveRuntimeContext({ projectRoot: root, specsRoot: path.join(root, 'specs'), specFile, featureName: feature, runtimeSession: 'packed-session' });
|
|
2152
|
-
fs.writeFileSync(path.join(root, 'specs', feature, 'feature-receipt.md'), ['Verification: PASS','Command: node --test','Exit: 0','Result: PASS','Expected: installed behavior passes','Observed: installed behavior passed','Base: ' + context.base,'Head: ' + context.head,'Feature: ' + feature].join('\\n') + '\\n');
|
|
2153
|
-
const result = checker.evaluateCloseout({ resolver, policy, projectRoot: root, runtime: {}, payload: { session_id: 'packed-session', featureName: feature } });
|
|
2154
|
-
process.stdout.write(JSON.stringify({ ok: result.ok, active: result.active, reason: result.reason || null, feature: result.candidate && result.candidate.featureName }));`;
|
|
2155
|
-
const result = spawnSync(process.execPath, ['-e', code, paths.policy, paths.provenance, paths.completion, paths.resolver, root, fixture.state.feature_name], {
|
|
2156
|
-
cwd: root, encoding: 'utf8', env: { ...process.env, HOME: home },
|
|
2157
|
-
});
|
|
2158
|
-
fs.rmSync(home, { recursive: true, force: true });
|
|
2159
|
-
assert.equal(result.status, 0, `${platform}\n${result.stdout}\n${result.stderr}`);
|
|
2160
|
-
assert.deepEqual(JSON.parse(result.stdout), {
|
|
2161
|
-
ok: true, active: true, reason: null, feature: fixture.state.feature_name,
|
|
2162
|
-
});
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
// `assertInstalledCompletion` leaves this fixture claiming closeout, which is what makes
|
|
2166
|
-
// it the one packet closeout approval resolves among the scaffolded siblings.
|
|
2167
|
-
const CLOSING_FIXTURE = 'compact-installed';
|
|
2168
|
-
|
|
2169
|
-
function assertInstalledResolution(paths, root, platform, explicitFeature) {
|
|
2170
|
-
const code = `
|
|
2171
|
-
const checker = require(process.argv[1]); const resolver = require(process.argv[2]);
|
|
2172
|
-
const root = process.argv[3]; const platform = process.argv[4]; const feature = process.argv[5];
|
|
2173
|
-
const base = { projectRoot: root, runtime: {}, payload: {} };
|
|
2174
|
-
if (platform === 'claude') base.resolver = resolver;
|
|
2175
|
-
const explicit = checker.resolveCandidate({ ...base, payload: { featureName: feature } });
|
|
2176
|
-
const ambiguous = checker.resolveCandidate(base);
|
|
2177
|
-
process.stdout.write(JSON.stringify({ explicit: explicit && explicit.featureName, isNull: ambiguous === null, name: ambiguous && ambiguous.featureName, error: ambiguous && ambiguous.error, candidates: ambiguous && ambiguous.candidates }));`;
|
|
2178
|
-
const run = () => {
|
|
2179
|
-
const result = spawnSync(process.execPath, ['-e', code, paths.completion, paths.resolver, root, platform, explicitFeature], { cwd: root, encoding: 'utf8' });
|
|
2180
|
-
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2181
|
-
return JSON.parse(result.stdout);
|
|
2182
|
-
};
|
|
2183
|
-
|
|
2184
|
-
// Contract change: closeout approval counts only packets actually claiming closeout.
|
|
2185
|
-
// This used to assert `multiple_persisted` from these fixtures, which pinned the
|
|
2186
|
-
// over-strict behaviour issue #79 reports. `assertInstalledCompletion` above drove
|
|
2187
|
-
// `compact-installed` to closeout, so exactly one of the three is claiming it; the
|
|
2188
|
-
// scaffolded siblings are not rivals and must not block the approval.
|
|
2189
|
-
const narrowed = run();
|
|
2190
|
-
assert.equal(narrowed.explicit, explicitFeature);
|
|
2191
|
-
assert.equal(narrowed.name, CLOSING_FIXTURE, `the one packet claiming closeout must resolve: ${JSON.stringify(narrowed)}`);
|
|
2192
|
-
assert.equal(narrowed.error, undefined);
|
|
2193
|
-
|
|
2194
|
-
// Genuine ambiguity still blocks, and the installed copy must prove it rather than
|
|
2195
|
-
// being assumed from the source tree this test deliberately deleted.
|
|
2196
|
-
const closing = ['closing-a-installed', 'closing-b-installed'];
|
|
2197
|
-
for (const name of closing) {
|
|
2198
|
-
const dir = path.join(root, 'specs', name);
|
|
2199
|
-
fs.mkdirSync(path.join(dir, 'tasks'), { recursive: true });
|
|
2200
|
-
fs.writeFileSync(path.join(dir, 'spec.json'), `${JSON.stringify({
|
|
2201
|
-
feature_name: name,
|
|
2202
|
-
status: 'done',
|
|
2203
|
-
current_phase: 'closeout',
|
|
2204
|
-
task_registry: { 'tasks/task-R0-01-x.md': { status: 'done', completed_at: '2026-01-01T00:00:00.000Z' } },
|
|
2205
|
-
}, null, 2)}\n`);
|
|
2206
|
-
fs.writeFileSync(path.join(dir, 'tasks', 'task-R0-01-x.md'), '# Task\n\nStatus: done\n');
|
|
2207
|
-
}
|
|
2208
|
-
try {
|
|
2209
|
-
const ambiguous = run();
|
|
2210
|
-
assert.equal(ambiguous.explicit, explicitFeature, 'an explicit target still wins');
|
|
2211
|
-
assert.equal(ambiguous.error, 'multiple_persisted');
|
|
2212
|
-
assert.deepEqual([...ambiguous.candidates].sort(), [...closing, CLOSING_FIXTURE].sort(), 'only claiming packets are named');
|
|
2213
|
-
} finally {
|
|
2214
|
-
for (const name of closing) fs.rmSync(path.join(root, 'specs', name), { recursive: true, force: true });
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
function assertStrictSimulatedHandlerGuardrail(paths, root, fixture) {
|
|
2219
|
-
// Simulated SubagentStop handler chain — NOT a live Codex host E2E.
|
|
2220
|
-
// This exercises the installed hook's handler logic via direct spawn,
|
|
2221
|
-
// not a real Codex binary dispatching SubagentStop. For live host,
|
|
2222
|
-
// see the opt-in CAFEKIT_CODEX_HOST_E2E test below.
|
|
2223
|
-
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-strict-home-'));
|
|
2224
|
-
const code = `
|
|
2225
|
-
const authority = require(process.argv[1]); const readiness = require(process.argv[2]); const root = process.argv[3]; const feature = process.argv[4]; const digest = process.argv[5]; const review = JSON.parse(process.argv[6]);
|
|
2226
|
-
const spec_file = 'specs/' + feature + '/spec.json';
|
|
2227
|
-
const marker = authority.MARKER + JSON.stringify({ feature_name: feature, spec_file, semantic_digest: digest, verdict: 'PASS' });
|
|
2228
|
-
const before = authority.verifyAttestation(root, spec_file, feature, digest);
|
|
2229
|
-
const {spawnSync} = require('child_process');
|
|
2230
|
-
const authorityPath = process.argv[1];
|
|
2231
|
-
function handlerInvoke(payload) {
|
|
2232
|
-
return spawnSync(process.execPath, [authorityPath], {cwd: root, input: JSON.stringify(payload), encoding:'utf8', env: {...process.env, HOME: process.env.HOME, USERPROFILE: process.env.HOME, PROJECT_ROOT: root}});
|
|
2233
|
-
}
|
|
2234
|
-
const forgedPayload = {cwd: root, hook_event_name: 'SubagentStop', session_id: 'self-session', agent_id: 'self', agent_type: 'spec-maker', last_assistant_message: marker};
|
|
2235
|
-
const forgedRes = handlerInvoke(forgedPayload);
|
|
2236
|
-
const afterForged = authority.verifyAttestation(root, spec_file, feature, digest);
|
|
2237
|
-
const observedPayload = {cwd: root, hook_event_name: 'SubagentStop', session_id: 'host-session', agent_id: 'reviewer-1', agent_type: 'code-auditor', last_assistant_message: marker};
|
|
2238
|
-
const observedRes = handlerInvoke(observedPayload);
|
|
2239
|
-
const afterObserved = authority.verifyAttestation(root, spec_file, feature, digest);
|
|
2240
|
-
const finalized = readiness.finalizeReadiness({ specDir: require('path').join(root, 'specs', feature), projectRoot: root, reviewResult: review });
|
|
2241
|
-
process.stdout.write(JSON.stringify({ before: before.ok, forged: afterForged.ok, afterForged: afterForged.ok, observed: afterObserved.ok, afterObserved: afterObserved.ok, ready: finalized.spec.ready_for_implementation, forgedStderr: forgedRes.stderr.trim(), observedStderr: observedRes.stderr.trim() }));`;
|
|
2242
|
-
const result = spawnSync(process.execPath, ['-e', code, paths.semanticAuthority, paths.readiness, root, fixture.state.feature_name, fixture.digest, JSON.stringify(fixture.reviewResult)], {
|
|
2243
|
-
cwd: root, encoding: 'utf8', env: { ...process.env, HOME: home, PROJECT_ROOT: root },
|
|
2244
|
-
});
|
|
2245
|
-
fs.rmSync(home, { recursive: true, force: true });
|
|
2246
|
-
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2247
|
-
const out = JSON.parse(result.stdout);
|
|
2248
|
-
// Simulated handler: forged self-attestation via spec-maker must NOT create observation, code-auditor must.
|
|
2249
|
-
assert.equal(out.before, false, 'strict attestation must be missing before simulated handler observation');
|
|
2250
|
-
assert.equal(out.afterForged, false, 'forged self-attestation must NOT create simulated handler observation');
|
|
2251
|
-
assert.equal(out.afterObserved, true, 'code-auditor simulated handler must create observation');
|
|
2252
|
-
assert.equal(out.ready, true, 'SPEC_READY after simulated handler observation');
|
|
2253
|
-
assert.ok(out.forgedStderr.includes('rejected') || out.forgedStderr === '', 'forged handler invoke should be rejected or silent but not create observation');
|
|
2254
|
-
assert.equal(out.observedStderr, '', 'code-auditor simulated handler observation should be silent');
|
|
1997
|
+
function assertStrictRefusedAsUnsupported(paths, root, fixture) {
|
|
1998
|
+
const specFile = path.join(fixture.featureDir, 'spec.json');
|
|
1999
|
+
const before = fs.readFileSync(specFile);
|
|
2000
|
+
const reviewFile = path.join(root, 'strict-review.json');
|
|
2001
|
+
fs.writeFileSync(reviewFile, JSON.stringify(fixture.reviewResult));
|
|
2002
|
+
const result = spawnSync(process.execPath, [paths.readiness, fixture.featureDir, '--review-result', reviewFile], { cwd: root, encoding: 'utf8' });
|
|
2003
|
+
fs.rmSync(reviewFile, { force: true });
|
|
2004
|
+
assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2005
|
+
assert.match(`${result.stdout}\n${result.stderr}`, /Strict assurance is no longer supported/);
|
|
2006
|
+
assert.equal(fs.readFileSync(specFile).equals(before), true, 'a refused Strict readiness must leave spec.json unchanged');
|
|
2255
2007
|
}
|
|
2256
2008
|
|
|
2257
2009
|
function assertStaleDigestMutations(paths, root, taskless, taskBearing) {
|
|
@@ -2285,8 +2037,8 @@ function assertTransforms(root, platform) {
|
|
|
2285
2037
|
);
|
|
2286
2038
|
const skill = path.join(skillRoot, 'develop', 'SKILL.md');
|
|
2287
2039
|
const content = fs.readFileSync(skill, 'utf8');
|
|
2288
|
-
const scout = fs.readFileSync(path.join(skillRoot, '
|
|
2289
|
-
const ask = fs.readFileSync(path.join(skillRoot, '
|
|
2040
|
+
const scout = fs.readFileSync(path.join(skillRoot, 'scout', 'SKILL.md'), 'utf8');
|
|
2041
|
+
const ask = fs.readFileSync(path.join(skillRoot, 'ask', 'SKILL.md'), 'utf8');
|
|
2290
2042
|
if (platform === 'codex') {
|
|
2291
2043
|
assert.match(content, /\$cf-develop/);
|
|
2292
2044
|
assert.doesNotMatch(content, /\/cf:develop/);
|
|
@@ -2297,8 +2049,8 @@ function assertTransforms(root, platform) {
|
|
|
2297
2049
|
assert.match(scout, /^name:\s*cf:scout$/m);
|
|
2298
2050
|
assert.match(ask, /^name:\s*cf:ask$/m);
|
|
2299
2051
|
}
|
|
2300
|
-
assert.equal(fs.existsSync(path.join(skillRoot, '
|
|
2301
|
-
assert.equal(fs.existsSync(path.join(skillRoot, '
|
|
2052
|
+
assert.equal(fs.existsSync(path.join(skillRoot, 'inspect')), false);
|
|
2053
|
+
assert.equal(fs.existsSync(path.join(skillRoot, 'question')), false);
|
|
2302
2054
|
assert.ok(fs.existsSync(path.join(root, RUNTIMES[platform].root, 'agents')) || platform === 'codex');
|
|
2303
2055
|
}
|
|
2304
2056
|
|
|
@@ -2320,12 +2072,7 @@ test('packed core-only installs reject optional capability routing', () => {
|
|
|
2320
2072
|
const tarball = path.join(destination, packed.filename);
|
|
2321
2073
|
const inventory = packedInventory(tarball);
|
|
2322
2074
|
assertCleanInventory(inventory);
|
|
2323
|
-
|
|
2324
|
-
'src/claude/skills/route/SKILL.md',
|
|
2325
|
-
'src/claude/skills/route/references/task-taxonomy.md',
|
|
2326
|
-
'src/claude/skills/route/references/chaining-patterns.md',
|
|
2327
|
-
'src/claude/skills/route/references/agent-timing.md',
|
|
2328
|
-
]) assert.ok(inventory.includes(required), required);
|
|
2075
|
+
assert.equal(inventory.some((entry) => entry.startsWith('src/claude/skills/route/')), false, 'Route is not packed');
|
|
2329
2076
|
|
|
2330
2077
|
const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
|
|
2331
2078
|
const project = path.join(root, 'project');
|
|
@@ -2339,9 +2086,10 @@ test('packed core-only installs reject optional capability routing', () => {
|
|
|
2339
2086
|
});
|
|
2340
2087
|
assert.equal(catalogResult.status, 0, catalogResult.stderr);
|
|
2341
2088
|
const catalog = JSON.parse(catalogResult.stdout);
|
|
2342
|
-
assert.
|
|
2089
|
+
assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:route'), false);
|
|
2090
|
+
assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route')), false);
|
|
2343
2091
|
assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:docs'), false);
|
|
2344
|
-
assert.deepEqual(
|
|
2092
|
+
assert.deepEqual(routingRuleIssues(installedRoutingRuleFiles(project, platform)), []);
|
|
2345
2093
|
}
|
|
2346
2094
|
const domain = fs.readFileSync(path.join(project, '.claude/rules/skill-domain-routing.md'), 'utf8');
|
|
2347
2095
|
assert.doesNotMatch(domain, /\/cf:(?:docs|docx|pdf|pptx|xlsx|ai-multimodal)/);
|
|
@@ -2350,7 +2098,7 @@ test('packed core-only installs reject optional capability routing', () => {
|
|
|
2350
2098
|
}
|
|
2351
2099
|
});
|
|
2352
2100
|
|
|
2353
|
-
test('packed
|
|
2101
|
+
test('packed routing rules reject semantic routing weakenings', () => {
|
|
2354
2102
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-route-mutations-'));
|
|
2355
2103
|
const destination = path.join(root, 'pack');
|
|
2356
2104
|
fs.mkdirSync(destination, { recursive: true });
|
|
@@ -2362,20 +2110,10 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2362
2110
|
const project = path.join(root, platform);
|
|
2363
2111
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2364
2112
|
runInstaller(installer, project, [platform], null);
|
|
2365
|
-
const files =
|
|
2366
|
-
assert.deepEqual(
|
|
2113
|
+
const files = installedRoutingRuleFiles(project, platform);
|
|
2114
|
+
assert.deepEqual(routingRuleIssues(files), []);
|
|
2367
2115
|
const mutations = [
|
|
2368
|
-
['
|
|
2369
|
-
['skill', 'classification', 'highest-link', 'average-link'],
|
|
2370
|
-
['chaining', 'link-contract', '**Owner:** exactly one', '**Owner:** several possible'],
|
|
2371
|
-
['chaining', 'collapse-and-detour', 'preserve its valid exit evidence, normalize the root cause,\nand choose one bounded detour', 'discard its valid exit evidence and repeat the unchanged action'],
|
|
2372
|
-
['chaining', 'failure-stop', 'Two failures with the same failure key stop the chain', 'The chain retries the same key forever'],
|
|
2373
|
-
['timing', 'agent-fallback', 'never synthesize a role', 'synthesize a missing role'],
|
|
2374
|
-
['timing', 'delegation-contract', '**Outcome** — observable result', '**Topic** — broad area'],
|
|
2375
|
-
['skill', 'authority', 'never expand it', 'may expand it'],
|
|
2376
|
-
['skill', 'risk-gates', 'require independent review and user confirmation', 'skip independent review and confirmation'],
|
|
2377
|
-
['skill', 'proof-boundary', 'remains `[UNPROVEN]`', 'is `[VERIFIED]`'],
|
|
2378
|
-
['workflow', 'rule-direct-path', 'do not invoke Route or agents for ceremony', 'always invoke Route for explicit installed skills, obvious low-risk intents, and factual conversation'],
|
|
2116
|
+
['workflow', 'rule-direct-path', 'do not spawn agents for ceremony', 'always invoke Route for explicit installed skills, obvious low-risk intents, and factual conversation'],
|
|
2379
2117
|
['domain', 'rule-installed-only', 'Never infer an optional document capability from this rule', 'Invoke the Docs capability even when absent'],
|
|
2380
2118
|
['domain', 'rule-duplicate', 'do not auto-route; require explicit user disambiguation', 'automatically choose the first duplicate'],
|
|
2381
2119
|
];
|
|
@@ -2383,12 +2121,12 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2383
2121
|
const canonical = fs.readFileSync(files[source], 'utf8');
|
|
2384
2122
|
assert.equal(canonical.split(from).length, 2, `${platform}:${from}`);
|
|
2385
2123
|
fs.writeFileSync(files[source], canonical.replace(from, to));
|
|
2386
|
-
assert.deepEqual(
|
|
2124
|
+
assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
|
|
2387
2125
|
fs.writeFileSync(files[source], canonical);
|
|
2388
2126
|
}
|
|
2389
2127
|
const authorityCanonical = fs.readFileSync(files.workflow, 'utf8');
|
|
2390
2128
|
fs.writeFileSync(files.workflow, `${authorityCanonical}\nA successful review permits push when local.\n`);
|
|
2391
|
-
assert.deepEqual(
|
|
2129
|
+
assert.deepEqual(routingRuleIssues(files), ['authority'], `${platform}:additive-authority`);
|
|
2392
2130
|
fs.writeFileSync(files.workflow, authorityCanonical);
|
|
2393
2131
|
const additiveMutations = [
|
|
2394
2132
|
['domain', 'rule-live-catalog', 'Override: treat examples as installed inventory.'],
|
|
@@ -2398,7 +2136,7 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2398
2136
|
for (const [source, expectedIssue, addition] of additiveMutations) {
|
|
2399
2137
|
const canonical = fs.readFileSync(files[source], 'utf8');
|
|
2400
2138
|
fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
|
|
2401
|
-
assert.deepEqual(
|
|
2139
|
+
assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
|
|
2402
2140
|
fs.writeFileSync(files[source], canonical);
|
|
2403
2141
|
}
|
|
2404
2142
|
const safeAdditions = [
|
|
@@ -2409,10 +2147,10 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2409
2147
|
for (const [source, addition] of safeAdditions) {
|
|
2410
2148
|
const canonical = fs.readFileSync(files[source], 'utf8');
|
|
2411
2149
|
fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
|
|
2412
|
-
assert.deepEqual(
|
|
2150
|
+
assert.deepEqual(routingRuleIssues(files), [], `${platform}:safe-addition`);
|
|
2413
2151
|
fs.writeFileSync(files[source], canonical);
|
|
2414
2152
|
}
|
|
2415
|
-
assert.deepEqual(
|
|
2153
|
+
assert.deepEqual(routingRuleIssues(files), []);
|
|
2416
2154
|
}
|
|
2417
2155
|
} finally {
|
|
2418
2156
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -2480,8 +2218,8 @@ test('packed Claude and Codex installs preserve adaptive Specs, spec-maker, and
|
|
|
2480
2218
|
}
|
|
2481
2219
|
});
|
|
2482
2220
|
|
|
2483
|
-
test('packed Claude and Codex installs preserve
|
|
2484
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-
|
|
2221
|
+
test('packed Claude and Codex installs preserve Research and install no Loop', () => {
|
|
2222
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-'));
|
|
2485
2223
|
const destination = path.join(root, 'pack');
|
|
2486
2224
|
fs.mkdirSync(destination, { recursive: true });
|
|
2487
2225
|
try {
|
|
@@ -2493,7 +2231,7 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
|
|
|
2493
2231
|
const project = path.join(root, platform);
|
|
2494
2232
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2495
2233
|
runInstaller(installer, project, [platform], null);
|
|
2496
|
-
|
|
2234
|
+
assertPackedResearchParity(project, platform);
|
|
2497
2235
|
assert.equal(fs.existsSync(path.join(
|
|
2498
2236
|
project, platform === 'codex' ? '.agents/skills/autoresearch' : '.claude/skills/autoresearch'
|
|
2499
2237
|
)), false);
|
|
@@ -2503,26 +2241,17 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
|
|
|
2503
2241
|
}
|
|
2504
2242
|
});
|
|
2505
2243
|
|
|
2506
|
-
test('packed Research
|
|
2507
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-
|
|
2244
|
+
test('packed Research rejects semantic weakenings', () => {
|
|
2245
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-mutations-'));
|
|
2508
2246
|
const destination = path.join(root, 'pack');
|
|
2509
2247
|
fs.mkdirSync(destination, { recursive: true });
|
|
2510
|
-
const canonicalBytes = new Map(Object.values(
|
|
2248
|
+
const canonicalBytes = new Map(Object.values(RESEARCH_SOURCE_RELATIVES).map((relative) => {
|
|
2511
2249
|
const sourcePath = path.join(PACKAGE_ROOT, relative);
|
|
2512
2250
|
return [sourcePath, fs.readFileSync(sourcePath)];
|
|
2513
2251
|
}));
|
|
2514
2252
|
const mutations = [
|
|
2515
2253
|
['research', 'research-adaptive-evidence', 'Use delegated researchers only as optional acceleration', 'Delegation is required for every research request'],
|
|
2516
2254
|
['research', 'research-adaptive-evidence', 'Default to a concise answer in chat.', 'Persist every answer before returning chat output.'],
|
|
2517
|
-
['loop', 'loop-safety', 'Loop is explicit-only.', 'Loop may be auto-routed.'],
|
|
2518
|
-
['loop', 'loop-safety', 'separately approved external', 'worktree-contained'],
|
|
2519
|
-
['loop', 'loop-safety', 'complete detached-worktree tracked/untracked manifest', 'scoped manifest'],
|
|
2520
|
-
['metric', 'metric-guard', 'Guard is mandatory,', 'Guard may be optional,'],
|
|
2521
|
-
['metric', 'metric-guard', '`numeric_format`: exactly IEEE-754 binary64', '`numeric_format`: implementation-defined'],
|
|
2522
|
-
['metric', 'metric-guard', 'nonzero exit', 'nonzero value'],
|
|
2523
|
-
['protocol', 'failure-handoff', 'do not clean. Preserve the exact path', 'clean the uncertain path and continue'],
|
|
2524
|
-
['protocol', 'failure-handoff', '`base_oid`;', '`optional_base`;'],
|
|
2525
|
-
['workflow', 'explicit-routing', 'must never be auto-routed', 'should be auto-routed when optimization seems useful'],
|
|
2526
2255
|
];
|
|
2527
2256
|
try {
|
|
2528
2257
|
const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
|
|
@@ -2532,8 +2261,8 @@ test('packed Research and Loop reject semantic weakenings', () => {
|
|
|
2532
2261
|
const project = path.join(root, platform);
|
|
2533
2262
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2534
2263
|
runInstaller(installer, project, [platform], null);
|
|
2535
|
-
|
|
2536
|
-
const paths =
|
|
2264
|
+
assertPackedResearchParity(project, platform);
|
|
2265
|
+
const paths = packedResearchPaths(project, platform);
|
|
2537
2266
|
for (const [source, expectedIssue, from, to] of mutations) {
|
|
2538
2267
|
const target = fs.realpathSync(paths[source]);
|
|
2539
2268
|
const relative = path.relative(fs.realpathSync(project), target);
|
|
@@ -2549,12 +2278,12 @@ test('packed Research and Loop reject semantic weakenings', () => {
|
|
|
2549
2278
|
assert.equal(content.indexOf(from, anchor + from.length), -1, `${platform}/${source} duplicate mutation anchor`);
|
|
2550
2279
|
try {
|
|
2551
2280
|
fs.writeFileSync(target, `${content.slice(0, anchor)}${to}${content.slice(anchor + from.length)}`);
|
|
2552
|
-
assert.deepEqual(
|
|
2281
|
+
assert.deepEqual(packedResearchIssues(project, platform), [expectedIssue]);
|
|
2553
2282
|
for (const [sourcePath, bytes] of canonicalBytes) assert.deepEqual(fs.readFileSync(sourcePath), bytes);
|
|
2554
2283
|
} finally {
|
|
2555
2284
|
fs.writeFileSync(target, original);
|
|
2556
2285
|
}
|
|
2557
|
-
assert.deepEqual(
|
|
2286
|
+
assert.deepEqual(packedResearchIssues(project, platform), []);
|
|
2558
2287
|
}
|
|
2559
2288
|
}
|
|
2560
2289
|
} finally {
|
|
@@ -2562,40 +2291,24 @@ test('packed Research and Loop reject semantic weakenings', () => {
|
|
|
2562
2291
|
}
|
|
2563
2292
|
});
|
|
2564
2293
|
|
|
2565
|
-
test('repository and package guides document adaptive Research and
|
|
2294
|
+
test('repository and package guides document adaptive Research and no Loop', () => {
|
|
2566
2295
|
const guides = {
|
|
2567
2296
|
repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
|
|
2568
2297
|
package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
|
|
2569
2298
|
};
|
|
2570
2299
|
for (const [name, guide] of Object.entries(guides)) {
|
|
2571
2300
|
assert.match(guide, /cf:research/, `${name} names Claude Research`);
|
|
2572
|
-
assert.match(guide, /cf:loop/, `${name} names Claude Loop`);
|
|
2573
2301
|
assert.match(guide, /\$cf-research/, `${name} names Codex Research`);
|
|
2574
|
-
assert.match(guide, /\$cf-loop/, `${name} names Codex Loop`);
|
|
2575
2302
|
assert.match(guide, /Quick, Standard, or Deep/, `${name} documents adaptive Research depth`);
|
|
2576
|
-
assert.
|
|
2577
|
-
for (const field of ['Goal', 'Scope', 'Metric', 'Direction', 'Baseline', 'Guard', 'minimum delta', 'budget']) {
|
|
2578
|
-
assert.match(guide, new RegExp(field, 'i'), `${name} documents Loop field ${field}`);
|
|
2579
|
-
}
|
|
2580
|
-
assert.match(guide, /stop\s+conditions/i, `${name} documents Loop stop conditions`);
|
|
2581
|
-
assert.match(guide, /detached worktree/, `${name} documents Loop isolation`);
|
|
2582
|
-
assert.match(guide, /base-bound (?:isolated )?patch handoff/, `${name} documents Loop handoff`);
|
|
2583
|
-
assert.match(guide, /does not .{0,80}guarantee|never .{0,80}guarantee/is, `${name} rejects guarantees`);
|
|
2303
|
+
assert.doesNotMatch(guide, /cf[:-]loop/, `${name} does not advertise Loop`);
|
|
2584
2304
|
assert.doesNotMatch(guide, /cf[:-]autoresearch/, `${name} does not advertise Autoresearch`);
|
|
2585
2305
|
}
|
|
2586
2306
|
|
|
2587
2307
|
const catalog = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/catalog-visuals.tsx'), 'utf8');
|
|
2588
2308
|
const overview = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/skill-overview.tsx'), 'utf8');
|
|
2589
|
-
assert.match(catalog, /\['Bounded optimization', \['loop'\]\]/);
|
|
2590
2309
|
assert.match(catalog, /proportional, traceable evidence/);
|
|
2591
2310
|
assert.match(overview, /\['cf:research'/);
|
|
2592
|
-
assert.
|
|
2593
|
-
for (const field of ['Goal', 'Scope', 'Metric', 'Direction', 'Baseline', 'Guard', 'noise policy', 'minimum delta', 'budget']) {
|
|
2594
|
-
assert.match(overview, new RegExp(field, 'i'), `website documents Loop field ${field}`);
|
|
2595
|
-
}
|
|
2596
|
-
assert.match(overview, /stop conditions/);
|
|
2597
|
-
assert.match(overview, /base-bound isolated patch handoff/);
|
|
2598
|
-
assert.match(overview, /without guaranteed improvement/);
|
|
2311
|
+
assert.doesNotMatch(`${catalog}\n${overview}`, /cf:loop|'loop'/);
|
|
2599
2312
|
assert.doesNotMatch(`${catalog}\n${overview}`, /autoresearch/i);
|
|
2600
2313
|
});
|
|
2601
2314
|
|
|
@@ -2623,11 +2336,13 @@ test('packed Claude and Codex reject adaptive Brainstorm semantic weakenings', (
|
|
|
2623
2336
|
const project = path.join(root, platform);
|
|
2624
2337
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2625
2338
|
runInstaller(installer, project, [platform], null);
|
|
2626
|
-
const
|
|
2627
|
-
? path.join(project, '.claude/skills
|
|
2628
|
-
: path.join(project, '.agents/skills
|
|
2629
|
-
assert.equal(fs.existsSync(
|
|
2630
|
-
`${platform}
|
|
2339
|
+
const skillsRoot = platform === 'claude'
|
|
2340
|
+
? path.join(project, '.claude/skills')
|
|
2341
|
+
: path.join(project, '.agents/skills');
|
|
2342
|
+
assert.equal(fs.existsSync(path.join(skillsRoot, 'fix', 'SKILL.md')), true,
|
|
2343
|
+
`${platform} installs Fix from the fix directory`);
|
|
2344
|
+
assert.equal(fs.existsSync(path.join(skillsRoot, 'hotfix')), false,
|
|
2345
|
+
`${platform} no longer installs the retired hotfix directory`);
|
|
2631
2346
|
assertPackedBrainstormParity(project, platform);
|
|
2632
2347
|
for (const entry of assertPackedAdaptiveBrainstormMutations(project, platform, canonicalBytes)) exercised.add(entry);
|
|
2633
2348
|
assertPackedBrainstormParity(project, platform);
|
|
@@ -2723,7 +2438,7 @@ function packedHotfixIssues(files, refPrefix) {
|
|
|
2723
2438
|
}
|
|
2724
2439
|
if (!parallel.includes('Diagnosis still starts only')
|
|
2725
2440
|
|| !parallel.includes('after the required scout outputs are synthesized')
|
|
2726
|
-
|| !parallel.includes('Research begins only after
|
|
2441
|
+
|| !parallel.includes('Research begins only after diagnosis')
|
|
2727
2442
|
|| parallel.includes('scout + diagnose + research together')
|
|
2728
2443
|
|| parallel.includes("You don't need to wait for scouting")) {
|
|
2729
2444
|
issues.add('scout-before-diagnosis');
|
|
@@ -2745,8 +2460,8 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
|
|
|
2745
2460
|
const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
|
|
2746
2461
|
assertCleanInventory(packedInventory(tarball));
|
|
2747
2462
|
const layouts = {
|
|
2748
|
-
claude: { skillsRoot: '.claude/skills/
|
|
2749
|
-
codex: { skillsRoot: '.agents/skills/
|
|
2463
|
+
claude: { skillsRoot: '.claude/skills/fix', refPrefix: 'cf:' },
|
|
2464
|
+
codex: { skillsRoot: '.agents/skills/fix', refPrefix: 'cf-' },
|
|
2750
2465
|
};
|
|
2751
2466
|
const exercised = new Set();
|
|
2752
2467
|
for (const [platform, layout] of Object.entries(layouts)) {
|
|
@@ -2773,7 +2488,7 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
|
|
|
2773
2488
|
{ group: 'deep-decision-route', file: 'SKILL.md', from: 'after diagnosis, research only unresolved external facts', to: 'research broadly before diagnosis' },
|
|
2774
2489
|
{ group: 'specialized-proof-overlays', file: 'references/workflow-specialized.md', from: 'Load only the matching section', to: 'Load every section' },
|
|
2775
2490
|
{ group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Diagnosis still starts only', to: 'Diagnosis may start' },
|
|
2776
|
-
{ group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Research begins only after
|
|
2491
|
+
{ group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Research begins only after diagnosis', to: 'Research may begin before diagnosis' },
|
|
2777
2492
|
];
|
|
2778
2493
|
for (const mutation of mutations) {
|
|
2779
2494
|
const target = path.join(project, layout.skillsRoot, mutation.file);
|
|
@@ -2849,8 +2564,7 @@ test('website keeps process-first docs, canonical public names, and historical l
|
|
|
2849
2564
|
const relative = path.relative(docsRoot, file).split(path.sep).join('/');
|
|
2850
2565
|
return file.endsWith('.mdx')
|
|
2851
2566
|
&& !relative.endsWith('/reference.mdx')
|
|
2852
|
-
&& !relative.endsWith('/platforms/opencode.mdx')
|
|
2853
|
-
&& !relative.endsWith('/spec-lifecycle.mdx');
|
|
2567
|
+
&& !relative.endsWith('/platforms/opencode.mdx');
|
|
2854
2568
|
});
|
|
2855
2569
|
const currentSources = walk(sourceRoot).filter((file) => /\.(?:ts|tsx)$/.test(file));
|
|
2856
2570
|
const currentCorpus = [...currentDocs, ...currentSources]
|
|
@@ -2898,10 +2612,7 @@ test('website keeps process-first docs, canonical public names, and historical l
|
|
|
2898
2612
|
|
|
2899
2613
|
for (const locale of ['en', 'vi', 'ja']) {
|
|
2900
2614
|
const lifecycle = fs.readFileSync(path.join(docsRoot, locale, 'spec-lifecycle.mdx'), 'utf8');
|
|
2901
|
-
|
|
2902
|
-
assert.ok(legacyIndex >= 0, `${locale} spec lifecycle must isolate legacy compatibility`);
|
|
2903
|
-
assert.doesNotMatch(lifecycle.slice(0, legacyIndex), /\bspec\.json\b|\btask_registry\b|tasks\/task-R/);
|
|
2904
|
-
assert.match(lifecycle.slice(legacyIndex), /\bspec\.json\b/);
|
|
2615
|
+
assert.doesNotMatch(lifecycle, /^## Legacy compatibility$/m, `${locale} spec lifecycle must not promise a legacy adapter`);
|
|
2905
2616
|
|
|
2906
2617
|
const opencode = fs.readFileSync(path.join(docsRoot, locale, 'platforms/opencode.mdx'), 'utf8');
|
|
2907
2618
|
assert.match(opencode, /0\.17/);
|
|
@@ -3055,12 +2766,10 @@ test('packed Claude and Codex installs execute semantic kernel behavior without
|
|
|
3055
2766
|
const paths = installedSemanticPaths(project, platform);
|
|
3056
2767
|
const taskless = createTasklessFixture(paths, project, 'compact-installed');
|
|
3057
2768
|
initializeGit(project);
|
|
3058
|
-
assertInstalledCompletion(paths, project, platform, taskless);
|
|
3059
2769
|
|
|
3060
2770
|
const strict = createTasklessFixture(paths, project, 'strict-installed', { strict: true });
|
|
3061
2771
|
const taskBearing = createTaskFixture(paths, project, 'tasks-installed');
|
|
3062
|
-
|
|
3063
|
-
assertStrictSimulatedHandlerGuardrail(paths, project, strict);
|
|
2772
|
+
assertStrictRefusedAsUnsupported(paths, project, strict);
|
|
3064
2773
|
assertStaleDigestMutations(paths, project, taskless, taskBearing);
|
|
3065
2774
|
runInstalled(paths.validator, [taskless.featureDir], project);
|
|
3066
2775
|
runInstalled(paths.validator, [taskBearing.featureDir], project);
|
|
@@ -3097,153 +2806,3 @@ test('packed Claude and Codex installs self-contain runtime provenance and fail
|
|
|
3097
2806
|
fs.rmSync(root, { recursive: true, force: true });
|
|
3098
2807
|
}
|
|
3099
2808
|
});
|
|
3100
|
-
|
|
3101
|
-
test('packed Codex live host E2E via codex binary (opt-in)', async (t) => {
|
|
3102
|
-
if (process.env.CAFEKIT_CODEX_HOST_E2E !== '1') {
|
|
3103
|
-
t.skip('opt-in only: set CAFEKIT_CODEX_HOST_E2E=1 to run live Codex host');
|
|
3104
|
-
return;
|
|
3105
|
-
}
|
|
3106
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-live-codex-'));
|
|
3107
|
-
const destination = path.join(root, 'pack');
|
|
3108
|
-
fs.mkdirSync(destination, { recursive: true });
|
|
3109
|
-
const cafekitHome = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-live-home-'));
|
|
3110
|
-
const originalHome = process.env.HOME;
|
|
3111
|
-
const originalUserProfile = process.env.USERPROFILE;
|
|
3112
|
-
const originalCodexHome = process.env.CODEX_HOME;
|
|
3113
|
-
// Preserve original home for Codex auth (without inspecting contents)
|
|
3114
|
-
const authHome = originalCodexHome || (originalHome ? path.join(originalHome, '.codex') : null);
|
|
3115
|
-
let project;
|
|
3116
|
-
try {
|
|
3117
|
-
const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
|
|
3118
|
-
const tarball = path.join(destination, packed.filename);
|
|
3119
|
-
const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
|
|
3120
|
-
project = path.join(root, 'codex-live');
|
|
3121
|
-
const installer = installPacked(tarball, project, runtimeClosure);
|
|
3122
|
-
runInstaller(installer, project, ['codex'], null);
|
|
3123
|
-
const paths = installedSemanticPaths(project, 'codex');
|
|
3124
|
-
const fixture = createTasklessFixture(paths, project, 'live-strict', { strict: true });
|
|
3125
|
-
initializeGit(project);
|
|
3126
|
-
// Verify via child process to keep CafeKit state isolated without mutating global env
|
|
3127
|
-
const verifyViaChild = (digest) => {
|
|
3128
|
-
const script = 'const a=require(process.argv[1]); console.log(JSON.stringify(a.verifyAttestation(process.argv[2],process.argv[3],process.argv[4],process.argv[5])))';
|
|
3129
|
-
const res = spawnSync(process.execPath, ['-e', script, paths.semanticAuthority, project, path.join(project, 'specs', 'live-strict', 'spec.json'), 'live-strict', digest], {
|
|
3130
|
-
encoding: 'utf8',
|
|
3131
|
-
env: { ...process.env, HOME: cafekitHome, USERPROFILE: cafekitHome },
|
|
3132
|
-
});
|
|
3133
|
-
assert.equal(res.status, 0, `verify child failed: ${res.stderr}`);
|
|
3134
|
-
return JSON.parse(res.stdout);
|
|
3135
|
-
};
|
|
3136
|
-
const before = verifyViaChild(fixture.digest);
|
|
3137
|
-
assert.equal(before.ok, false, 'before host observation, Strict attestation must be missing (fail-closed)');
|
|
3138
|
-
|
|
3139
|
-
const model = process.env.CAFEKIT_CODEX_MODEL || 'gpt-5.6-luna';
|
|
3140
|
-
const reasoning = process.env.CAFEKIT_CODEX_REASONING || 'max';
|
|
3141
|
-
const codexBin = process.env.CAFEKIT_CODEX_BIN || 'codex';
|
|
3142
|
-
const canonicalProject = fs.realpathSync(project);
|
|
3143
|
-
const codeAuditorConfig = path.join(canonicalProject, '.codex', 'agents', 'code_auditor.toml');
|
|
3144
|
-
const semanticAuthorityHook = fs.realpathSync(paths.semanticAuthority);
|
|
3145
|
-
const shellQuote = (value) => `'${String(value).replaceAll("'", `'\\''`)}'`;
|
|
3146
|
-
const posixHookCommand = `${shellQuote(process.execPath)} ${shellQuote(semanticAuthorityHook)}`;
|
|
3147
|
-
const encodedHookPath = Buffer.from(semanticAuthorityHook, 'utf8').toString('base64url');
|
|
3148
|
-
const windowsHookCommand = 'node -e "process.argv[1]=Buffer.from(process.argv[1],\'base64url\').toString(\'utf8\');require(\'module\').runMain()" ' + encodedHookPath;
|
|
3149
|
-
const subagentStopHooks = `[{ matcher = "*", hooks = [{ type = "command", command = ${JSON.stringify(posixHookCommand)}, commandWindows = ${JSON.stringify(windowsHookCommand)} }] }]`;
|
|
3150
|
-
assert.equal(fs.existsSync(codeAuditorConfig), true, 'packed install must contain code_auditor config');
|
|
3151
|
-
const versionCheck = spawnSync(codexBin, ['--version'], { encoding: 'utf8' });
|
|
3152
|
-
if (versionCheck.status !== 0) {
|
|
3153
|
-
assert.fail(`Codex binary not available at ${codexBin}: ${versionCheck.stderr.trim()} (exit ${versionCheck.status})`);
|
|
3154
|
-
}
|
|
3155
|
-
const controllerPrompt = [
|
|
3156
|
-
'You are a CafeKit controller. Do NOT emit CAFEKIT_SEMANTIC_REVIEW_ATTESTATION yourself.',
|
|
3157
|
-
`Spawn exactly one subagent with agent_type="code_auditor" to perform Strict semantic review for feature live-strict.`,
|
|
3158
|
-
'Wait for that exact subagent to finish by calling wait_agent before you answer.',
|
|
3159
|
-
'This is a specs-only authoring review: assess requirements, design, semantic model, and counterexample coverage only. Do not require implementation code, execution receipts, or a Develop run.',
|
|
3160
|
-
'The request has assurance_level: Strict. Keep the review bounded to this fixture and do not invoke a skill.',
|
|
3161
|
-
'This is intentionally a pre-finalization candidate: semantic_model is null and semantic_review is not-run until a PASS marker is observed and spec-readiness atomically promotes them. Do not run or require the full validator before that host event because it must fail closed at this stage.',
|
|
3162
|
-
`The subagent must recompute the digest via: node .codex/scripts/validate-spec-output.cjs specs/live-strict --semantic-digest`,
|
|
3163
|
-
`The exact review result that will be passed to the finalizer is ${JSON.stringify(fixture.reviewResult)}. Verify every criterion, counterexample, decision ref, verification ref, and artifact statement before PASS.`,
|
|
3164
|
-
`If the subagent verifies PASS and the digest matches ${fixture.digest}, it must emit exactly:`,
|
|
3165
|
-
`CAFEKIT_SEMANTIC_REVIEW_ATTESTATION {"feature_name":"live-strict","spec_file":"specs/live-strict/spec.json","semantic_digest":"${fixture.digest}","verdict":"PASS"}`,
|
|
3166
|
-
'No other marker. After the subagent, do not add any marker.',
|
|
3167
|
-
].join(' ');
|
|
3168
|
-
|
|
3169
|
-
const execArgs = [
|
|
3170
|
-
'exec',
|
|
3171
|
-
'-m', model,
|
|
3172
|
-
'-c', `model_reasoning_effort=${reasoning}`,
|
|
3173
|
-
// Project agents are intentionally skipped until a user trusts the repo.
|
|
3174
|
-
// This disposable E2E must not mutate global trust, so register the
|
|
3175
|
-
// packed profile explicitly for this process. Static packed tests cover
|
|
3176
|
-
// the normal trusted-project auto-discovery path and file schema.
|
|
3177
|
-
'-c', 'agents.code_auditor.description="CafeKit Strict semantic reviewer"',
|
|
3178
|
-
'-c', `agents.code_auditor.config_file=${JSON.stringify(codeAuditorConfig)}`,
|
|
3179
|
-
// Isolate this E2E from the user's config/hooks while retaining auth.
|
|
3180
|
-
// The process-scoped hook executes the packed entrypoint directly.
|
|
3181
|
-
'--ignore-user-config',
|
|
3182
|
-
// Only thread-spawned Codex subagents emit the supported SubagentStop
|
|
3183
|
-
// event. The legacy internal path emits spawn PostToolUse but no
|
|
3184
|
-
// hook-observable completion payload and must remain fail-closed.
|
|
3185
|
-
'--enable', 'multi_agent_v2',
|
|
3186
|
-
'-c', 'features.hooks=true',
|
|
3187
|
-
'-c', `hooks.SubagentStop=${subagentStopHooks}`,
|
|
3188
|
-
'-s', 'read-only',
|
|
3189
|
-
'--dangerously-bypass-hook-trust',
|
|
3190
|
-
'--ephemeral',
|
|
3191
|
-
'-C', canonicalProject,
|
|
3192
|
-
controllerPrompt,
|
|
3193
|
-
];
|
|
3194
|
-
const codexResult = spawnSync(codexBin, execArgs, {
|
|
3195
|
-
cwd: project,
|
|
3196
|
-
encoding: 'utf8',
|
|
3197
|
-
timeout: 600000,
|
|
3198
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
3199
|
-
env: { ...process.env, HOME: cafekitHome, USERPROFILE: cafekitHome, ...(authHome ? { CODEX_HOME: authHome } : {}) },
|
|
3200
|
-
});
|
|
3201
|
-
if (codexResult.status !== 0) {
|
|
3202
|
-
const cliOutput = `${codexResult.stderr.trim()}\n${codexResult.stdout.trim()}`.trim();
|
|
3203
|
-
const processState = [
|
|
3204
|
-
`status=${codexResult.status}`,
|
|
3205
|
-
`signal=${codexResult.signal || 'none'}`,
|
|
3206
|
-
codexResult.error ? `spawn_error=${codexResult.error.code || codexResult.error.message}` : null,
|
|
3207
|
-
].filter(Boolean).join(', ');
|
|
3208
|
-
const isEnvAuth = /authentication|unauthorized|credential|not logged in|login required|CODEX_HOME|not.*found|ENOENT/i.test(cliOutput);
|
|
3209
|
-
if (isEnvAuth) {
|
|
3210
|
-
assert.fail(`Live Codex host failed due to CLI/environment (${processState}): ${cliOutput.slice(0, 1600)}`);
|
|
3211
|
-
}
|
|
3212
|
-
assert.fail(`Live Codex host exec failed (${processState}): ${cliOutput.slice(0, 1600)}`);
|
|
3213
|
-
}
|
|
3214
|
-
// Verify via public authority API only (no HOME inspection) via child process
|
|
3215
|
-
const after = verifyViaChild(fixture.digest);
|
|
3216
|
-
const liveTrace = `${codexResult.stderr || ''}\n${codexResult.stdout || ''}`.trim();
|
|
3217
|
-
assert.equal(
|
|
3218
|
-
after.ok,
|
|
3219
|
-
true,
|
|
3220
|
-
`host observation must exist after Codex reviewer completion event (got ${after.reason})\n` +
|
|
3221
|
-
`Codex trace tail:\n${liveTrace.slice(-6000)}`,
|
|
3222
|
-
);
|
|
3223
|
-
assert.equal(after.record.reviewer_agent_type, 'code_auditor', 'observation must be from code_auditor');
|
|
3224
|
-
const reviewFile = path.join(project, 'specs', 'live-strict', '.live-review.json');
|
|
3225
|
-
fs.writeFileSync(reviewFile, JSON.stringify(fixture.reviewResult));
|
|
3226
|
-
const readinessResult = spawnSync(process.execPath, [paths.readiness, path.join(project, 'specs', 'live-strict'), '--review-result', reviewFile], {
|
|
3227
|
-
cwd: project,
|
|
3228
|
-
encoding: 'utf8',
|
|
3229
|
-
env: { ...process.env, HOME: cafekitHome },
|
|
3230
|
-
});
|
|
3231
|
-
fs.unlinkSync(reviewFile);
|
|
3232
|
-
assert.equal(readinessResult.status, 0, `readiness should succeed after host observation: ${readinessResult.stderr.trim()}`);
|
|
3233
|
-
assert.match(readinessResult.stdout, /SPEC_READY/);
|
|
3234
|
-
const specAfter = JSON.parse(fs.readFileSync(path.join(project, 'specs', 'live-strict', 'spec.json'), 'utf8'));
|
|
3235
|
-
assert.equal(specAfter.ready_for_implementation, true, 'SPEC_READY after live host observation');
|
|
3236
|
-
} finally {
|
|
3237
|
-
// Restore exact environment on all paths
|
|
3238
|
-
if (originalHome !== undefined) process.env.HOME = originalHome; else delete process.env.HOME;
|
|
3239
|
-
if (originalUserProfile !== undefined) process.env.USERPROFILE = originalUserProfile; else delete process.env.USERPROFILE;
|
|
3240
|
-
if (originalCodexHome !== undefined) process.env.CODEX_HOME = originalCodexHome; else delete process.env.CODEX_HOME;
|
|
3241
|
-
// Clear require cache for authority to avoid polluting other tests
|
|
3242
|
-
try {
|
|
3243
|
-
const tmpPaths = path.join(root || os.tmpdir(), 'codex-live', '.codex', 'hooks', 'semantic-review-authority.cjs');
|
|
3244
|
-
delete require.cache[require.resolve(tmpPaths)];
|
|
3245
|
-
} catch {}
|
|
3246
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
3247
|
-
fs.rmSync(cafekitHome, { recursive: true, force: true });
|
|
3248
|
-
}
|
|
3249
|
-
});
|