@haposoft/cafekit 0.16.7 → 0.18.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/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +169 -272
- package/bin/__tests__/codex-native.test.js +100 -202
- package/bin/__tests__/develop-contract.test.js +122 -1413
- 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 +154 -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 +90 -925
- 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 +30 -161
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- 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 +18 -2949
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +27 -0
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +25 -0
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +44 -56
- package/src/claude/agents/debugger.md +8 -88
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +6 -14
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +12 -16
- package/src/claude/agents/inspector.md +4 -3
- package/src/claude/agents/project-manager.md +9 -17
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +15 -15
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -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/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 -83
- package/src/claude/hooks/spec-state.cjs +142 -39
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +42 -22
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +2 -3
- 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 +11 -19
- 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 -121
- package/src/claude/scripts/spec-resolver.cjs +41 -458
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +29 -81
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +26 -83
- 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/develop/references/subagent-patterns.md +0 -6
- 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 +17 -46
- 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 +16 -45
- 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 +39 -104
- package/src/claude/skills/test/references/execution-strategy.md +93 -21
- 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 +25 -80
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- 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 +4 -32
- package/src/codex/hooks/lib/spec-utils.cjs +6 -52
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +13 -81
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +6 -12
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- 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/lib/context.cjs +0 -616
- package/src/claude/hooks/lib/detect.cjs +0 -474
- 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/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3941
- package/src/claude/skills/code-review/references/verification-gate.md +0 -46
- 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/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- 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
|
};
|
|
@@ -114,31 +114,17 @@ const REQUIRED_PAYLOAD = [
|
|
|
114
114
|
'src/claude/scripts/workflow-policy.cjs',
|
|
115
115
|
'src/claude/scripts/provenance.cjs',
|
|
116
116
|
'src/claude/scripts/spec-receipt.cjs',
|
|
117
|
-
'src/claude/scripts/spec-ground.cjs',
|
|
118
|
-
'src/claude/scripts/spec-final-state.cjs',
|
|
119
|
-
'src/claude/scripts/spec-readiness.cjs',
|
|
120
|
-
'src/claude/scripts/spec-semantic-model.cjs',
|
|
121
|
-
'src/claude/scripts/validate-spec-output.cjs',
|
|
122
|
-
'src/claude/scripts/spec-authoring-validation.cjs',
|
|
123
|
-
'src/claude/scripts/spec-authoring-digest.cjs',
|
|
124
117
|
'src/claude/scripts/generate-skill-catalog.cjs',
|
|
125
118
|
'src/claude/rules/process-management.md',
|
|
126
119
|
'src/claude/rules/review-audit-self-decision.md',
|
|
127
120
|
'src/claude/rules/skill-domain-routing.md',
|
|
128
121
|
'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
122
|
'src/claude/skills/orca/SKILL.md',
|
|
134
123
|
'src/claude/skills/brainstorm/SKILL.md',
|
|
135
124
|
'src/claude/skills/brainstorm/references/question-framework.md',
|
|
136
125
|
'src/claude/agents/brainstormer.md',
|
|
137
126
|
'src/claude/skills/research/SKILL.md',
|
|
138
127
|
'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
128
|
'src/claude/skills/docs/SKILL.md',
|
|
143
129
|
'src/claude/skills/docx/SKILL.md',
|
|
144
130
|
'src/claude/skills/pdf/SKILL.md',
|
|
@@ -179,20 +165,17 @@ const BRAINSTORM_SOURCE_RELATIVES = {
|
|
|
179
165
|
framework: 'src/claude/skills/brainstorm/references/question-framework.md',
|
|
180
166
|
agent: 'src/claude/agents/brainstormer.md',
|
|
181
167
|
};
|
|
182
|
-
const
|
|
168
|
+
const RESEARCH_SOURCE_RELATIVES = {
|
|
183
169
|
research: 'src/claude/skills/research/SKILL.md',
|
|
184
170
|
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
171
|
workflow: 'src/claude/rules/skill-workflow-routing.md',
|
|
189
172
|
domain: 'src/claude/rules/skill-domain-routing.md',
|
|
190
173
|
};
|
|
191
174
|
const HOTFIX_SOURCE_RELATIVES = {
|
|
192
|
-
skill: 'src/claude/skills/
|
|
193
|
-
review: 'src/claude/skills/
|
|
194
|
-
parallel: 'src/claude/skills/
|
|
195
|
-
specialized: 'src/claude/skills/
|
|
175
|
+
skill: 'src/claude/skills/fix/SKILL.md',
|
|
176
|
+
review: 'src/claude/skills/fix/references/review-cycle.md',
|
|
177
|
+
parallel: 'src/claude/skills/fix/references/parallel-patterns.md',
|
|
178
|
+
specialized: 'src/claude/skills/fix/references/workflow-specialized.md',
|
|
196
179
|
};
|
|
197
180
|
const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
|
|
198
181
|
'adviser-gate-fallback',
|
|
@@ -208,7 +191,7 @@ const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
|
|
|
208
191
|
];
|
|
209
192
|
const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
|
|
210
193
|
{ group: 'direct-precedence', mutations: [
|
|
211
|
-
{ from: 'Route Direct first, then
|
|
194
|
+
{ 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
195
|
] },
|
|
213
196
|
{ group: 'ordered-depth', mutations: [
|
|
214
197
|
{ 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 +199,32 @@ const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
|
|
|
216
199
|
{ group: 'leading-flags', mutations: [
|
|
217
200
|
{ 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
201
|
{ 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
|
|
202
|
+
{ from: '`--` ends the control segment.', to: '`--` is treated as another control.', clause: '`--` ends the control segment.' },
|
|
220
203
|
{ 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
204
|
] },
|
|
222
205
|
{ group: 'lens-trigger-skip', mutations: [
|
|
223
|
-
{ from: 'failure isolation for partial or cascading failure
|
|
206
|
+
{ 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
207
|
] },
|
|
225
208
|
{ group: 'evidence-semantics', mutations: [
|
|
226
|
-
{ from: 'Missing evidence
|
|
209
|
+
{ 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
210
|
] },
|
|
228
211
|
{ group: 'numeric-estimates', mutations: [
|
|
229
|
-
{ from: 'A numeric estimate requires
|
|
212
|
+
{ 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
213
|
] },
|
|
231
214
|
{ 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
|
|
215
|
+
{ 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.' },
|
|
216
|
+
{ 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.' },
|
|
217
|
+
{ 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
218
|
] },
|
|
236
219
|
{ group: 'adviser-gate-fallback', mutations: [
|
|
237
|
-
{ from: '`--advice` invokes
|
|
238
|
-
{ from: 'if advice is unavailable or
|
|
220
|
+
{ 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;' },
|
|
221
|
+
{ 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
222
|
] },
|
|
240
223
|
{ group: 'decision-brief', mutations: [
|
|
241
|
-
{ from: 'The first section records target
|
|
224
|
+
{ 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
225
|
] },
|
|
243
226
|
{ group: 'no-persistence-dispatch', mutations: [
|
|
244
|
-
{ from: 'Neither overlay
|
|
227
|
+
{ 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
228
|
] },
|
|
246
229
|
];
|
|
247
230
|
|
|
@@ -342,20 +325,15 @@ function runInstaller(installer, root, platforms, lang, extraArgs = []) {
|
|
|
342
325
|
return result;
|
|
343
326
|
}
|
|
344
327
|
|
|
345
|
-
function
|
|
328
|
+
function installedRoutingRuleFiles(project, platform) {
|
|
346
329
|
const runtimeRoot = platform === 'codex' ? '.codex' : '.claude';
|
|
347
|
-
const root = path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route');
|
|
348
330
|
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
331
|
workflow: path.join(project, runtimeRoot, 'rules/skill-workflow-routing.md'),
|
|
354
332
|
domain: path.join(project, runtimeRoot, 'rules/skill-domain-routing.md'),
|
|
355
333
|
};
|
|
356
334
|
}
|
|
357
335
|
|
|
358
|
-
function
|
|
336
|
+
function routingRuleIssues(files) {
|
|
359
337
|
const text = Object.fromEntries(Object.entries(files).map(([key, file]) => [
|
|
360
338
|
key, fs.readFileSync(file, 'utf8').replace(/\s+/g, ' '),
|
|
361
339
|
]));
|
|
@@ -363,56 +341,10 @@ function routeProjectionIssues(files) {
|
|
|
363
341
|
const requires = (issue, clauses) => {
|
|
364
342
|
if (clauses.some(([source, clause]) => !text[source].includes(clause))) issues.add(issue);
|
|
365
343
|
};
|
|
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
344
|
requires('rule-direct-path', [
|
|
413
345
|
['workflow', 'If the user names a valid installed skill, use it directly'],
|
|
414
346
|
['workflow', 'If one obvious low-risk installed skill covers the intent, use it directly'],
|
|
415
|
-
['workflow', 'do not
|
|
347
|
+
['workflow', 'do not spawn agents for ceremony'],
|
|
416
348
|
]);
|
|
417
349
|
requires('rule-live-catalog', [
|
|
418
350
|
['workflow', 'Resolve every abstract link against the current runtime catalog'],
|
|
@@ -774,30 +706,24 @@ function assertPackedBrainstormParity(project, platform) {
|
|
|
774
706
|
}
|
|
775
707
|
}
|
|
776
708
|
|
|
777
|
-
function
|
|
709
|
+
function packedResearchPaths(project, platform) {
|
|
778
710
|
return platform === 'codex'
|
|
779
711
|
? {
|
|
780
712
|
research: path.join(project, '.agents/skills/research/SKILL.md'),
|
|
781
713
|
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
714
|
workflow: path.join(project, '.codex/rules/skill-workflow-routing.md'),
|
|
786
715
|
domain: path.join(project, '.codex/rules/skill-domain-routing.md'),
|
|
787
716
|
}
|
|
788
717
|
: {
|
|
789
718
|
research: path.join(project, '.claude/skills/research/SKILL.md'),
|
|
790
719
|
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
720
|
workflow: path.join(project, '.claude/rules/skill-workflow-routing.md'),
|
|
795
721
|
domain: path.join(project, '.claude/rules/skill-domain-routing.md'),
|
|
796
722
|
};
|
|
797
723
|
}
|
|
798
724
|
|
|
799
|
-
function
|
|
800
|
-
const paths =
|
|
725
|
+
function readPackedResearch(project, platform) {
|
|
726
|
+
const paths = packedResearchPaths(project, platform);
|
|
801
727
|
const values = Object.fromEntries(Object.entries(paths).map(([key, target]) => [
|
|
802
728
|
key, fs.readFileSync(target, 'utf8'),
|
|
803
729
|
]));
|
|
@@ -807,13 +733,12 @@ function readPackedResearchLoop(project, platform) {
|
|
|
807
733
|
return values;
|
|
808
734
|
}
|
|
809
735
|
|
|
810
|
-
function
|
|
811
|
-
const values = Object.fromEntries(Object.entries(
|
|
736
|
+
function packedResearchIssues(project, platform) {
|
|
737
|
+
const values = Object.fromEntries(Object.entries(readPackedResearch(project, platform)).map(
|
|
812
738
|
([key, value]) => [key, value.replace(/\s+/g, ' ').trim()]
|
|
813
739
|
));
|
|
814
740
|
const issues = new Set();
|
|
815
741
|
const publicResearch = platform === 'codex' ? 'name: cf-research' : 'name: cf:research';
|
|
816
|
-
const publicLoop = platform === 'codex' ? 'name: cf-loop' : 'name: cf:loop';
|
|
817
742
|
if (!values.research.includes(publicResearch)
|
|
818
743
|
|| !values.research.includes('Choose the smallest depth that can support the decision:')
|
|
819
744
|
|| !values.research.includes('Use delegated researchers only as optional acceleration')
|
|
@@ -826,35 +751,15 @@ function packedResearchLoopIssues(project, platform) {
|
|
|
826
751
|
|| !values.agent.includes('owns any authorized persistence.')) {
|
|
827
752
|
issues.add('research-agent-boundary');
|
|
828
753
|
}
|
|
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');
|
|
754
|
+
if (/numeric optimization capability/.test(`${values.workflow}\n${values.domain}`)) {
|
|
755
|
+
issues.add('loop-removed');
|
|
851
756
|
}
|
|
852
757
|
return [...issues].sort();
|
|
853
758
|
}
|
|
854
759
|
|
|
855
|
-
function
|
|
856
|
-
const installed =
|
|
857
|
-
for (const [key, relative] of Object.entries(
|
|
760
|
+
function assertPackedResearchParity(project, platform) {
|
|
761
|
+
const installed = packedResearchPaths(project, platform);
|
|
762
|
+
for (const [key, relative] of Object.entries(RESEARCH_SOURCE_RELATIVES)) {
|
|
858
763
|
const sourcePath = path.join(PACKAGE_ROOT, relative);
|
|
859
764
|
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
860
765
|
const expected = platform === 'codex'
|
|
@@ -866,12 +771,15 @@ function assertPackedResearchLoopParity(project, platform) {
|
|
|
866
771
|
}
|
|
867
772
|
const manifest = JSON.parse(fs.readFileSync(path.join(project, RUNTIMES[platform].manifest), 'utf8'));
|
|
868
773
|
const prefixes = platform === 'codex'
|
|
869
|
-
? ['.agents/skills/research/'
|
|
870
|
-
: ['skills/research/'
|
|
774
|
+
? ['.agents/skills/research/']
|
|
775
|
+
: ['skills/research/'];
|
|
871
776
|
for (const prefix of prefixes) {
|
|
872
777
|
assert.ok(Object.keys(manifest.files).some((entry) => entry.startsWith(prefix)), `${platform} manifest missing ${prefix}`);
|
|
873
778
|
}
|
|
874
|
-
|
|
779
|
+
const removedLoop = platform === 'codex' ? '.agents/skills/loop/' : 'skills/loop/';
|
|
780
|
+
assert.equal(Object.keys(manifest.files).some((entry) => entry.startsWith(removedLoop)), false, `${platform} manifest still lists ${removedLoop}`);
|
|
781
|
+
assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/loop' : '.claude/skills/loop')), false, `${platform} still installs Loop`);
|
|
782
|
+
assert.deepEqual(packedResearchIssues(project, platform), []);
|
|
875
783
|
}
|
|
876
784
|
|
|
877
785
|
function packedBrainstormInstalledPaths(project, platform) {
|
|
@@ -1316,8 +1224,8 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
|
|
|
1316
1224
|
},
|
|
1317
1225
|
{
|
|
1318
1226
|
name: 'fresh-original-counterexample-replay', issue: 'repair-closure', source: 'review',
|
|
1319
|
-
from: 'After applying an accepted
|
|
1320
|
-
to: 'After applying an accepted
|
|
1227
|
+
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:',
|
|
1228
|
+
to: 'After applying an accepted GATE-REVIEW finding, record the repair under this review-log header:',
|
|
1321
1229
|
},
|
|
1322
1230
|
{
|
|
1323
1231
|
name: 'distinct-repair-and-proof-evidence', issue: 'repair-closure', source: 'review',
|
|
@@ -1423,7 +1331,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
|
|
|
1423
1331
|
['stale-profile-after-c2', 'templates', ADAPTIVE_COVERAGE_CLAUSES.profileRederivation,
|
|
1424
1332
|
'Keep existing coverage rows after accepted plan changes.', ['profile-lifecycle']],
|
|
1425
1333
|
['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`/
|
|
1334
|
+
'`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
1335
|
['source-promotes-live', 'templates', ADAPTIVE_COVERAGE_CLAUSES.proofSeparation,
|
|
1428
1336
|
'Source proof may promote installed and live proof.', ['proof-lifecycle']],
|
|
1429
1337
|
['unmapped-proof-level', 'templates', IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
|
|
@@ -1437,7 +1345,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
|
|
|
1437
1345
|
['spec-maker-examples-decide', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity,
|
|
1438
1346
|
'Use examples to settle every ambiguous observable behavior.', ['spec-maker-authority']],
|
|
1439
1347
|
['spec-maker-skips-brainstorm', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute,
|
|
1440
|
-
'Apply the risk-first route before
|
|
1348
|
+
'Apply the risk-first route before GATE-SCOPE and stop only for direct work.', ['spec-maker-authority']],
|
|
1441
1349
|
['static-proves-live', 'skill', ADAPTIVE_COVERAGE_CLAUSES.liveLimit,
|
|
1442
1350
|
'Source/static checks prove live-model adherence.', ['proof-lifecycle']],
|
|
1443
1351
|
];
|
|
@@ -1607,69 +1515,15 @@ function assertInstalledProvenance(root, platform, fixture) {
|
|
|
1607
1515
|
const policy = path.join(scripts, 'workflow-policy.cjs');
|
|
1608
1516
|
const resolver = path.join(scripts, 'spec-resolver.cjs');
|
|
1609
1517
|
const receipt = path.join(scripts, 'spec-receipt.cjs');
|
|
1610
|
-
const finalState = path.join(scripts, 'spec-final-state.cjs');
|
|
1611
|
-
const readiness = path.join(scripts, 'spec-readiness.cjs');
|
|
1612
1518
|
const gate = path.join(
|
|
1613
1519
|
root,
|
|
1614
1520
|
platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
|
|
1615
1521
|
);
|
|
1616
|
-
const
|
|
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
|
-
for (const file of [helper, policy, resolver, receipt, finalState, readiness]) {
|
|
1522
|
+
for (const file of [helper, policy, resolver, receipt]) {
|
|
1622
1523
|
assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
|
|
1623
1524
|
}
|
|
1624
1525
|
assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
|
|
1625
1526
|
|
|
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
1527
|
const helperRun = spawnSync(process.execPath, provenanceCliArgs(helper, root, fixture), {
|
|
1674
1528
|
cwd: root,
|
|
1675
1529
|
encoding: 'utf8',
|
|
@@ -1797,26 +1651,13 @@ function assertInstalledScripts(root, platform) {
|
|
|
1797
1651
|
const scripts = path.join(root, RUNTIMES[platform].root, 'scripts');
|
|
1798
1652
|
const policy = path.join(scripts, 'workflow-policy.cjs');
|
|
1799
1653
|
const scanner = path.join(scripts, 'scan-staged-secrets.cjs');
|
|
1800
|
-
const grounder = path.join(scripts, 'spec-ground.cjs');
|
|
1801
|
-
const finalState = path.join(scripts, 'spec-final-state.cjs');
|
|
1802
|
-
const readiness = path.join(scripts, 'spec-readiness.cjs');
|
|
1803
|
-
const validator = path.join(scripts, 'validate-spec-output.cjs');
|
|
1804
1654
|
assert.ok(fs.existsSync(policy), `installed policy missing: ${policy}`);
|
|
1805
1655
|
assert.ok(fs.existsSync(scanner), `installed scanner missing: ${scanner}`);
|
|
1806
|
-
assert.ok(fs.existsSync(grounder), `installed validator dependency missing: ${grounder}`);
|
|
1807
|
-
assert.ok(fs.existsSync(finalState), `installed final-state dependency missing: ${finalState}`);
|
|
1808
|
-
assert.ok(fs.existsSync(readiness), `installed readiness finalizer missing: ${readiness}`);
|
|
1809
|
-
assert.ok(fs.existsSync(validator), `installed validator missing: ${validator}`);
|
|
1810
1656
|
|
|
1811
1657
|
const policyRun = spawnSync(process.execPath, [policy, '--json'], { cwd: root, encoding: 'utf8' });
|
|
1812
1658
|
assert.equal(policyRun.status, 0, `${policyRun.stdout}\n${policyRun.stderr}`);
|
|
1813
1659
|
assert.equal(JSON.parse(policyRun.stdout).contract, 'execution-policy');
|
|
1814
1660
|
|
|
1815
|
-
const validatorRun = spawnSync(process.execPath, [validator], { cwd: root, encoding: 'utf8' });
|
|
1816
|
-
assert.equal(validatorRun.status, 2, `${validatorRun.stdout}\n${validatorRun.stderr}`);
|
|
1817
|
-
assert.match(validatorRun.stderr, /Usage: .*validate-spec-output\.cjs/);
|
|
1818
|
-
assert.doesNotMatch(validatorRun.stderr, /MODULE_NOT_FOUND|Cannot find module/);
|
|
1819
|
-
|
|
1820
1661
|
const safe = path.join(root, 'safe.txt');
|
|
1821
1662
|
fs.writeFileSync(safe, 'mode=safe\n');
|
|
1822
1663
|
spawnSync('git', ['init', '-q'], { cwd: root });
|
|
@@ -1826,458 +1667,10 @@ function assertInstalledScripts(root, platform) {
|
|
|
1826
1667
|
assert.match(scannerRun.stdout, /No staged secrets found/);
|
|
1827
1668
|
}
|
|
1828
1669
|
|
|
1829
|
-
function installedSemanticPaths(root, platform) {
|
|
1830
|
-
const runtimeRoot = path.join(root, RUNTIMES[platform].root);
|
|
1831
|
-
const skillRoot = platform === 'codex'
|
|
1832
|
-
? path.join(root, '.agents', 'skills')
|
|
1833
|
-
: path.join(runtimeRoot, 'skills');
|
|
1834
|
-
const paths = {
|
|
1835
|
-
policy: path.join(runtimeRoot, 'scripts', 'workflow-policy.cjs'),
|
|
1836
|
-
scaffold: path.join(runtimeRoot, 'scripts', 'spec-scaffold.cjs'),
|
|
1837
|
-
validator: path.join(runtimeRoot, 'scripts', 'validate-spec-output.cjs'),
|
|
1838
|
-
authoringValidator: path.join(runtimeRoot, 'scripts', 'spec-authoring-validation.cjs'),
|
|
1839
|
-
authoringDigest: path.join(runtimeRoot, 'scripts', 'spec-authoring-digest.cjs'),
|
|
1840
|
-
grounder: path.join(runtimeRoot, 'scripts', 'spec-ground.cjs'),
|
|
1841
|
-
finalState: path.join(runtimeRoot, 'scripts', 'spec-final-state.cjs'),
|
|
1842
|
-
readiness: path.join(runtimeRoot, 'scripts', 'spec-readiness.cjs'),
|
|
1843
|
-
semanticModel: path.join(runtimeRoot, 'scripts', 'spec-semantic-model.cjs'),
|
|
1844
|
-
resolver: path.join(runtimeRoot, 'scripts', 'spec-resolver.cjs'),
|
|
1845
|
-
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
|
-
stateTemplate: path.join(skillRoot, 'specs', 'templates', 'spec-state.json'),
|
|
1849
|
-
};
|
|
1850
|
-
for (const [name, target] of Object.entries(paths)) {
|
|
1851
|
-
assert.equal(fs.existsSync(target), true, `${platform} installed ${name} missing: ${target}`);
|
|
1852
|
-
assert.equal(path.relative(root, target).startsWith('..'), false, `${platform} ${name} escaped install root`);
|
|
1853
|
-
}
|
|
1854
|
-
return paths;
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
function runInstalled(script, args, root, expected = 0) {
|
|
1858
|
-
const result = spawnSync(process.execPath, [script, ...args], { cwd: root, encoding: 'utf8' });
|
|
1859
|
-
assert.equal(result.status, expected, `${script}\n${result.stdout}\n${result.stderr}`);
|
|
1860
|
-
return result;
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
1670
|
function writeJson(target, value) {
|
|
1864
1671
|
fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
|
|
1865
1672
|
}
|
|
1866
1673
|
|
|
1867
|
-
function materializeSpecState(paths, root, feature, { strict = false } = {}) {
|
|
1868
|
-
const featureDir = path.join(root, 'specs', feature);
|
|
1869
|
-
runInstalled(paths.scaffold, [feature], root);
|
|
1870
|
-
const state = JSON.parse(fs.readFileSync(path.join(featureDir, 'spec.json'), 'utf8'));
|
|
1871
|
-
state.scope_lock.in_scope = [`${feature} behavior`];
|
|
1872
|
-
state.scope_lock.out_of_scope = ['unrelated behavior'];
|
|
1873
|
-
if (strict) {
|
|
1874
|
-
state.workflow_policy.assurance_level = 'Strict';
|
|
1875
|
-
state.workflow_policy.classified_minimum.assurance_level = 'Strict';
|
|
1876
|
-
state.workflow_policy.risks = ['auth'];
|
|
1877
|
-
}
|
|
1878
|
-
writeJson(path.join(featureDir, 'spec.json'), state);
|
|
1879
|
-
return { featureDir, specFile: path.join(featureDir, 'spec.json'), state };
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
function requirementsProjection({ proof = false } = {}) {
|
|
1883
|
-
return `# Requirements
|
|
1884
|
-
|
|
1885
|
-
## Requirements
|
|
1886
|
-
|
|
1887
|
-
### Requirement 1: Installed behavior
|
|
1888
|
-
|
|
1889
|
-
- **R1.1**: The CommonJS entrypoint \`src/entry.js\` shall return an object containing only status \`enabled\` when its sole \`valid\` argument is the boolean \`true\`; it shall return an object containing only status \`rejected\` for \`false\`, a missing argument, or any non-boolean value.${proof ? '\n- **R1.2**: The installed verifier shall prove the observed entry behavior through its grounded evidence boundary.' : ''}
|
|
1890
|
-
`;
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
const TYPED_ANCHOR_COLUMNS = ['ID', 'Type', 'Target', 'Role', 'Access', 'Action'];
|
|
1894
|
-
|
|
1895
|
-
function typedAnchorTable(rows) {
|
|
1896
|
-
assert.ok(Array.isArray(rows) && rows.length > 0, 'typed anchor projection requires rows');
|
|
1897
|
-
const header = `| ${TYPED_ANCHOR_COLUMNS.join(' | ')} |`;
|
|
1898
|
-
const separator = `|${TYPED_ANCHOR_COLUMNS.map(() => '---').join('|')}|`;
|
|
1899
|
-
const body = rows.map((row) => {
|
|
1900
|
-
assert.deepEqual(Object.keys(row), ['id', 'type', 'target', 'role', 'access', 'action']);
|
|
1901
|
-
assert.ok(Object.values(row).every((value) => typeof value === 'string' && value.trim() !== ''));
|
|
1902
|
-
return `| ${row.id} | ${row.type} | \`${row.target}\` | ${row.role} | ${row.access} | ${row.action} |`;
|
|
1903
|
-
});
|
|
1904
|
-
return [header, separator, ...body].join('\n');
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
function designProjection({
|
|
1908
|
-
expected = 'exit 0; boolean true returns the exact enabled object; false, undefined, null, `"true"`, 1, and an object return the exact rejected object',
|
|
1909
|
-
taskful = false,
|
|
1910
|
-
} = {}) {
|
|
1911
|
-
const anchors = typedAnchorTable([{
|
|
1912
|
-
id: 'A-D-01', type: 'file', target: taskful ? 'src/design-boundary.js' : 'src/entry.js',
|
|
1913
|
-
role: taskful ? 'existing design boundary for the entrypoint' : 'existing runtime entrypoint and contract owner',
|
|
1914
|
-
access: 'read', action: 'read',
|
|
1915
|
-
}]);
|
|
1916
|
-
const reachabilityAnchors = taskful ? 'A-R1-01-01, A-R1-02-02' : 'A-D-01';
|
|
1917
|
-
return `# Design
|
|
1918
|
-
|
|
1919
|
-
## Boundary
|
|
1920
|
-
|
|
1921
|
-
- **Owns:** The exact return contract of the CommonJS function exported by \`src/entry.js\`.
|
|
1922
|
-
- **Reads:** One \`valid\` argument; only the boolean \`true\` is valid.
|
|
1923
|
-
- **Writes/exposes:** Exactly one status field whose value is \`enabled\` or \`rejected\`.
|
|
1924
|
-
- **Outside boundary:** Unrelated runtime behavior.
|
|
1925
|
-
|
|
1926
|
-
## Typed Anchors
|
|
1927
|
-
|
|
1928
|
-
${anchors}
|
|
1929
|
-
|
|
1930
|
-
## Decisions and Invariants
|
|
1931
|
-
|
|
1932
|
-
### D1 — Installed result decision
|
|
1933
|
-
|
|
1934
|
-
- **Decision:** \`valid === true\` returns an object containing only status \`enabled\`; every other value returns an object containing only status \`rejected\`.
|
|
1935
|
-
- **Rejects ambiguity:** Truthy non-booleans such as \`"true"\` never count as valid.
|
|
1936
|
-
- **Negative path:** \`false\`, \`undefined\`, \`null\`, strings, numbers, and objects return rejected.
|
|
1937
|
-
- **Anchors:** A-D-01
|
|
1938
|
-
|
|
1939
|
-
### I1 — Rejection invariant
|
|
1940
|
-
|
|
1941
|
-
Any input other than the boolean \`true\` never returns enabled.
|
|
1942
|
-
|
|
1943
|
-
### C1 — Result contract
|
|
1944
|
-
|
|
1945
|
-
- **Owner:** A-D-01
|
|
1946
|
-
- **Consumers:** \`test/entry.test.js\` and callers of the CommonJS export.
|
|
1947
|
-
- **Shape/behavior:** The result is an object with exactly one \`status\` field set to \`enabled\` or \`rejected\`.
|
|
1948
|
-
- **Compatibility:** The strict boolean discriminator, exact object shape, and two status values remain stable.
|
|
1949
|
-
|
|
1950
|
-
## Verification Definitions
|
|
1951
|
-
|
|
1952
|
-
- **V1**: Criteria R1.1; Owner ${taskful ? 'R1-01' : 'A-D-01'}; ${taskful ? 'Proof criteria R1.2; Proof owner R1-02; Evidence anchor A-R1-02-02; ' : ''}Decision refs D1, I1, C1; Method command \`node --test test/entry.test.js\`; Expected ${expected}; Negative/failure \`false\`, missing input, \`null\`, and truthy non-booleans all return the exact rejected object; Reachability/grounding entrypoint \`src/entry.js\` via ${reachabilityAnchors}.
|
|
1953
|
-
`;
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
function completeSemanticReview(paths, root, fixture, counterexamples) {
|
|
1957
|
-
// C16/D13: only the installed spec-authoring-validation.cjs coordinator may
|
|
1958
|
-
// flip authoring.* to validated and write the matching receipt (I21/R3.9) —
|
|
1959
|
-
// run it over the fixture's current bytes instead of hand-writing the enum.
|
|
1960
|
-
// Lifecycle order (I15/R3.7): authoring -> coordinator -> semantic review ->
|
|
1961
|
-
// readiness, never the reverse.
|
|
1962
|
-
runInstalled(paths.scaffold, [fixture.state.feature_name, '--sync-semantic-model'], root);
|
|
1963
|
-
runInstalled(paths.authoringValidator, [fixture.featureDir], root);
|
|
1964
|
-
const state = JSON.parse(fs.readFileSync(fixture.specFile, 'utf8'));
|
|
1965
|
-
fixture.reviewResult = {
|
|
1966
|
-
verdict: 'PASS',
|
|
1967
|
-
findings: [],
|
|
1968
|
-
unresolved_decisions: [],
|
|
1969
|
-
graph_coverage: [
|
|
1970
|
-
'criterion_local', 'cross_criterion', 'runtime_path',
|
|
1971
|
-
'assumption_provenance', 'compatibility_migration',
|
|
1972
|
-
].map((surface) => ({
|
|
1973
|
-
surface, covered: true,
|
|
1974
|
-
notes: 'The review covers this semantic surface against the canonical model.',
|
|
1975
|
-
})),
|
|
1976
|
-
reviewed_criteria: counterexamples.map(({ criterion }) => criterion),
|
|
1977
|
-
counterexamples,
|
|
1978
|
-
reviewer_evidence: null,
|
|
1979
|
-
};
|
|
1980
|
-
if (state.workflow_policy.assurance_level === 'Strict') {
|
|
1981
|
-
const digest = runInstalled(paths.validator, [fixture.featureDir, '--semantic-digest'], root).stdout.trim();
|
|
1982
|
-
assert.match(digest, /^sha256:[a-f0-9]{64}$/);
|
|
1983
|
-
return digest;
|
|
1984
|
-
}
|
|
1985
|
-
const reviewFile = path.join(fixture.featureDir, '.review-result.json');
|
|
1986
|
-
writeJson(reviewFile, fixture.reviewResult);
|
|
1987
|
-
runInstalled(paths.readiness, [fixture.featureDir, '--review-result', reviewFile], root);
|
|
1988
|
-
fs.unlinkSync(reviewFile);
|
|
1989
|
-
runInstalled(paths.validator, [fixture.featureDir], root);
|
|
1990
|
-
runInstalled(paths.grounder, [fixture.featureDir, '--root', root], root);
|
|
1991
|
-
return JSON.parse(fs.readFileSync(fixture.specFile, 'utf8')).validation.semantic_review.semantic_digest;
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
function createTasklessFixture(paths, root, feature, options = {}) {
|
|
1995
|
-
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
|
|
1996
|
-
fs.mkdirSync(path.join(root, 'test'), { recursive: true });
|
|
1997
|
-
fs.writeFileSync(path.join(root, 'src', 'entry.js'), 'module.exports = (valid) => ({ status: valid === true ? "enabled" : "rejected" });\n');
|
|
1998
|
-
fs.writeFileSync(path.join(root, 'src', 'design-boundary.js'), 'module.exports = { entry: "src/entry.js" };\n');
|
|
1999
|
-
fs.writeFileSync(path.join(root, 'test', 'entry.test.js'), `const test = require('node:test');
|
|
2000
|
-
const assert = require('node:assert/strict');
|
|
2001
|
-
const entry = require('../src/entry.js');
|
|
2002
|
-
|
|
2003
|
-
test('entry accepts only the boolean true discriminator', () => {
|
|
2004
|
-
assert.deepEqual(entry(true), { status: 'enabled' });
|
|
2005
|
-
for (const invalid of [false, undefined, null, 'true', 1, {}]) {
|
|
2006
|
-
assert.deepEqual(entry(invalid), { status: 'rejected' });
|
|
2007
|
-
}
|
|
2008
|
-
});
|
|
2009
|
-
`);
|
|
2010
|
-
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ scripts: { test: 'node --test' } }));
|
|
2011
|
-
const fixture = materializeSpecState(paths, root, feature, options);
|
|
2012
|
-
fs.writeFileSync(path.join(fixture.featureDir, 'requirements.md'), requirementsProjection());
|
|
2013
|
-
fs.writeFileSync(path.join(fixture.featureDir, 'design.md'), designProjection());
|
|
2014
|
-
fixture.digest = completeSemanticReview(paths, root, fixture, [{
|
|
2015
|
-
criterion: 'R1.1', case_kind: 'failure',
|
|
2016
|
-
scenario: 'The installed entry receives input without the required discriminator.',
|
|
2017
|
-
expected: 'The installed entry returns rejected and never returns enabled.',
|
|
2018
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
2019
|
-
}]);
|
|
2020
|
-
return fixture;
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
function taskProjection({ id, title, ownedPath, criterion, verificationRef, role, artifact = false }) {
|
|
2024
|
-
const artifactPath = `artifacts/${id}.json`;
|
|
2025
|
-
const anchors = typedAnchorTable([
|
|
2026
|
-
{ id: `A-${id}-01`, type: 'file', target: ownedPath, role: 'owner', access: 'write', action: 'modify' },
|
|
2027
|
-
...(artifact ? [{
|
|
2028
|
-
id: `A-${id}-02`, type: 'artifact', target: artifactPath,
|
|
2029
|
-
role: 'verifier', access: 'write', action: 'create',
|
|
2030
|
-
}] : []),
|
|
2031
|
-
{
|
|
2032
|
-
id: `A-${id}-${artifact ? '03' : '02'}`, type: 'command',
|
|
2033
|
-
target: 'node --test test/entry.test.js', role: 'verifier', access: 'read', action: 'read',
|
|
2034
|
-
},
|
|
2035
|
-
]);
|
|
2036
|
-
return `# Task ${id}: ${title}
|
|
2037
|
-
**Status:** pending
|
|
2038
|
-
|
|
2039
|
-
## Outcome
|
|
2040
|
-
|
|
2041
|
-
Deliver observable ${title.toLowerCase()} behavior through the installed entrypoint.
|
|
2042
|
-
|
|
2043
|
-
## Scope
|
|
2044
|
-
|
|
2045
|
-
- **In scope:** Exact behavior owned at ${ownedPath}.
|
|
2046
|
-
- **Out of scope:** Unrelated runtime behavior.
|
|
2047
|
-
|
|
2048
|
-
## Anchors and Ownership
|
|
2049
|
-
|
|
2050
|
-
${anchors}
|
|
2051
|
-
|
|
2052
|
-
## Changes
|
|
2053
|
-
|
|
2054
|
-
- [ ] Implement the exact owned behavior. _Requirements: ${criterion.slice(1)}_
|
|
2055
|
-
|
|
2056
|
-
## Acceptance
|
|
2057
|
-
|
|
2058
|
-
- **${criterion}:** The installed command returns the criterion-specific observable state.
|
|
2059
|
-
|
|
2060
|
-
## Dependencies
|
|
2061
|
-
|
|
2062
|
-
- none
|
|
2063
|
-
|
|
2064
|
-
## Verification Plan
|
|
2065
|
-
|
|
2066
|
-
- **Verification ref:** ${verificationRef}
|
|
2067
|
-
- **Task role:** ${role}
|
|
2068
|
-
- **Command:** \`node --test test/entry.test.js\`
|
|
2069
|
-
- **Expected:** Exit code 0 and the criterion-specific state is observed.${artifact ? ` The artifact \`${artifactPath}\` must exist, and SHA-256 over its current bytes must match the recorded digest.` : ''}
|
|
2070
|
-
- **Negative path:** Invalid input returns the named rejected state.
|
|
2071
|
-
- **Reachability:** \`src/entry.js\` is reached by the installed test command.
|
|
2072
|
-
`;
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
function createTaskFixture(paths, root, feature) {
|
|
2076
|
-
fs.mkdirSync(path.join(root, 'artifacts'), { recursive: true });
|
|
2077
|
-
const fixture = materializeSpecState(paths, root, feature);
|
|
2078
|
-
fs.writeFileSync(path.join(fixture.featureDir, 'requirements.md'), requirementsProjection({ proof: true }));
|
|
2079
|
-
fs.writeFileSync(path.join(fixture.featureDir, 'design.md'), designProjection({
|
|
2080
|
-
expected: 'exit 0, status enabled, and verifier artifact `artifacts/R1-02.json` whose SHA-256 over current bytes matches the recorded digest',
|
|
2081
|
-
taskful: true,
|
|
2082
|
-
}));
|
|
2083
|
-
const taskOne = 'tasks/task-R1-01-implement.md';
|
|
2084
|
-
const taskTwo = 'tasks/task-R1-02-verify.md';
|
|
2085
|
-
fs.mkdirSync(path.join(fixture.featureDir, 'tasks'), { recursive: true });
|
|
2086
|
-
fs.writeFileSync(path.join(fixture.featureDir, taskOne), taskProjection({
|
|
2087
|
-
id: 'R1-01', title: 'Implement behavior', ownedPath: 'src/entry.js', criterion: 'R1.1',
|
|
2088
|
-
verificationRef: 'V1', role: 'subject implements R1.1',
|
|
2089
|
-
}));
|
|
2090
|
-
fs.writeFileSync(path.join(fixture.featureDir, taskTwo), taskProjection({
|
|
2091
|
-
id: 'R1-02', title: 'Verify behavior', ownedPath: 'test/entry.test.js', criterion: 'R1.2',
|
|
2092
|
-
verificationRef: 'V1', role: 'verifier verifies V1 through separately owned R1.2 proof', artifact: true,
|
|
2093
|
-
}));
|
|
2094
|
-
const state = fixture.state;
|
|
2095
|
-
state.authoring.tasks = 'draft';
|
|
2096
|
-
state.task_files = [taskOne, taskTwo];
|
|
2097
|
-
state.task_registry = Object.fromEntries([
|
|
2098
|
-
[taskOne, { id: 'R1-01', title: 'Implement behavior', status: 'pending', dependencies: [], blocker: null, started_at: null, completed_at: null, last_updated_at: null }],
|
|
2099
|
-
[taskTwo, { id: 'R1-02', title: 'Verify behavior', status: 'pending', dependencies: [], blocker: null, started_at: null, completed_at: null, last_updated_at: null }],
|
|
2100
|
-
]);
|
|
2101
|
-
state.coordination.boundaries = [{
|
|
2102
|
-
id: 'B-OWN', type: 'ownership', tasks: ['R1-01', 'R1-02'],
|
|
2103
|
-
write_sets: { 'R1-01': ['src/entry.js'], 'R1-02': ['test/entry.test.js', 'artifacts/R1-02.json'] },
|
|
2104
|
-
}, {
|
|
2105
|
-
id: 'B-PROOF', type: 'proof', subject: 'R1-01', verifier: 'R1-02',
|
|
2106
|
-
verification_ref: 'V1', artifact_anchor: 'A-R1-02-02',
|
|
2107
|
-
}];
|
|
2108
|
-
writeJson(fixture.specFile, state);
|
|
2109
|
-
fixture.digest = completeSemanticReview(paths, root, fixture, [{
|
|
2110
|
-
criterion: 'R1.1', case_kind: 'failure',
|
|
2111
|
-
scenario: 'Invalid input reaches the installed implementation boundary.',
|
|
2112
|
-
expected: 'The implementation returns rejected and never reports enabled.',
|
|
2113
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
2114
|
-
}, {
|
|
2115
|
-
criterion: 'R1.2', case_kind: 'failure',
|
|
2116
|
-
scenario: 'The verification command completes without producing its proof artifact.',
|
|
2117
|
-
expected: 'The verification criterion remains unsatisfied until `artifacts/R1-02.json` exists and its SHA-256 over current bytes matches the recorded digest.',
|
|
2118
|
-
decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
|
|
2119
|
-
}]);
|
|
2120
|
-
return fixture;
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
function initializeGit(root) {
|
|
2124
|
-
fs.writeFileSync(path.join(root, '.gitignore'), `${'node_' + 'modules'}/\n`);
|
|
2125
|
-
for (const args of [
|
|
2126
|
-
['init', '-q'], ['config', 'user.name', 'CafeKit Packed E2E'],
|
|
2127
|
-
['config', 'user.email', 'packed-e2e@example.invalid'],
|
|
2128
|
-
['add', '--', '.gitignore', 'src/entry.js', 'src/design-boundary.js', 'test/entry.test.js', 'package.json'],
|
|
2129
|
-
['commit', '--no-gpg-sign', '-qm', 'packed semantic fixture'],
|
|
2130
|
-
]) {
|
|
2131
|
-
const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
|
|
2132
|
-
assert.equal(result.status, 0, `${args.join(' ')}\n${result.stdout}\n${result.stderr}`);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
function assertInstalledCompletion(paths, root, platform, fixture) {
|
|
2137
|
-
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-completion-home-'));
|
|
2138
|
-
const code = `
|
|
2139
|
-
const fs = require('fs');
|
|
2140
|
-
const path = require('path');
|
|
2141
|
-
const policy = require(process.argv[1]);
|
|
2142
|
-
const provenance = require(process.argv[2]);
|
|
2143
|
-
const checker = require(process.argv[3]);
|
|
2144
|
-
const resolver = require(process.argv[4]);
|
|
2145
|
-
const root = process.argv[5];
|
|
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');
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
function assertStaleDigestMutations(paths, root, taskless, taskBearing) {
|
|
2258
|
-
const requirementFile = path.join(taskless.featureDir, 'requirements.md');
|
|
2259
|
-
const requirementBytes = fs.readFileSync(requirementFile);
|
|
2260
|
-
fs.appendFileSync(requirementFile, '\nThe installed Markdown meaning changed.\n');
|
|
2261
|
-
assert.match(runInstalled(paths.validator, [taskless.featureDir], root, 1).stderr, /semantic_digest: stale/);
|
|
2262
|
-
fs.writeFileSync(requirementFile, requirementBytes);
|
|
2263
|
-
|
|
2264
|
-
const tasklessState = JSON.parse(fs.readFileSync(taskless.specFile, 'utf8'));
|
|
2265
|
-
const originalPolicy = JSON.parse(JSON.stringify(tasklessState.workflow_policy));
|
|
2266
|
-
tasklessState.workflow_policy.planning_depth = 'Full';
|
|
2267
|
-
writeJson(taskless.specFile, tasklessState);
|
|
2268
|
-
assert.match(runInstalled(paths.validator, [taskless.featureDir], root, 1).stderr, /semantic_digest: stale/);
|
|
2269
|
-
tasklessState.workflow_policy = originalPolicy;
|
|
2270
|
-
writeJson(taskless.specFile, tasklessState);
|
|
2271
|
-
|
|
2272
|
-
const taskState = JSON.parse(fs.readFileSync(taskBearing.specFile, 'utf8'));
|
|
2273
|
-
const originalId = taskState.coordination.boundaries[0].id;
|
|
2274
|
-
taskState.coordination.boundaries[0].id = 'B-OWN-MUTATED';
|
|
2275
|
-
writeJson(taskBearing.specFile, taskState);
|
|
2276
|
-
assert.match(runInstalled(paths.validator, [taskBearing.featureDir], root, 1).stderr, /semantic_digest: stale/);
|
|
2277
|
-
taskState.coordination.boundaries[0].id = originalId;
|
|
2278
|
-
writeJson(taskBearing.specFile, taskState);
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
1674
|
function assertTransforms(root, platform) {
|
|
2282
1675
|
const skillRoot = path.join(
|
|
2283
1676
|
root,
|
|
@@ -2285,8 +1678,8 @@ function assertTransforms(root, platform) {
|
|
|
2285
1678
|
);
|
|
2286
1679
|
const skill = path.join(skillRoot, 'develop', 'SKILL.md');
|
|
2287
1680
|
const content = fs.readFileSync(skill, 'utf8');
|
|
2288
|
-
const scout = fs.readFileSync(path.join(skillRoot, '
|
|
2289
|
-
const ask = fs.readFileSync(path.join(skillRoot, '
|
|
1681
|
+
const scout = fs.readFileSync(path.join(skillRoot, 'scout', 'SKILL.md'), 'utf8');
|
|
1682
|
+
const ask = fs.readFileSync(path.join(skillRoot, 'ask', 'SKILL.md'), 'utf8');
|
|
2290
1683
|
if (platform === 'codex') {
|
|
2291
1684
|
assert.match(content, /\$cf-develop/);
|
|
2292
1685
|
assert.doesNotMatch(content, /\/cf:develop/);
|
|
@@ -2297,8 +1690,8 @@ function assertTransforms(root, platform) {
|
|
|
2297
1690
|
assert.match(scout, /^name:\s*cf:scout$/m);
|
|
2298
1691
|
assert.match(ask, /^name:\s*cf:ask$/m);
|
|
2299
1692
|
}
|
|
2300
|
-
assert.equal(fs.existsSync(path.join(skillRoot, '
|
|
2301
|
-
assert.equal(fs.existsSync(path.join(skillRoot, '
|
|
1693
|
+
assert.equal(fs.existsSync(path.join(skillRoot, 'inspect')), false);
|
|
1694
|
+
assert.equal(fs.existsSync(path.join(skillRoot, 'question')), false);
|
|
2302
1695
|
assert.ok(fs.existsSync(path.join(root, RUNTIMES[platform].root, 'agents')) || platform === 'codex');
|
|
2303
1696
|
}
|
|
2304
1697
|
|
|
@@ -2320,12 +1713,7 @@ test('packed core-only installs reject optional capability routing', () => {
|
|
|
2320
1713
|
const tarball = path.join(destination, packed.filename);
|
|
2321
1714
|
const inventory = packedInventory(tarball);
|
|
2322
1715
|
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);
|
|
1716
|
+
assert.equal(inventory.some((entry) => entry.startsWith('src/claude/skills/route/')), false, 'Route is not packed');
|
|
2329
1717
|
|
|
2330
1718
|
const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
|
|
2331
1719
|
const project = path.join(root, 'project');
|
|
@@ -2339,9 +1727,10 @@ test('packed core-only installs reject optional capability routing', () => {
|
|
|
2339
1727
|
});
|
|
2340
1728
|
assert.equal(catalogResult.status, 0, catalogResult.stderr);
|
|
2341
1729
|
const catalog = JSON.parse(catalogResult.stdout);
|
|
2342
|
-
assert.
|
|
1730
|
+
assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:route'), false);
|
|
1731
|
+
assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route')), false);
|
|
2343
1732
|
assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:docs'), false);
|
|
2344
|
-
assert.deepEqual(
|
|
1733
|
+
assert.deepEqual(routingRuleIssues(installedRoutingRuleFiles(project, platform)), []);
|
|
2345
1734
|
}
|
|
2346
1735
|
const domain = fs.readFileSync(path.join(project, '.claude/rules/skill-domain-routing.md'), 'utf8');
|
|
2347
1736
|
assert.doesNotMatch(domain, /\/cf:(?:docs|docx|pdf|pptx|xlsx|ai-multimodal)/);
|
|
@@ -2350,7 +1739,7 @@ test('packed core-only installs reject optional capability routing', () => {
|
|
|
2350
1739
|
}
|
|
2351
1740
|
});
|
|
2352
1741
|
|
|
2353
|
-
test('packed
|
|
1742
|
+
test('packed routing rules reject semantic routing weakenings', () => {
|
|
2354
1743
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-route-mutations-'));
|
|
2355
1744
|
const destination = path.join(root, 'pack');
|
|
2356
1745
|
fs.mkdirSync(destination, { recursive: true });
|
|
@@ -2362,20 +1751,10 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2362
1751
|
const project = path.join(root, platform);
|
|
2363
1752
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2364
1753
|
runInstaller(installer, project, [platform], null);
|
|
2365
|
-
const files =
|
|
2366
|
-
assert.deepEqual(
|
|
1754
|
+
const files = installedRoutingRuleFiles(project, platform);
|
|
1755
|
+
assert.deepEqual(routingRuleIssues(files), []);
|
|
2367
1756
|
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'],
|
|
1757
|
+
['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
1758
|
['domain', 'rule-installed-only', 'Never infer an optional document capability from this rule', 'Invoke the Docs capability even when absent'],
|
|
2380
1759
|
['domain', 'rule-duplicate', 'do not auto-route; require explicit user disambiguation', 'automatically choose the first duplicate'],
|
|
2381
1760
|
];
|
|
@@ -2383,12 +1762,12 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2383
1762
|
const canonical = fs.readFileSync(files[source], 'utf8');
|
|
2384
1763
|
assert.equal(canonical.split(from).length, 2, `${platform}:${from}`);
|
|
2385
1764
|
fs.writeFileSync(files[source], canonical.replace(from, to));
|
|
2386
|
-
assert.deepEqual(
|
|
1765
|
+
assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
|
|
2387
1766
|
fs.writeFileSync(files[source], canonical);
|
|
2388
1767
|
}
|
|
2389
1768
|
const authorityCanonical = fs.readFileSync(files.workflow, 'utf8');
|
|
2390
1769
|
fs.writeFileSync(files.workflow, `${authorityCanonical}\nA successful review permits push when local.\n`);
|
|
2391
|
-
assert.deepEqual(
|
|
1770
|
+
assert.deepEqual(routingRuleIssues(files), ['authority'], `${platform}:additive-authority`);
|
|
2392
1771
|
fs.writeFileSync(files.workflow, authorityCanonical);
|
|
2393
1772
|
const additiveMutations = [
|
|
2394
1773
|
['domain', 'rule-live-catalog', 'Override: treat examples as installed inventory.'],
|
|
@@ -2398,7 +1777,7 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2398
1777
|
for (const [source, expectedIssue, addition] of additiveMutations) {
|
|
2399
1778
|
const canonical = fs.readFileSync(files[source], 'utf8');
|
|
2400
1779
|
fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
|
|
2401
|
-
assert.deepEqual(
|
|
1780
|
+
assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
|
|
2402
1781
|
fs.writeFileSync(files[source], canonical);
|
|
2403
1782
|
}
|
|
2404
1783
|
const safeAdditions = [
|
|
@@ -2409,10 +1788,10 @@ test('packed Route rejects semantic routing weakenings', () => {
|
|
|
2409
1788
|
for (const [source, addition] of safeAdditions) {
|
|
2410
1789
|
const canonical = fs.readFileSync(files[source], 'utf8');
|
|
2411
1790
|
fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
|
|
2412
|
-
assert.deepEqual(
|
|
1791
|
+
assert.deepEqual(routingRuleIssues(files), [], `${platform}:safe-addition`);
|
|
2413
1792
|
fs.writeFileSync(files[source], canonical);
|
|
2414
1793
|
}
|
|
2415
|
-
assert.deepEqual(
|
|
1794
|
+
assert.deepEqual(routingRuleIssues(files), []);
|
|
2416
1795
|
}
|
|
2417
1796
|
} finally {
|
|
2418
1797
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -2480,8 +1859,8 @@ test('packed Claude and Codex installs preserve adaptive Specs, spec-maker, and
|
|
|
2480
1859
|
}
|
|
2481
1860
|
});
|
|
2482
1861
|
|
|
2483
|
-
test('packed Claude and Codex installs preserve
|
|
2484
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-
|
|
1862
|
+
test('packed Claude and Codex installs preserve Research and install no Loop', () => {
|
|
1863
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-'));
|
|
2485
1864
|
const destination = path.join(root, 'pack');
|
|
2486
1865
|
fs.mkdirSync(destination, { recursive: true });
|
|
2487
1866
|
try {
|
|
@@ -2493,7 +1872,7 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
|
|
|
2493
1872
|
const project = path.join(root, platform);
|
|
2494
1873
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2495
1874
|
runInstaller(installer, project, [platform], null);
|
|
2496
|
-
|
|
1875
|
+
assertPackedResearchParity(project, platform);
|
|
2497
1876
|
assert.equal(fs.existsSync(path.join(
|
|
2498
1877
|
project, platform === 'codex' ? '.agents/skills/autoresearch' : '.claude/skills/autoresearch'
|
|
2499
1878
|
)), false);
|
|
@@ -2503,26 +1882,17 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
|
|
|
2503
1882
|
}
|
|
2504
1883
|
});
|
|
2505
1884
|
|
|
2506
|
-
test('packed Research
|
|
2507
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-
|
|
1885
|
+
test('packed Research rejects semantic weakenings', () => {
|
|
1886
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-mutations-'));
|
|
2508
1887
|
const destination = path.join(root, 'pack');
|
|
2509
1888
|
fs.mkdirSync(destination, { recursive: true });
|
|
2510
|
-
const canonicalBytes = new Map(Object.values(
|
|
1889
|
+
const canonicalBytes = new Map(Object.values(RESEARCH_SOURCE_RELATIVES).map((relative) => {
|
|
2511
1890
|
const sourcePath = path.join(PACKAGE_ROOT, relative);
|
|
2512
1891
|
return [sourcePath, fs.readFileSync(sourcePath)];
|
|
2513
1892
|
}));
|
|
2514
1893
|
const mutations = [
|
|
2515
1894
|
['research', 'research-adaptive-evidence', 'Use delegated researchers only as optional acceleration', 'Delegation is required for every research request'],
|
|
2516
1895
|
['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
1896
|
];
|
|
2527
1897
|
try {
|
|
2528
1898
|
const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
|
|
@@ -2532,8 +1902,8 @@ test('packed Research and Loop reject semantic weakenings', () => {
|
|
|
2532
1902
|
const project = path.join(root, platform);
|
|
2533
1903
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2534
1904
|
runInstaller(installer, project, [platform], null);
|
|
2535
|
-
|
|
2536
|
-
const paths =
|
|
1905
|
+
assertPackedResearchParity(project, platform);
|
|
1906
|
+
const paths = packedResearchPaths(project, platform);
|
|
2537
1907
|
for (const [source, expectedIssue, from, to] of mutations) {
|
|
2538
1908
|
const target = fs.realpathSync(paths[source]);
|
|
2539
1909
|
const relative = path.relative(fs.realpathSync(project), target);
|
|
@@ -2549,12 +1919,12 @@ test('packed Research and Loop reject semantic weakenings', () => {
|
|
|
2549
1919
|
assert.equal(content.indexOf(from, anchor + from.length), -1, `${platform}/${source} duplicate mutation anchor`);
|
|
2550
1920
|
try {
|
|
2551
1921
|
fs.writeFileSync(target, `${content.slice(0, anchor)}${to}${content.slice(anchor + from.length)}`);
|
|
2552
|
-
assert.deepEqual(
|
|
1922
|
+
assert.deepEqual(packedResearchIssues(project, platform), [expectedIssue]);
|
|
2553
1923
|
for (const [sourcePath, bytes] of canonicalBytes) assert.deepEqual(fs.readFileSync(sourcePath), bytes);
|
|
2554
1924
|
} finally {
|
|
2555
1925
|
fs.writeFileSync(target, original);
|
|
2556
1926
|
}
|
|
2557
|
-
assert.deepEqual(
|
|
1927
|
+
assert.deepEqual(packedResearchIssues(project, platform), []);
|
|
2558
1928
|
}
|
|
2559
1929
|
}
|
|
2560
1930
|
} finally {
|
|
@@ -2562,40 +1932,24 @@ test('packed Research and Loop reject semantic weakenings', () => {
|
|
|
2562
1932
|
}
|
|
2563
1933
|
});
|
|
2564
1934
|
|
|
2565
|
-
test('repository and package guides document adaptive Research and
|
|
1935
|
+
test('repository and package guides document adaptive Research and no Loop', () => {
|
|
2566
1936
|
const guides = {
|
|
2567
1937
|
repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
|
|
2568
1938
|
package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
|
|
2569
1939
|
};
|
|
2570
1940
|
for (const [name, guide] of Object.entries(guides)) {
|
|
2571
1941
|
assert.match(guide, /cf:research/, `${name} names Claude Research`);
|
|
2572
|
-
assert.match(guide, /cf:loop/, `${name} names Claude Loop`);
|
|
2573
1942
|
assert.match(guide, /\$cf-research/, `${name} names Codex Research`);
|
|
2574
|
-
assert.match(guide, /\$cf-loop/, `${name} names Codex Loop`);
|
|
2575
1943
|
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`);
|
|
1944
|
+
assert.doesNotMatch(guide, /cf[:-]loop/, `${name} does not advertise Loop`);
|
|
2584
1945
|
assert.doesNotMatch(guide, /cf[:-]autoresearch/, `${name} does not advertise Autoresearch`);
|
|
2585
1946
|
}
|
|
2586
1947
|
|
|
2587
1948
|
const catalog = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/catalog-visuals.tsx'), 'utf8');
|
|
2588
1949
|
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
1950
|
assert.match(catalog, /proportional, traceable evidence/);
|
|
2591
1951
|
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/);
|
|
1952
|
+
assert.doesNotMatch(`${catalog}\n${overview}`, /cf:loop|'loop'/);
|
|
2599
1953
|
assert.doesNotMatch(`${catalog}\n${overview}`, /autoresearch/i);
|
|
2600
1954
|
});
|
|
2601
1955
|
|
|
@@ -2623,11 +1977,13 @@ test('packed Claude and Codex reject adaptive Brainstorm semantic weakenings', (
|
|
|
2623
1977
|
const project = path.join(root, platform);
|
|
2624
1978
|
const installer = installPacked(tarball, project, runtimeClosure);
|
|
2625
1979
|
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}
|
|
1980
|
+
const skillsRoot = platform === 'claude'
|
|
1981
|
+
? path.join(project, '.claude/skills')
|
|
1982
|
+
: path.join(project, '.agents/skills');
|
|
1983
|
+
assert.equal(fs.existsSync(path.join(skillsRoot, 'fix', 'SKILL.md')), true,
|
|
1984
|
+
`${platform} installs Fix from the fix directory`);
|
|
1985
|
+
assert.equal(fs.existsSync(path.join(skillsRoot, 'hotfix')), false,
|
|
1986
|
+
`${platform} no longer installs the retired hotfix directory`);
|
|
2631
1987
|
assertPackedBrainstormParity(project, platform);
|
|
2632
1988
|
for (const entry of assertPackedAdaptiveBrainstormMutations(project, platform, canonicalBytes)) exercised.add(entry);
|
|
2633
1989
|
assertPackedBrainstormParity(project, platform);
|
|
@@ -2723,7 +2079,7 @@ function packedHotfixIssues(files, refPrefix) {
|
|
|
2723
2079
|
}
|
|
2724
2080
|
if (!parallel.includes('Diagnosis still starts only')
|
|
2725
2081
|
|| !parallel.includes('after the required scout outputs are synthesized')
|
|
2726
|
-
|| !parallel.includes('Research begins only after
|
|
2082
|
+
|| !parallel.includes('Research begins only after diagnosis')
|
|
2727
2083
|
|| parallel.includes('scout + diagnose + research together')
|
|
2728
2084
|
|| parallel.includes("You don't need to wait for scouting")) {
|
|
2729
2085
|
issues.add('scout-before-diagnosis');
|
|
@@ -2745,8 +2101,8 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
|
|
|
2745
2101
|
const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
|
|
2746
2102
|
assertCleanInventory(packedInventory(tarball));
|
|
2747
2103
|
const layouts = {
|
|
2748
|
-
claude: { skillsRoot: '.claude/skills/
|
|
2749
|
-
codex: { skillsRoot: '.agents/skills/
|
|
2104
|
+
claude: { skillsRoot: '.claude/skills/fix', refPrefix: 'cf:' },
|
|
2105
|
+
codex: { skillsRoot: '.agents/skills/fix', refPrefix: 'cf-' },
|
|
2750
2106
|
};
|
|
2751
2107
|
const exercised = new Set();
|
|
2752
2108
|
for (const [platform, layout] of Object.entries(layouts)) {
|
|
@@ -2773,7 +2129,7 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
|
|
|
2773
2129
|
{ group: 'deep-decision-route', file: 'SKILL.md', from: 'after diagnosis, research only unresolved external facts', to: 'research broadly before diagnosis' },
|
|
2774
2130
|
{ group: 'specialized-proof-overlays', file: 'references/workflow-specialized.md', from: 'Load only the matching section', to: 'Load every section' },
|
|
2775
2131
|
{ 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
|
|
2132
|
+
{ group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Research begins only after diagnosis', to: 'Research may begin before diagnosis' },
|
|
2777
2133
|
];
|
|
2778
2134
|
for (const mutation of mutations) {
|
|
2779
2135
|
const target = path.join(project, layout.skillsRoot, mutation.file);
|
|
@@ -2849,8 +2205,7 @@ test('website keeps process-first docs, canonical public names, and historical l
|
|
|
2849
2205
|
const relative = path.relative(docsRoot, file).split(path.sep).join('/');
|
|
2850
2206
|
return file.endsWith('.mdx')
|
|
2851
2207
|
&& !relative.endsWith('/reference.mdx')
|
|
2852
|
-
&& !relative.endsWith('/platforms/opencode.mdx')
|
|
2853
|
-
&& !relative.endsWith('/spec-lifecycle.mdx');
|
|
2208
|
+
&& !relative.endsWith('/platforms/opencode.mdx');
|
|
2854
2209
|
});
|
|
2855
2210
|
const currentSources = walk(sourceRoot).filter((file) => /\.(?:ts|tsx)$/.test(file));
|
|
2856
2211
|
const currentCorpus = [...currentDocs, ...currentSources]
|
|
@@ -2898,10 +2253,7 @@ test('website keeps process-first docs, canonical public names, and historical l
|
|
|
2898
2253
|
|
|
2899
2254
|
for (const locale of ['en', 'vi', 'ja']) {
|
|
2900
2255
|
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/);
|
|
2256
|
+
assert.doesNotMatch(lifecycle, /^## Legacy compatibility$/m, `${locale} spec lifecycle must not promise a legacy adapter`);
|
|
2905
2257
|
|
|
2906
2258
|
const opencode = fs.readFileSync(path.join(docsRoot, locale, 'platforms/opencode.mdx'), 'utf8');
|
|
2907
2259
|
assert.match(opencode, /0\.17/);
|
|
@@ -3034,43 +2386,6 @@ test('repository and package guides document adaptive Docs usage', () => {
|
|
|
3034
2386
|
}
|
|
3035
2387
|
});
|
|
3036
2388
|
|
|
3037
|
-
test('packed Claude and Codex installs execute semantic kernel behavior without package source', () => {
|
|
3038
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-semantic-'));
|
|
3039
|
-
const destination = path.join(root, 'pack');
|
|
3040
|
-
fs.mkdirSync(destination, { recursive: true });
|
|
3041
|
-
try {
|
|
3042
|
-
const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
|
|
3043
|
-
const tarball = path.join(destination, packed.filename);
|
|
3044
|
-
const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
|
|
3045
|
-
assertCleanInventory(packedInventory(tarball));
|
|
3046
|
-
|
|
3047
|
-
for (const platform of ['claude', 'codex']) {
|
|
3048
|
-
const project = path.join(root, platform);
|
|
3049
|
-
const installer = installPacked(tarball, project, runtimeClosure);
|
|
3050
|
-
runInstaller(installer, project, [platform], null);
|
|
3051
|
-
const packedSource = path.resolve(path.dirname(installer), '..', 'src');
|
|
3052
|
-
fs.rmSync(packedSource, { recursive: true, force: true });
|
|
3053
|
-
assert.equal(fs.existsSync(packedSource), false, `${platform} package source must be unavailable`);
|
|
3054
|
-
|
|
3055
|
-
const paths = installedSemanticPaths(project, platform);
|
|
3056
|
-
const taskless = createTasklessFixture(paths, project, 'compact-installed');
|
|
3057
|
-
initializeGit(project);
|
|
3058
|
-
assertInstalledCompletion(paths, project, platform, taskless);
|
|
3059
|
-
|
|
3060
|
-
const strict = createTasklessFixture(paths, project, 'strict-installed', { strict: true });
|
|
3061
|
-
const taskBearing = createTaskFixture(paths, project, 'tasks-installed');
|
|
3062
|
-
assertInstalledResolution(paths, project, platform, 'strict-installed');
|
|
3063
|
-
assertStrictSimulatedHandlerGuardrail(paths, project, strict);
|
|
3064
|
-
assertStaleDigestMutations(paths, project, taskless, taskBearing);
|
|
3065
|
-
runInstalled(paths.validator, [taskless.featureDir], project);
|
|
3066
|
-
runInstalled(paths.validator, [taskBearing.featureDir], project);
|
|
3067
|
-
runInstalled(paths.grounder, [taskBearing.featureDir, '--root', project], project);
|
|
3068
|
-
}
|
|
3069
|
-
} finally {
|
|
3070
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
3071
|
-
}
|
|
3072
|
-
});
|
|
3073
|
-
|
|
3074
2389
|
test('packed Claude and Codex installs self-contain runtime provenance and fail closed without it', () => {
|
|
3075
2390
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-provenance-package-'));
|
|
3076
2391
|
const destination = path.join(root, 'pack');
|
|
@@ -3097,153 +2412,3 @@ test('packed Claude and Codex installs self-contain runtime provenance and fail
|
|
|
3097
2412
|
fs.rmSync(root, { recursive: true, force: true });
|
|
3098
2413
|
}
|
|
3099
2414
|
});
|
|
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
|
-
});
|