@haposoft/cafekit 0.16.0-rc.8 → 0.16.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 +117 -28
- package/bin/__tests__/codex-hooks-ownership.test.js +239 -0
- package/bin/__tests__/codex-hooks.test.js +175 -0
- package/bin/__tests__/codex-native.test.js +1882 -70
- package/bin/__tests__/develop-contract.test.js +442 -23
- package/bin/__tests__/grok-envelope.test.js +271 -0
- package/bin/__tests__/grok-platform.test.js +133 -0
- package/bin/__tests__/hook-payload.test.js +194 -0
- package/bin/__tests__/installer-safety.test.js +3 -3
- package/bin/__tests__/omp-bridge.test.js +168 -0
- package/bin/__tests__/omp-hooks.test.js +178 -0
- package/bin/__tests__/omp-runtime.test.js +150 -0
- package/bin/__tests__/optional-skill-inventory.test.js +249 -0
- package/bin/__tests__/output-styles.test.js +214 -0
- package/bin/__tests__/package-inventory.test.js +1735 -98
- package/bin/__tests__/runtime-dir.test.js +158 -0
- package/bin/__tests__/runtime-schema.test.js +113 -0
- package/bin/__tests__/secret-output-guardrail.test.js +122 -0
- package/bin/__tests__/skill-routing-source.test.js +172 -0
- package/bin/__tests__/specs-v2-execution-closeout.test.js +28 -1
- package/bin/install.js +15 -5
- package/bin/lib/codex-install.js +170 -5
- package/bin/lib/context.js +64 -3
- package/bin/lib/i18n.js +15 -36
- package/bin/lib/skill-deps.js +5 -5
- package/bin/phases/claude-runtime.js +23 -0
- package/bin/phases/claude-settings.js +8 -0
- package/bin/phases/codex-hooks.js +156 -0
- package/bin/phases/codex-runtime.js +29 -38
- package/bin/phases/copy-payload.js +15 -2
- package/bin/phases/omp-runtime.js +140 -0
- package/bin/phases/root-config.js +3 -0
- package/bin/phases/select-platform.js +22 -1
- package/bin/phases/select-skill-bundles.js +96 -0
- package/bin/phases/skill-inventory.js +71 -0
- package/bin/phases/skills-setup.js +9 -2
- package/bin/phases/summary.js +0 -4
- package/bin/phases/write-metadata.js +8 -5
- package/package.json +8 -2
- package/src/claude/CLAUDE.md +3 -3
- package/src/claude/agents/brainstormer.md +52 -69
- package/src/claude/agents/code-auditor.md +32 -14
- package/src/claude/agents/debugger.md +35 -20
- package/src/claude/agents/deployer.md +12 -5
- package/src/claude/agents/docs-keeper.md +11 -1
- package/src/claude/agents/implementer.md +25 -13
- package/src/claude/agents/inspector.md +6 -3
- package/src/claude/agents/project-manager.md +1 -1
- package/src/claude/agents/researcher.md +26 -55
- package/src/claude/agents/spec-maker.md +12 -7
- package/src/claude/agents/strategist.md +52 -0
- package/src/claude/agents/test-runner.md +117 -132
- package/src/claude/agents/ui-ux-designer.md +1 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -0
- package/src/claude/hooks/__tests__/precompact.test.js +158 -0
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +2 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +348 -3
- package/src/claude/hooks/__tests__/state.test.js +7 -0
- package/src/claude/hooks/__tests__/statusline.test.js +196 -0
- package/src/claude/hooks/agent.cjs +13 -8
- package/src/claude/hooks/completion-authority.cjs +10 -4
- package/src/claude/hooks/docs-sync.cjs +14 -11
- package/src/claude/hooks/inspect-block.cjs +28 -8
- package/src/claude/hooks/lib/config.cjs +8 -2
- package/src/claude/hooks/lib/counter.cjs +7 -5
- package/src/claude/hooks/lib/hook-payload.cjs +160 -0
- package/src/claude/hooks/lib/hook-state-dir.cjs +39 -0
- package/src/claude/hooks/lib/runtime-dir.cjs +49 -0
- package/src/claude/hooks/lib/secret-keywords.cjs +42 -0
- package/src/claude/hooks/precompact.cjs +106 -0
- package/src/claude/hooks/privacy-block.cjs +4 -2
- package/src/claude/hooks/rules.cjs +5 -3
- package/src/claude/hooks/secret-output-guardrail.cjs +44 -0
- package/src/claude/hooks/semantic-review-authority.cjs +6 -4
- package/src/claude/hooks/session.cjs +37 -9
- package/src/claude/hooks/spec-gate.cjs +21 -11
- package/src/claude/hooks/spec-state.cjs +11 -7
- package/src/claude/hooks/state.cjs +7 -5
- package/src/claude/hooks/task-scaffold-guard.cjs +30 -9
- package/src/claude/hooks/usage.cjs +6 -5
- package/src/claude/migration-manifest.json +31 -15
- package/src/claude/output-styles/coding-level-0-eli5.md +103 -0
- package/src/claude/output-styles/coding-level-1-junior.md +124 -0
- package/src/claude/output-styles/coding-level-2-mid.md +146 -0
- package/src/claude/output-styles/coding-level-3-senior.md +148 -0
- package/src/claude/output-styles/coding-level-4-lead.md +159 -0
- package/src/claude/output-styles/coding-level-5-god.md +91 -0
- package/src/claude/references/debugger/core-philosophy.md +5 -3
- package/src/claude/references/debugger/frontend-verification.md +2 -2
- package/src/claude/references/debugger/log-ci-analysis.md +13 -1
- package/src/claude/references/debugger/parallel-agent-hydration.md +19 -34
- package/src/claude/references/debugger/performance-diagnostics.md +2 -2
- package/src/claude/references/debugger/root-cause-tracing.md +37 -6
- package/src/claude/references/debugger/side-effect-gate.md +3 -1
- package/src/claude/references/debugger/verification-protocol.md +13 -12
- package/src/claude/rules/orchestrator.md +7 -1
- package/src/claude/rules/process-management.md +39 -0
- package/src/claude/rules/review-audit-self-decision.md +48 -0
- package/src/claude/rules/skill-domain-routing.md +26 -24
- package/src/claude/rules/skill-workflow-routing.md +22 -14
- package/src/claude/rules/state-sync.md +7 -1
- package/src/claude/rules/workflow.md +5 -3
- package/src/claude/runtime.json +1 -0
- package/src/claude/runtime.schema.json +205 -0
- package/src/claude/scripts/generate-skill-catalog.cjs +156 -122
- package/src/claude/scripts/provenance.cjs +29 -9
- package/src/claude/scripts/spec-receipt.cjs +65 -4
- package/src/claude/scripts/workflow-policy.cjs +1 -1
- package/src/claude/settings/settings.json +16 -0
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
- package/src/claude/skills/brainstorm/SKILL.md +193 -172
- package/src/claude/skills/brainstorm/references/question-framework.md +105 -167
- package/src/claude/skills/chrome-devtools/SKILL.md +1 -1
- package/src/claude/skills/code-review/SKILL.md +43 -25
- package/src/claude/skills/code-review/references/spec-compliance-review.md +3 -4
- package/src/claude/skills/code-review/references/verification-gate.md +1 -1
- package/src/claude/skills/debug/SKILL.md +48 -19
- package/src/claude/skills/delegate/SKILL.md +1 -1
- package/src/claude/skills/develop/SKILL.md +92 -65
- package/src/claude/skills/develop/references/parallel-waves.md +36 -63
- package/src/claude/skills/develop/references/quality-gate.md +31 -4
- package/src/claude/skills/develop/references/subagent-patterns.md +21 -7
- package/src/claude/skills/docs/SKILL.md +42 -23
- package/src/claude/skills/docs/references/init-workflow.md +4 -4
- package/src/claude/skills/docs/references/reconstruct-workflow.md +6 -6
- package/src/claude/skills/docs/references/standard-docs-workflow.md +6 -2
- package/src/claude/skills/docs/references/summarize-workflow.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +9 -7
- package/src/claude/skills/docs/templates/requirements-as-is.md +1 -1
- package/src/claude/skills/docx/SKILL.md +1 -1
- package/src/claude/skills/git/SKILL.md +1 -1
- package/src/claude/skills/git/references/commit-protocols.md +1 -1
- package/src/claude/skills/git/references/finish-branch.md +1 -1
- package/src/claude/skills/hotfix/SKILL.md +159 -170
- package/src/claude/skills/hotfix/references/diagnosis-protocol.md +14 -9
- package/src/claude/skills/hotfix/references/parallel-patterns.md +27 -13
- package/src/claude/skills/hotfix/references/prevention-gate.md +5 -0
- package/src/claude/skills/hotfix/references/review-cycle.md +21 -15
- package/src/claude/skills/hotfix/references/workflow-specialized.md +53 -21
- package/src/claude/skills/inspect/SKILL.md +55 -46
- package/src/claude/skills/inspect/references/internal-inspection.md +21 -5
- package/src/claude/skills/loop/SKILL.md +114 -0
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +104 -0
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +76 -0
- package/src/claude/skills/pdf/SKILL.md +1 -1
- package/src/claude/skills/pptx/SKILL.md +1 -1
- package/src/claude/skills/question/SKILL.md +16 -13
- package/src/claude/skills/research/SKILL.md +70 -43
- package/src/claude/skills/route/SKILL.md +94 -0
- package/src/claude/skills/route/references/agent-timing.md +46 -0
- package/src/claude/skills/route/references/chaining-patterns.md +53 -0
- package/src/claude/skills/route/references/task-taxonomy.md +48 -0
- package/src/claude/skills/specs/SKILL.md +51 -71
- package/src/claude/skills/specs/references/review.md +11 -8
- package/src/claude/skills/specs/references/templates.md +34 -18
- package/src/claude/skills/specs/templates/requirements-init.md +1 -1
- package/src/claude/skills/specs/templates/research.md +2 -2
- package/src/claude/skills/sync/SKILL.md +6 -6
- package/src/claude/skills/sync/references/sync-protocols.md +1 -1
- package/src/claude/skills/test/SKILL.md +120 -120
- package/src/claude/skills/test/references/execution-strategy.md +208 -361
- package/src/claude/skills/test/references/failure-triage.md +55 -121
- package/src/claude/skills/test/references/test-memory.md +20 -34
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/src/claude/skills/web-testing/SKILL.md +1 -1
- package/src/claude/skills/xlsx/SKILL.md +1 -1
- package/src/claude/status.cjs +135 -33
- package/src/codex/AGENTS.md +14 -4
- package/src/codex/hooks/docs-sync.cjs +8 -3
- package/src/codex/hooks/lib/hook-context.cjs +18 -4
- package/src/codex/hooks/rules.cjs +29 -1
- package/src/codex/hooks/secret-output-guardrail.cjs +30 -0
- package/src/codex/hooks/spec-gate.cjs +3 -8
- package/src/codex/hooks/spec-state.cjs +3 -2
- package/src/codex/hooks.json +5 -0
- package/src/codex/rules/state-sync.md +7 -1
- package/src/codex/runtime.json +2 -0
- package/src/common/.env.example +1 -1
- package/src/omp/extensions/cafekit-bridge.mjs +233 -0
- package/src/omp/hooks/privacy-block.cjs +250 -0
- package/src/omp/runtime.json +33 -0
- package/bin/lib/rtk-run.js +0 -28
- package/bin/phases/setup-rtk.js +0 -159
- package/src/claude/skills/backend-development/SKILL.md +0 -101
- package/src/claude/skills/backend-development/references/backend-api-design.md +0 -495
- package/src/claude/skills/backend-development/references/backend-architecture.md +0 -454
- package/src/claude/skills/backend-development/references/backend-authentication.md +0 -338
- package/src/claude/skills/backend-development/references/backend-code-quality.md +0 -659
- package/src/claude/skills/backend-development/references/backend-debugging.md +0 -904
- package/src/claude/skills/backend-development/references/backend-devops.md +0 -491
- package/src/claude/skills/backend-development/references/backend-mindset.md +0 -387
- package/src/claude/skills/backend-development/references/backend-performance.md +0 -397
- package/src/claude/skills/backend-development/references/backend-security.md +0 -342
- package/src/claude/skills/backend-development/references/backend-technologies.md +0 -256
- package/src/claude/skills/backend-development/references/backend-testing.md +0 -429
- package/src/claude/skills/devops/.env.example +0 -76
- package/src/claude/skills/devops/SKILL.md +0 -102
- package/src/claude/skills/devops/references/browser-rendering.md +0 -305
- package/src/claude/skills/devops/references/cloudflare-d1-kv.md +0 -123
- package/src/claude/skills/devops/references/cloudflare-platform.md +0 -271
- package/src/claude/skills/devops/references/cloudflare-r2-storage.md +0 -280
- package/src/claude/skills/devops/references/cloudflare-workers-advanced.md +0 -312
- package/src/claude/skills/devops/references/cloudflare-workers-apis.md +0 -309
- package/src/claude/skills/devops/references/cloudflare-workers-basics.md +0 -418
- package/src/claude/skills/devops/references/docker-basics.md +0 -297
- package/src/claude/skills/devops/references/docker-compose.md +0 -292
- package/src/claude/skills/devops/references/gcloud-platform.md +0 -297
- package/src/claude/skills/devops/references/gcloud-services.md +0 -304
- package/src/claude/skills/devops/references/kubernetes-basics.md +0 -95
- package/src/claude/skills/devops/references/kubernetes-helm-advanced.md +0 -70
- package/src/claude/skills/devops/references/kubernetes-helm.md +0 -81
- package/src/claude/skills/devops/references/kubernetes-kubectl.md +0 -74
- package/src/claude/skills/devops/references/kubernetes-security-advanced.md +0 -94
- package/src/claude/skills/devops/references/kubernetes-security.md +0 -89
- package/src/claude/skills/devops/references/kubernetes-troubleshooting-advanced.md +0 -74
- package/src/claude/skills/devops/references/kubernetes-troubleshooting.md +0 -49
- package/src/claude/skills/devops/references/kubernetes-workflows-advanced.md +0 -75
- package/src/claude/skills/devops/references/kubernetes-workflows.md +0 -76
- package/src/claude/skills/devops/scripts/cloudflare_deploy.py +0 -269
- package/src/claude/skills/devops/scripts/docker_optimize.py +0 -332
- package/src/claude/skills/devops/scripts/requirements.txt +0 -20
- package/src/claude/skills/devops/scripts/tests/requirements.txt +0 -3
- package/src/claude/skills/devops/scripts/tests/test_cloudflare_deploy.py +0 -285
- package/src/claude/skills/devops/scripts/tests/test_docker_optimize.py +0 -436
- package/src/claude/skills/frontend-design/SKILL.md +0 -135
- package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +0 -165
- package/src/claude/skills/frontend-design/references/analysis-best-practices.md +0 -80
- package/src/claude/skills/frontend-design/references/analysis-prompts.md +0 -141
- package/src/claude/skills/frontend-design/references/analysis-techniques.md +0 -118
- package/src/claude/skills/frontend-design/references/animejs.md +0 -396
- package/src/claude/skills/frontend-design/references/anti-slop-rules.md +0 -103
- package/src/claude/skills/frontend-design/references/asset-generation.md +0 -337
- package/src/claude/skills/frontend-design/references/bento-motion-engine.md +0 -142
- package/src/claude/skills/frontend-design/references/design-extraction-overview.md +0 -71
- package/src/claude/skills/frontend-design/references/extraction-best-practices.md +0 -141
- package/src/claude/skills/frontend-design/references/extraction-output-templates.md +0 -162
- package/src/claude/skills/frontend-design/references/extraction-prompts.md +0 -127
- package/src/claude/skills/frontend-design/references/magicui-components.md +0 -129
- package/src/claude/skills/frontend-design/references/performance-guardrails.md +0 -169
- package/src/claude/skills/frontend-design/references/premium-design-patterns.md +0 -93
- package/src/claude/skills/frontend-design/references/redesign-audit-checklist.md +0 -114
- package/src/claude/skills/frontend-design/references/technical-accessibility.md +0 -119
- package/src/claude/skills/frontend-design/references/technical-best-practices.md +0 -97
- package/src/claude/skills/frontend-design/references/technical-optimization.md +0 -44
- package/src/claude/skills/frontend-design/references/technical-overview.md +0 -90
- package/src/claude/skills/frontend-design/references/technical-workflows.md +0 -150
- package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +0 -95
- package/src/claude/skills/frontend-design/references/workflow-3d.md +0 -102
- package/src/claude/skills/frontend-design/references/workflow-describe.md +0 -87
- package/src/claude/skills/frontend-design/references/workflow-immersive.md +0 -87
- package/src/claude/skills/frontend-design/references/workflow-quick.md +0 -57
- package/src/claude/skills/frontend-design/references/workflow-screenshot.md +0 -63
- package/src/claude/skills/frontend-design/references/workflow-video.md +0 -74
- package/src/claude/skills/frontend-development/SKILL.md +0 -406
- package/src/claude/skills/frontend-development/resources/common-patterns.md +0 -331
- package/src/claude/skills/frontend-development/resources/complete-examples.md +0 -872
- package/src/claude/skills/frontend-development/resources/component-patterns.md +0 -502
- package/src/claude/skills/frontend-development/resources/data-fetching.md +0 -767
- package/src/claude/skills/frontend-development/resources/file-organization.md +0 -502
- package/src/claude/skills/frontend-development/resources/loading-and-error-states.md +0 -501
- package/src/claude/skills/frontend-development/resources/performance.md +0 -406
- package/src/claude/skills/frontend-development/resources/routing-guide.md +0 -364
- package/src/claude/skills/frontend-development/resources/styling-guide.md +0 -428
- package/src/claude/skills/frontend-development/resources/typescript-standards.md +0 -418
- package/src/claude/skills/mobile-development/SKILL.md +0 -218
- package/src/claude/skills/mobile-development/references/mobile-android.md +0 -604
- package/src/claude/skills/mobile-development/references/mobile-best-practices.md +0 -544
- package/src/claude/skills/mobile-development/references/mobile-debugging.md +0 -1089
- package/src/claude/skills/mobile-development/references/mobile-frameworks.md +0 -465
- package/src/claude/skills/mobile-development/references/mobile-ios.md +0 -496
- package/src/claude/skills/mobile-development/references/mobile-mindset.md +0 -542
- package/src/claude/skills/react-best-practices/AGENTS.md +0 -2249
- package/src/claude/skills/react-best-practices/README.md +0 -123
- package/src/claude/skills/react-best-practices/SKILL.md +0 -128
- package/src/claude/skills/react-best-practices/metadata.json +0 -15
- package/src/claude/skills/react-best-practices/rules/_sections.md +0 -46
- package/src/claude/skills/react-best-practices/rules/_template.md +0 -28
- package/src/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/src/claude/skills/react-best-practices/rules/advanced-use-latest.md +0 -49
- package/src/claude/skills/react-best-practices/rules/async-api-routes.md +0 -38
- package/src/claude/skills/react-best-practices/rules/async-defer-await.md +0 -80
- package/src/claude/skills/react-best-practices/rules/async-dependencies.md +0 -36
- package/src/claude/skills/react-best-practices/rules/async-parallel.md +0 -28
- package/src/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/src/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/src/claude/skills/react-best-practices/rules/bundle-conditional.md +0 -31
- package/src/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/src/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/src/claude/skills/react-best-practices/rules/bundle-preload.md +0 -50
- package/src/claude/skills/react-best-practices/rules/client-event-listeners.md +0 -74
- package/src/claude/skills/react-best-practices/rules/client-swr-dedup.md +0 -56
- package/src/claude/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
- package/src/claude/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
- package/src/claude/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
- package/src/claude/skills/react-best-practices/rules/js-cache-storage.md +0 -70
- package/src/claude/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
- package/src/claude/skills/react-best-practices/rules/js-early-exit.md +0 -50
- package/src/claude/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/src/claude/skills/react-best-practices/rules/js-index-maps.md +0 -37
- package/src/claude/skills/react-best-practices/rules/js-length-check-first.md +0 -49
- package/src/claude/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
- package/src/claude/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/src/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/src/claude/skills/react-best-practices/rules/rendering-activity.md +0 -26
- package/src/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/src/claude/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/src/claude/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/src/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/src/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/src/claude/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/src/claude/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/src/claude/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
- package/src/claude/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
- package/src/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/src/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/src/claude/skills/react-best-practices/rules/rerender-memo.md +0 -44
- package/src/claude/skills/react-best-practices/rules/rerender-transitions.md +0 -40
- package/src/claude/skills/react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/src/claude/skills/react-best-practices/rules/server-cache-lru.md +0 -41
- package/src/claude/skills/react-best-practices/rules/server-cache-react.md +0 -26
- package/src/claude/skills/react-best-practices/rules/server-parallel-fetching.md +0 -79
- package/src/claude/skills/react-best-practices/rules/server-serialization.md +0 -38
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert/strict');
|
|
4
|
+
const { createHash } = require('node:crypto');
|
|
4
5
|
const fs = require('node:fs');
|
|
5
6
|
const os = require('node:os');
|
|
6
7
|
const path = require('node:path');
|
|
@@ -8,6 +9,7 @@ const { spawnSync } = require('node:child_process');
|
|
|
8
9
|
const { test } = require('node:test');
|
|
9
10
|
|
|
10
11
|
const {
|
|
12
|
+
convertCodexAgentContent,
|
|
11
13
|
normalizeCodexBody,
|
|
12
14
|
transformManagedCodexContent,
|
|
13
15
|
upsertManagedCodexBlock
|
|
@@ -29,14 +31,85 @@ const MANIFEST = JSON.parse(
|
|
|
29
31
|
fs.readFileSync(path.join(PACKAGE_ROOT, 'src/claude/migration-manifest.json'), 'utf8')
|
|
30
32
|
);
|
|
31
33
|
const SPECS_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/specs');
|
|
34
|
+
const SPEC_MAKER_SOURCE_PATH = path.join(PACKAGE_ROOT, 'src/claude/agents/spec-maker.md');
|
|
35
|
+
const BRAINSTORM_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/brainstorm');
|
|
36
|
+
const BRAINSTORM_SKILL_SOURCE_PATH = path.join(BRAINSTORM_SOURCE_ROOT, 'SKILL.md');
|
|
37
|
+
const BRAINSTORM_REFERENCE_SOURCE_PATH = path.join(
|
|
38
|
+
BRAINSTORM_SOURCE_ROOT,
|
|
39
|
+
'references/question-framework.md'
|
|
40
|
+
);
|
|
41
|
+
const BRAINSTORM_AGENT_SOURCE_PATH = path.join(PACKAGE_ROOT, 'src/claude/agents/brainstormer.md');
|
|
42
|
+
const BRAINSTORM_SOURCE_PATHS = [
|
|
43
|
+
BRAINSTORM_SKILL_SOURCE_PATH,
|
|
44
|
+
BRAINSTORM_REFERENCE_SOURCE_PATH,
|
|
45
|
+
BRAINSTORM_AGENT_SOURCE_PATH
|
|
46
|
+
];
|
|
47
|
+
const RESEARCH_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/research');
|
|
48
|
+
const RESEARCH_SKILL_SOURCE_PATH = path.join(RESEARCH_SOURCE_ROOT, 'SKILL.md');
|
|
49
|
+
const RESEARCH_AGENT_SOURCE_PATH = path.join(PACKAGE_ROOT, 'src/claude/agents/researcher.md');
|
|
50
|
+
const DEVELOP_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/develop');
|
|
51
|
+
const DEVELOP_BUNDLE = [
|
|
52
|
+
'SKILL.md',
|
|
53
|
+
'references/quality-gate.md',
|
|
54
|
+
'references/parallel-waves.md',
|
|
55
|
+
'references/subagent-patterns.md'
|
|
56
|
+
];
|
|
57
|
+
const TEST_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/test');
|
|
58
|
+
const TEST_BUNDLE = [
|
|
59
|
+
'SKILL.md',
|
|
60
|
+
'references/execution-strategy.md',
|
|
61
|
+
'references/failure-triage.md',
|
|
62
|
+
'references/test-memory.md'
|
|
63
|
+
];
|
|
64
|
+
const TEST_RUNNER_SOURCE_PATH = path.join(PACKAGE_ROOT, 'src/claude/agents/test-runner.md');
|
|
65
|
+
const DOCS_ADAPTIVE_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/docs');
|
|
66
|
+
const DOCS_ADAPTIVE_BUNDLE = [
|
|
67
|
+
'SKILL.md',
|
|
68
|
+
'references/init-workflow.md',
|
|
69
|
+
'references/update-workflow.md',
|
|
70
|
+
'references/standard-docs-workflow.md'
|
|
71
|
+
];
|
|
72
|
+
const HOTFIX_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/hotfix');
|
|
73
|
+
const HOTFIX_BUNDLE = [
|
|
74
|
+
'SKILL.md',
|
|
75
|
+
'references/diagnosis-protocol.md',
|
|
76
|
+
'references/review-cycle.md',
|
|
77
|
+
'references/parallel-patterns.md',
|
|
78
|
+
'references/prevention-gate.md',
|
|
79
|
+
'references/workflow-specialized.md'
|
|
80
|
+
];
|
|
81
|
+
const DEBUG_SKILL_SOURCE_PATH = path.join(PACKAGE_ROOT, 'src/claude/skills/debug/SKILL.md');
|
|
82
|
+
const DEBUG_AGENT_SOURCE_PATH = path.join(PACKAGE_ROOT, 'src/claude/agents/debugger.md');
|
|
83
|
+
const DEBUG_REFERENCE_FILES = [
|
|
84
|
+
'core-philosophy.md',
|
|
85
|
+
'root-cause-tracing.md',
|
|
86
|
+
'verification-protocol.md',
|
|
87
|
+
'log-ci-analysis.md',
|
|
88
|
+
'parallel-agent-hydration.md',
|
|
89
|
+
'side-effect-gate.md'
|
|
90
|
+
];
|
|
91
|
+
const CODE_REVIEW_SOURCE_ROOT = path.join(PACKAGE_ROOT, 'src/claude/skills/code-review');
|
|
32
92
|
const IMPLEMENTATION_READINESS_BOUNDARY_ROWS = [
|
|
93
|
+
['Interaction/UI', 'entry journey; visible/loading/empty/error states; input/focus/keyboard; accessibility; responsive/native/device behavior'],
|
|
33
94
|
['API/CLI', 'entrypoint/route or command grammar; identity/auth; input/default/normalization; success output; error/status/exit; duplicate/retry/idempotency; compatibility'],
|
|
34
|
-
['
|
|
35
|
-
['
|
|
95
|
+
['Data/schema', 'authority/storage/transaction; version; exact keys/nesting/types; required/optional; enum/format/bounds/cardinality; unknown-field behavior; compatibility/migration'],
|
|
96
|
+
['Async/state', 'initial/terminal states; event + guard + effect + next + error; ordering/concurrency; duplicate/retry; writer/lock acquire/contention/release; cancellation; rollback/recovery'],
|
|
36
97
|
['Filesystem/security', 'authoritative root; trusted/untrusted segment grammar; lexical + canonical containment and symlink policy; flags/mode; temp/rename/fsync; lock/stale reclaim; crash cleanup'],
|
|
98
|
+
['Runtime/deploy', 'config/env/flags; registration/packaging; OS/arch; rollout/rollback; health/logging; operator recovery'],
|
|
37
99
|
['Time/retention', 'clock source; unit/precision/timezone; endpoints and inclusion/comparator; anomaly behavior; expiry/purge/recovery'],
|
|
100
|
+
['AI/model', 'provider/model/prompt/tool schema; nondeterminism/bounds; safety/privacy; fallback; cost/token limit; eval oracle'],
|
|
38
101
|
['Integration/proof', 'caller; export/registration; packaging/config; native path/consumer; proof level (`source`/`installed`/`live`); observable failure oracle']
|
|
39
102
|
];
|
|
103
|
+
|
|
104
|
+
function boundaryTableHasRequiredRows(table) {
|
|
105
|
+
const [header, ...rows] = table;
|
|
106
|
+
const labels = rows.map((row) => row[0]);
|
|
107
|
+
return JSON.stringify(header || []) === JSON.stringify(['Boundary', 'Required contract when material'])
|
|
108
|
+
&& new Set(labels).size === labels.length
|
|
109
|
+
&& IMPLEMENTATION_READINESS_BOUNDARY_ROWS.every((expected) =>
|
|
110
|
+
rows.some((row) => JSON.stringify(row) === JSON.stringify(expected)));
|
|
111
|
+
}
|
|
112
|
+
|
|
40
113
|
const IMPLEMENTATION_READINESS_CLAUSES = {
|
|
41
114
|
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 C1 or C2 question and block handoff.',
|
|
42
115
|
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,15 +117,16 @@ const IMPLEMENTATION_READINESS_CLAUSES = {
|
|
|
44
117
|
proofPlanLines: [
|
|
45
118
|
'- Command: `<exact runnable command>`',
|
|
46
119
|
'- Named probe: <existing concrete probe/test/hook ID; never only a suite label>',
|
|
47
|
-
'- Reachability: <
|
|
120
|
+
'- Reachability: <known command/caller/environment per required level; `UNKNOWN` only when the path cannot yet be established>',
|
|
48
121
|
'- Oracle: <externally observable success or failure>',
|
|
49
122
|
'- Counterexample: <material alternative behavior that must make this proof fail>',
|
|
50
123
|
'- Artifacts: <required artifact path plus digest algorithm/comparison rule, or explicitly ephemeral with cleanup rule>'
|
|
51
124
|
],
|
|
52
125
|
proofTrace: 'Trace `Command → Named probe → Reachability → Oracle`.',
|
|
53
|
-
namedProbeOwnership: 'Aggregate suites
|
|
54
|
-
proofLevelSeparation: '
|
|
126
|
+
namedProbeOwnership: 'Aggregate suites name the\nowning concrete probe.',
|
|
127
|
+
proofLevelSeparation: 'Levels stay separate and never promote one another.',
|
|
55
128
|
disposableTemplateControls: 'Run mutation or destructive\nnegative controls only on disposable copies under a verified temporary root,\nnever tracked worktree or canonical source bytes.',
|
|
129
|
+
proofLevelMapping: 'For every required level in each referenced CP row, map its named probe and\nreachability here; one command may own several explicitly named level probes.',
|
|
56
130
|
disposableReviewControls: 'Run mutation or destructive negative controls only on disposable copies below a verified temporary root, never tracked worktree or canonical source bytes.',
|
|
57
131
|
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.',
|
|
58
132
|
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.',
|
|
@@ -62,6 +136,48 @@ const IMPLEMENTATION_READINESS_CLAUSES = {
|
|
|
62
136
|
unknownBlocks: 'Only `PASS` closes it; `FAIL` remains open for the remaining paper-review round; `UNKNOWN` blocks implementation handoff.',
|
|
63
137
|
scopeReturnsToC1: 'A repair that adds user semantics or scope returns to C1.'
|
|
64
138
|
};
|
|
139
|
+
const ADAPTIVE_COVERAGE_PROFILE_HEADER = [
|
|
140
|
+
'ID', 'Outcome', 'Change kinds', 'Material surfaces', 'Ambiguity/action',
|
|
141
|
+
'Risk/evidence', 'Required proof'
|
|
142
|
+
];
|
|
143
|
+
const ADAPTIVE_COVERAGE_PROFILE_ROW = [
|
|
144
|
+
'CP-01', '<externally observable outcome>', '<all kinds>', '<all material surfaces>',
|
|
145
|
+
'<state + action>', '<level + evidence>', '<source/installed/live set>'
|
|
146
|
+
];
|
|
147
|
+
const ADAPTIVE_COVERAGE_AMBIGUITY_ROWS = [
|
|
148
|
+
['State', 'Required action'],
|
|
149
|
+
['`none`', 'proceed'],
|
|
150
|
+
['`examples-needed`', 'add two or three examples only for an already decided rule; promote to `decision-needed` if an example changes observable behavior'],
|
|
151
|
+
['`decision-needed`', 'ask the user at C1/C2 and keep affected tasks blocked'],
|
|
152
|
+
['`design-needed`', 'after user-owned decisions settle, route material competing technical designs through Brainstorm']
|
|
153
|
+
];
|
|
154
|
+
const ADAPTIVE_REVIEWER_ROWS = [
|
|
155
|
+
['Groups', 'Reviewers', 'Roles', 'Claim budget'],
|
|
156
|
+
['1-2', '2', 'Fact Checker plus all matching material lenses', 'about 5 per group'],
|
|
157
|
+
['3-5', '3', 'Fact Checker plus all matching material lenses', 'about 10 per group'],
|
|
158
|
+
['6+', '4', 'Fact Checker plus all matching material lenses', 'at least 15 total']
|
|
159
|
+
];
|
|
160
|
+
const ADAPTIVE_COVERAGE_CLAUSES = {
|
|
161
|
+
riskFirst: 'Classify material risk before choosing a workflow; user wording never lowers an observed floor.',
|
|
162
|
+
criticalFloor: '`critical`: auth/secrets/privacy; destructive/irreversible work or possible data loss/corruption; money/privilege/safety; production-state mutation.',
|
|
163
|
+
elevatedFloor: '`elevated`: cross-component contracts, compatibility, concurrency, external integration, or installed/runtime behavior.',
|
|
164
|
+
frontmatterGate: 'skip only when a change is clear, isolated, reversible, routine, and likely limited to one or two files.',
|
|
165
|
+
directGate: 'Work directly only when the cause and change are clear, isolated, reversible,\n`routine`, and likely limited to one or two files.',
|
|
166
|
+
splitRoute: 'Split three or more independent\nsubsystems; otherwise use one Specs packet for any material work that does not qualify for direct work or Brainstorm-only exploration.',
|
|
167
|
+
independentSubsystem: 'A subsystem is independent only when its outcome, boundary, and verification/deployment path can move through the lifecycle separately.',
|
|
168
|
+
profileAuthority: 'For a Specs route, `plan.md` owns one `## Coverage profile` row per externally observable outcome; direct and Brainstorm-only routes do not persist it.',
|
|
169
|
+
openKinds: 'Change kinds are multi-valued (`add`, `modify`, `fix`, `refactor`, `remove`, `migrate`, `integrate`), and unfamiliar kinds or surfaces use `other:<verbatim>` rather than disappearing.',
|
|
170
|
+
scopedUnion: 'Each task references its CP IDs; authoring, review, edge, and proof obligations union only inside affected rows/tasks.',
|
|
171
|
+
profileRederivation: 'Rederive affected CP rows after any accepted scope, outcome, criteria, ownership, dependency, risk, or proof delta before task status.',
|
|
172
|
+
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`/C3.',
|
|
173
|
+
proofSeparation: 'Levels stay separate and never promote one another.',
|
|
174
|
+
liveLimit: 'Source/static checks prove the written contract, not live-model adherence.',
|
|
175
|
+
specMakerAuthority: 'they are the canonical risk and coverage authority. Do not duplicate\ntheir taxonomy here.',
|
|
176
|
+
specMakerAmbiguity: 'Apply the canonical ambiguity action; examples never decide observable behavior.',
|
|
177
|
+
specMakerRoute: 'Apply their risk-first route before C1 and stop when the\nrequest qualifies for direct work; hand off when it requires Brainstorm-only exploration.',
|
|
178
|
+
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.',
|
|
179
|
+
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.'
|
|
180
|
+
};
|
|
65
181
|
const V3_SPECS_BUNDLE = [
|
|
66
182
|
'SKILL.md',
|
|
67
183
|
'references/review.md',
|
|
@@ -112,6 +228,21 @@ function install(root, extraArgs = []) {
|
|
|
112
228
|
);
|
|
113
229
|
}
|
|
114
230
|
|
|
231
|
+
function installPlatforms(root, platforms, extraArgs = []) {
|
|
232
|
+
return spawnSync(
|
|
233
|
+
process.execPath,
|
|
234
|
+
[INSTALLER, '--platform', platforms.join(','), '--yes', ...extraArgs],
|
|
235
|
+
{ cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } }
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function installedCatalog(root, runtime) {
|
|
240
|
+
const script = path.join(root, runtime === 'codex' ? '.codex' : '.claude', 'scripts', 'generate-skill-catalog.cjs');
|
|
241
|
+
const result = spawnSync(process.execPath, [script, '--json'], { cwd: root, encoding: 'utf8' });
|
|
242
|
+
assert.equal(result.status, 0, result.stderr);
|
|
243
|
+
return JSON.parse(result.stdout);
|
|
244
|
+
}
|
|
245
|
+
|
|
115
246
|
function allFiles(root, predicate) {
|
|
116
247
|
const found = [];
|
|
117
248
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
@@ -134,6 +265,13 @@ function parseGeneratedTomlString(content, key) {
|
|
|
134
265
|
return JSON.parse(match[1]);
|
|
135
266
|
}
|
|
136
267
|
|
|
268
|
+
function replaceGeneratedTomlString(content, key, value) {
|
|
269
|
+
const pattern = new RegExp(`^${key} = .+$`, 'gm');
|
|
270
|
+
const matches = [...content.matchAll(pattern)];
|
|
271
|
+
assert.equal(matches.length, 1, `expected exactly one TOML key: ${key}`);
|
|
272
|
+
return content.replace(pattern, `${key} = ${JSON.stringify(value)}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
137
275
|
function renderCanonicalVerificationExample(template) {
|
|
138
276
|
const values = new Map([
|
|
139
277
|
['SUBJECT_REQ', '1'],
|
|
@@ -227,30 +365,34 @@ function markdownBetweenHeadings(content, startHeading, endHeading) {
|
|
|
227
365
|
return value.slice(startIndex + startMarker.length, endIndex);
|
|
228
366
|
}
|
|
229
367
|
|
|
230
|
-
function implementationReadinessIssues(
|
|
231
|
-
|
|
368
|
+
function implementationReadinessIssues(input) {
|
|
369
|
+
const keys = input && typeof input === 'object' && !Array.isArray(input)
|
|
370
|
+
? Object.keys(input).sort()
|
|
371
|
+
: [];
|
|
372
|
+
if (keys.join(',') !== 'review,templates'
|
|
373
|
+
|| typeof input.templates !== 'string' || typeof input.review !== 'string') {
|
|
232
374
|
throw new TypeError('implementation-readiness checker expects templates and review UTF-8 strings');
|
|
233
375
|
}
|
|
234
376
|
|
|
235
377
|
const issues = new Set();
|
|
378
|
+
const { templates, review } = input;
|
|
236
379
|
const authoring = markdownSection(templates, 'No-invention and conditional boundary contracts');
|
|
237
380
|
if (!authoring.includes(IMPLEMENTATION_READINESS_CLAUSES.noInvention)) {
|
|
238
381
|
issues.add('no-invention');
|
|
239
382
|
}
|
|
383
|
+
const contradictoryNoInvention = /\b(?:exception|however)\b.{0,160}\bimplementation handoff\b.{0,80}\b(?:may|can)\s+(?:proceed|continue)\b.{0,160}\bunresolved\b/i;
|
|
384
|
+
if (contradictoryNoInvention.test(normalizeMarkdownWhitespace(authoring))) {
|
|
385
|
+
issues.add('no-invention');
|
|
386
|
+
}
|
|
240
387
|
|
|
241
388
|
const boundaryTable = markdownTableUnderHeading(
|
|
242
389
|
templates,
|
|
243
390
|
'No-invention and conditional boundary contracts'
|
|
244
391
|
);
|
|
245
|
-
const expectedBoundaryTable = [
|
|
246
|
-
['Boundary', 'Required contract when material'],
|
|
247
|
-
...IMPLEMENTATION_READINESS_BOUNDARY_ROWS
|
|
248
|
-
];
|
|
249
392
|
if (!authoring.includes(IMPLEMENTATION_READINESS_CLAUSES.materialDefinition)
|
|
250
393
|
|| !authoring.includes(IMPLEMENTATION_READINESS_CLAUSES.exactBoundaryChoices)
|
|
251
|
-
|| IMPLEMENTATION_READINESS_BOUNDARY_ROWS.length !==
|
|
252
|
-
|| boundaryTable
|
|
253
|
-
|| JSON.stringify(boundaryTable) !== JSON.stringify(expectedBoundaryTable)) {
|
|
394
|
+
|| IMPLEMENTATION_READINESS_BOUNDARY_ROWS.length !== 9
|
|
395
|
+
|| !boundaryTableHasRequiredRows(boundaryTable)) {
|
|
254
396
|
issues.add('boundary-contract');
|
|
255
397
|
}
|
|
256
398
|
|
|
@@ -265,7 +407,8 @@ function implementationReadinessIssues(templates, review) {
|
|
|
265
407
|
IMPLEMENTATION_READINESS_CLAUSES.proofTrace,
|
|
266
408
|
IMPLEMENTATION_READINESS_CLAUSES.namedProbeOwnership,
|
|
267
409
|
IMPLEMENTATION_READINESS_CLAUSES.proofLevelSeparation,
|
|
268
|
-
IMPLEMENTATION_READINESS_CLAUSES.disposableTemplateControls
|
|
410
|
+
IMPLEMENTATION_READINESS_CLAUSES.disposableTemplateControls,
|
|
411
|
+
IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping
|
|
269
412
|
].map(normalizeMarkdownWhitespace);
|
|
270
413
|
const reviewNegativeControls = normalizeMarkdownWhitespace(
|
|
271
414
|
markdownSection(review, 'B2 — fresh-context red team')
|
|
@@ -305,9 +448,107 @@ function implementationReadinessIssues(templates, review) {
|
|
|
305
448
|
return [...issues].sort();
|
|
306
449
|
}
|
|
307
450
|
|
|
451
|
+
function adaptiveCoverageIssues(input) {
|
|
452
|
+
const expectedKeys = ['review', 'skill', 'specMaker', 'templates'];
|
|
453
|
+
const keys = input && typeof input === 'object' && !Array.isArray(input)
|
|
454
|
+
? Object.keys(input).sort()
|
|
455
|
+
: [];
|
|
456
|
+
if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)
|
|
457
|
+
|| expectedKeys.some((key) => typeof input[key] !== 'string')) {
|
|
458
|
+
throw new TypeError('adaptive-coverage checker expects skill, specMaker, templates, and review UTF-8 strings');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const issues = new Set();
|
|
462
|
+
const skill = normalizeMarkdownWhitespace(input.skill);
|
|
463
|
+
const templates = normalizeMarkdownWhitespace(input.templates);
|
|
464
|
+
const review = normalizeMarkdownWhitespace(input.review);
|
|
465
|
+
const specMaker = normalizeMarkdownWhitespace(input.specMaker);
|
|
466
|
+
const has = (source, clause) => source.includes(normalizeMarkdownWhitespace(clause));
|
|
467
|
+
|
|
468
|
+
const riskClauses = [
|
|
469
|
+
ADAPTIVE_COVERAGE_CLAUSES.riskFirst,
|
|
470
|
+
ADAPTIVE_COVERAGE_CLAUSES.criticalFloor,
|
|
471
|
+
ADAPTIVE_COVERAGE_CLAUSES.elevatedFloor,
|
|
472
|
+
ADAPTIVE_COVERAGE_CLAUSES.frontmatterGate,
|
|
473
|
+
ADAPTIVE_COVERAGE_CLAUSES.directGate,
|
|
474
|
+
ADAPTIVE_COVERAGE_CLAUSES.splitRoute,
|
|
475
|
+
ADAPTIVE_COVERAGE_CLAUSES.independentSubsystem
|
|
476
|
+
];
|
|
477
|
+
const riskDowngrade = /\buser\b.{0,80}\b(?:may|can)\b.{0,80}\blower\b.{0,40}\brisk\b/i;
|
|
478
|
+
const riskyDirectOverride = /\b(?:exception|even if|regardless)\b.{0,160}\b(?:auth|secret|privacy|destructive|irreversible|data loss|corruption|production[- ]state|critical)\b.{0,160}\b(?:direct|work directly|go direct)\b/i;
|
|
479
|
+
if (riskClauses.some((clause) => !has(skill, clause))
|
|
480
|
+
|| riskDowngrade.test(skill) || riskyDirectOverride.test(skill)) {
|
|
481
|
+
issues.add('risk-first-routing');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const profileTable = markdownTableUnderHeading(input.templates, 'Coverage profile');
|
|
485
|
+
const profileHeadingCount = (input.templates.match(/^## Coverage profile\s*$/gm) || []).length;
|
|
486
|
+
const taskReferencesCoverage = /## Coverage\s*\n- <exact `CP-NN` IDs owned by this task>/.test(input.templates);
|
|
487
|
+
if (JSON.stringify(profileTable[0] || []) !== JSON.stringify(ADAPTIVE_COVERAGE_PROFILE_HEADER)
|
|
488
|
+
|| profileHeadingCount !== 1 || profileTable.length !== 2
|
|
489
|
+
|| JSON.stringify(profileTable[1]) !== JSON.stringify(ADAPTIVE_COVERAGE_PROFILE_ROW)
|
|
490
|
+
|| !taskReferencesCoverage
|
|
491
|
+
|| !has(templates, ADAPTIVE_COVERAGE_CLAUSES.profileAuthority)
|
|
492
|
+
|| !has(templates, ADAPTIVE_COVERAGE_CLAUSES.openKinds)) {
|
|
493
|
+
issues.add('coverage-profile-shape');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const ambiguityTable = markdownTableUnderHeading(input.templates, 'Example Mapping rule');
|
|
497
|
+
if (JSON.stringify(ambiguityTable) !== JSON.stringify(ADAPTIVE_COVERAGE_AMBIGUITY_ROWS)
|
|
498
|
+
|| !templates.includes('retention of 30 versus 90 days is `decision-needed`')) {
|
|
499
|
+
issues.add('ambiguity-actions');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const globalCeremony = /\b(?:critical|security|failure|proof|review|edge|obligations?|lenses?)\b[^.!?\n]{0,120}\b(?:union|apply|spread|require)\w*\b[^.!?\n]{0,120}\b(?:all|every)\s+(?:cp\s+)?(?:rows?|outcomes?|tasks?)\b|\b(?:union|apply|spread)\w*\b[^.!?\n]{0,120}\b(?:all|every)\s+(?:cp\s+)?(?:rows?|outcomes?|tasks?)\b/i;
|
|
503
|
+
if (!has(templates, ADAPTIVE_COVERAGE_CLAUSES.scopedUnion)
|
|
504
|
+
|| globalCeremony.test(templates)
|
|
505
|
+
|| !review.includes('nonmaterial lenses are not added')) {
|
|
506
|
+
issues.add('scoped-coverage');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const rederiveSources = [templates, skill, specMaker, review];
|
|
510
|
+
if (!has(templates, ADAPTIVE_COVERAGE_CLAUSES.profileRederivation)
|
|
511
|
+
|| rederiveSources.some((source) => !/rederive affected cp rows?/.test(source.toLowerCase()))) {
|
|
512
|
+
issues.add('profile-lifecycle');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const statusMatrix = markdownTableUnderHeading(input.templates, 'Status matrix');
|
|
516
|
+
if (!has(templates, ADAPTIVE_COVERAGE_CLAUSES.plannedProof)
|
|
517
|
+
|| !has(templates, ADAPTIVE_COVERAGE_CLAUSES.proofSeparation)
|
|
518
|
+
|| !has(templates, IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping)
|
|
519
|
+
|| !has(skill, ADAPTIVE_COVERAGE_CLAUSES.liveLimit)
|
|
520
|
+
|| !statusMatrix.some((row) => row[0] === 'accepted finding open or `UNKNOWN` reachability' && row[1] === '`blocked`')) {
|
|
521
|
+
issues.add('proof-lifecycle');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const reviewerRowsPresent = ADAPTIVE_REVIEWER_ROWS.every((row) =>
|
|
525
|
+
input.review.includes(`| ${row.join(' | ')} |`));
|
|
526
|
+
if (!has(review, ADAPTIVE_COVERAGE_CLAUSES.reviewRisk)
|
|
527
|
+
|| !has(review, ADAPTIVE_COVERAGE_CLAUSES.reviewCapacity)
|
|
528
|
+
|| !reviewerRowsPresent) {
|
|
529
|
+
issues.add('reviewer-routing');
|
|
530
|
+
}
|
|
531
|
+
if (!specMaker.includes('skills/specs/SKILL.md')
|
|
532
|
+
|| !specMaker.includes('skills/specs/references/templates.md')
|
|
533
|
+
|| !has(specMaker, ADAPTIVE_COVERAGE_CLAUSES.specMakerAuthority)
|
|
534
|
+
|| !has(specMaker, ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity)
|
|
535
|
+
|| !has(specMaker, ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute)) {
|
|
536
|
+
issues.add('spec-maker-authority');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const boundaryTable = markdownTableUnderHeading(
|
|
540
|
+
input.templates,
|
|
541
|
+
'No-invention and conditional boundary contracts'
|
|
542
|
+
);
|
|
543
|
+
if (!boundaryTableHasRequiredRows(boundaryTable)) {
|
|
544
|
+
issues.add('adaptive-boundary-lenses');
|
|
545
|
+
}
|
|
546
|
+
return [...issues].sort();
|
|
547
|
+
}
|
|
548
|
+
|
|
308
549
|
function authoringProjectionIssues(files) {
|
|
309
550
|
const issues = [];
|
|
310
|
-
issues.push(...implementationReadinessIssues(files.templates, files.review));
|
|
551
|
+
issues.push(...implementationReadinessIssues({ templates: files.templates, review: files.review }));
|
|
311
552
|
const foreignRuntimeBoundary = 'If you are Claude Code or any other runtime, ignore this entire Codex block';
|
|
312
553
|
if (!files.skill.includes('Task files are flat beside `plan.md`')
|
|
313
554
|
|| !files.skill.includes('Do not create a nested task directory.')) {
|
|
@@ -334,7 +575,7 @@ function authoringProjectionIssues(files) {
|
|
|
334
575
|
]) {
|
|
335
576
|
if (!files.templates.includes(field)) issues.push(`receipt-${field.toLowerCase()}`);
|
|
336
577
|
}
|
|
337
|
-
if (!/^name:
|
|
578
|
+
if (!/^name: cf-specs$/m.test(files.skill)) issues.push('codex-skill-name');
|
|
338
579
|
const bundle = [files.skill, files.review, files.templates, files.legacyTemplates].join('\n');
|
|
339
580
|
const runtimeProjection = `${bundle}\n${files.codex.replace(foreignRuntimeBoundary, '')}`;
|
|
340
581
|
if (!files.codex.includes(foreignRuntimeBoundary)
|
|
@@ -343,11 +584,11 @@ function authoringProjectionIssues(files) {
|
|
|
343
584
|
}
|
|
344
585
|
for (const claudeOnly of [
|
|
345
586
|
'AskUserQuestion', 'TaskCreate', 'TaskGet', 'TaskUpdate', 'TaskList',
|
|
346
|
-
'WebSearch', 'WebFetch', 'SendMessage', 'Claude Code', '.claude', '/
|
|
587
|
+
'WebSearch', 'WebFetch', 'SendMessage', 'Claude Code', '.claude', '/cf:', 'cf:'
|
|
347
588
|
]) {
|
|
348
589
|
if (runtimeProjection.includes(claudeOnly)) issues.push(`claude-vocabulary-${claudeOnly}`);
|
|
349
590
|
}
|
|
350
|
-
for (const command of ['$
|
|
591
|
+
for (const command of ['$cf-specs', '$cf-develop', '$cf-sync']) {
|
|
351
592
|
if (!files.codex.includes(command)) issues.push(`codex-command-${command}`);
|
|
352
593
|
}
|
|
353
594
|
for (const state of ['pending', 'in_progress', 'paused', 'blocked', 'done']) {
|
|
@@ -379,10 +620,15 @@ function canonicalInstalledSpecsRoot(projectRoot, installedRoot) {
|
|
|
379
620
|
function readInstalledAuthoringProjection(projectRoot, installedRoot) {
|
|
380
621
|
const canonicalRoot = canonicalInstalledSpecsRoot(projectRoot, installedRoot);
|
|
381
622
|
const read = (relative) => fs.readFileSync(path.join(canonicalRoot, relative), 'utf8');
|
|
623
|
+
const specMakerToml = fs.readFileSync(
|
|
624
|
+
path.join(projectRoot, '.codex/agents/spec_maker.toml'),
|
|
625
|
+
'utf8'
|
|
626
|
+
);
|
|
382
627
|
return {
|
|
383
628
|
skill: read('SKILL.md'),
|
|
384
629
|
review: read('references/review.md'),
|
|
385
630
|
templates: read('references/templates.md'),
|
|
631
|
+
specMaker: parseGeneratedTomlString(specMakerToml, 'developer_instructions'),
|
|
386
632
|
legacyTemplates: V3_SPECS_BUNDLE.filter((relative) => relative.startsWith('templates/')).map(read).join('\n'),
|
|
387
633
|
codex: [
|
|
388
634
|
fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'),
|
|
@@ -395,21 +641,40 @@ function installedAuthoringProjectionIssues(projectRoot, installedRoot) {
|
|
|
395
641
|
return authoringProjectionIssues(readInstalledAuthoringProjection(projectRoot, installedRoot));
|
|
396
642
|
}
|
|
397
643
|
|
|
398
|
-
function
|
|
644
|
+
function installedAdaptiveCoverageIssues(projectRoot, installedRoot) {
|
|
645
|
+
const { skill, specMaker, templates, review } = readInstalledAuthoringProjection(
|
|
646
|
+
projectRoot,
|
|
647
|
+
installedRoot
|
|
648
|
+
);
|
|
649
|
+
return adaptiveCoverageIssues({ skill, specMaker, templates, review });
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function assertDisposableInstalledMutationTarget({
|
|
653
|
+
projectRoot,
|
|
654
|
+
installedScope,
|
|
655
|
+
targetPath,
|
|
656
|
+
expectedTargetPath,
|
|
657
|
+
canonicalSourcePaths,
|
|
658
|
+
scopeLabel
|
|
659
|
+
}) {
|
|
399
660
|
const canonicalProject = fs.realpathSync(projectRoot);
|
|
400
|
-
const
|
|
401
|
-
const targetPath = path.join(canonicalInstalled, relative);
|
|
661
|
+
const canonicalScope = fs.realpathSync(installedScope);
|
|
402
662
|
const targetLstat = fs.lstatSync(targetPath);
|
|
403
663
|
assert.equal(targetLstat.isSymbolicLink(), false, 'installed mutation target must not be a symlink');
|
|
404
664
|
assert.equal(targetLstat.isFile(), true, 'installed mutation target must be a regular file');
|
|
405
665
|
const canonicalTarget = fs.realpathSync(targetPath);
|
|
406
|
-
|
|
666
|
+
assert.equal(
|
|
667
|
+
canonicalTarget,
|
|
668
|
+
fs.realpathSync(expectedTargetPath),
|
|
669
|
+
`installed mutation target must be the exact ${scopeLabel} projection`
|
|
670
|
+
);
|
|
671
|
+
const scopeRelative = path.relative(canonicalScope, canonicalTarget);
|
|
407
672
|
const projectRelative = path.relative(canonicalProject, canonicalTarget);
|
|
408
673
|
assert.ok(
|
|
409
|
-
|
|
410
|
-
&& !
|
|
411
|
-
&& !path.isAbsolute(
|
|
412
|
-
|
|
674
|
+
scopeRelative && scopeRelative !== '..'
|
|
675
|
+
&& !scopeRelative.startsWith(`..${path.sep}`)
|
|
676
|
+
&& !path.isAbsolute(scopeRelative),
|
|
677
|
+
`installed mutation target must stay inside ${scopeLabel}`
|
|
413
678
|
);
|
|
414
679
|
assert.ok(
|
|
415
680
|
projectRelative && projectRelative !== '..'
|
|
@@ -417,28 +682,257 @@ function assertInstalledMutationTarget(projectRoot, installedRoot, relative) {
|
|
|
417
682
|
&& !path.isAbsolute(projectRelative),
|
|
418
683
|
'installed mutation target must stay inside the disposable project'
|
|
419
684
|
);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
canonicalSource,
|
|
424
|
-
'installed mutation target must not resolve to canonical source'
|
|
685
|
+
assert.ok(
|
|
686
|
+
Array.isArray(canonicalSourcePaths) && canonicalSourcePaths.length > 0,
|
|
687
|
+
'installed mutation guard requires the full canonical source set'
|
|
425
688
|
);
|
|
426
689
|
const targetStat = fs.statSync(canonicalTarget);
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
690
|
+
for (const sourcePath of canonicalSourcePaths) {
|
|
691
|
+
const canonicalSource = fs.realpathSync(sourcePath);
|
|
692
|
+
assert.notEqual(
|
|
693
|
+
canonicalTarget,
|
|
694
|
+
canonicalSource,
|
|
695
|
+
'installed mutation target must not resolve to any canonical source'
|
|
696
|
+
);
|
|
697
|
+
const sourceStat = fs.statSync(canonicalSource);
|
|
698
|
+
assert.notDeepEqual(
|
|
699
|
+
[targetStat.dev, targetStat.ino],
|
|
700
|
+
[sourceStat.dev, sourceStat.ino],
|
|
701
|
+
`installed mutation target must not share an inode with canonical source set: ${sourcePath}`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
assert.equal(targetStat.nlink, 1, 'installed mutation target must have exactly one hard link');
|
|
433
705
|
return canonicalTarget;
|
|
434
706
|
}
|
|
435
707
|
|
|
708
|
+
function assertInstalledMutationTarget(projectRoot, installedRoot, relative) {
|
|
709
|
+
const canonicalInstalled = canonicalInstalledSpecsRoot(projectRoot, installedRoot);
|
|
710
|
+
const targetPath = path.join(canonicalInstalled, relative);
|
|
711
|
+
return assertDisposableInstalledMutationTarget({
|
|
712
|
+
projectRoot,
|
|
713
|
+
installedScope: canonicalInstalled,
|
|
714
|
+
targetPath,
|
|
715
|
+
expectedTargetPath: path.join(projectRoot, '.agents/skills/specs', relative),
|
|
716
|
+
canonicalSourcePaths: canonicalProjectionSourcePaths(),
|
|
717
|
+
scopeLabel: '.agents/skills/specs'
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function assertInstalledSpecMakerMutationTarget(projectRoot) {
|
|
722
|
+
const installedScope = path.join(projectRoot, '.codex/agents');
|
|
723
|
+
const targetPath = path.join(installedScope, 'spec_maker.toml');
|
|
724
|
+
return assertDisposableInstalledMutationTarget({
|
|
725
|
+
projectRoot,
|
|
726
|
+
installedScope,
|
|
727
|
+
targetPath,
|
|
728
|
+
expectedTargetPath: targetPath,
|
|
729
|
+
canonicalSourcePaths: canonicalProjectionSourcePaths(),
|
|
730
|
+
scopeLabel: '.codex/agents'
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function canonicalProjectionSourcePaths() {
|
|
735
|
+
return [
|
|
736
|
+
...V3_SPECS_BUNDLE.map((relative) => path.join(SPECS_SOURCE_ROOT, relative)),
|
|
737
|
+
SPEC_MAKER_SOURCE_PATH
|
|
738
|
+
];
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function canonicalProjectionSourceBytes() {
|
|
742
|
+
const sourcePaths = canonicalProjectionSourcePaths();
|
|
743
|
+
return new Map(sourcePaths.map((sourcePath) => [
|
|
744
|
+
sourcePath,
|
|
745
|
+
fs.readFileSync(sourcePath)
|
|
746
|
+
]));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function sha256(content) {
|
|
750
|
+
return createHash('sha256').update(content).digest('hex');
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function canonicalBrainstormSourceBytes() {
|
|
754
|
+
return new Map(BRAINSTORM_SOURCE_PATHS.map((sourcePath) => [
|
|
755
|
+
sourcePath,
|
|
756
|
+
fs.readFileSync(sourcePath)
|
|
757
|
+
]));
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function assertCanonicalBrainstormSourceBytesUnchanged(sourceBytes) {
|
|
761
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
762
|
+
const actual = fs.readFileSync(sourcePath);
|
|
763
|
+
assert.deepEqual(
|
|
764
|
+
actual,
|
|
765
|
+
expected,
|
|
766
|
+
`canonical Brainstorm source changed: ${path.relative(PACKAGE_ROOT, sourcePath)}`
|
|
767
|
+
);
|
|
768
|
+
assert.equal(sha256(actual), sha256(expected));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function readInstalledBrainstormProjection(projectRoot) {
|
|
773
|
+
const skillRoot = path.join(projectRoot, '.agents/skills/brainstorm');
|
|
774
|
+
const agentPath = path.join(projectRoot, '.codex/agents/brainstormer.toml');
|
|
775
|
+
return {
|
|
776
|
+
skill: fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8'),
|
|
777
|
+
framework: fs.readFileSync(path.join(skillRoot, 'references/question-framework.md'), 'utf8'),
|
|
778
|
+
agent: parseGeneratedTomlString(fs.readFileSync(agentPath, 'utf8'), 'developer_instructions')
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function brainstormProjectionClausePrefix(content, index) {
|
|
783
|
+
const starts = ['.', '!', '?', ';', '\n'].map((marker) => content.lastIndexOf(marker, index - 1));
|
|
784
|
+
return content.slice(Math.max(...starts) + 1, index).trim();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function brainstormProjectionIsLocallyNegated(content, index) {
|
|
788
|
+
const clausePrefix = brainstormProjectionClausePrefix(content, index);
|
|
789
|
+
const localBoundary = Math.max(
|
|
790
|
+
clausePrefix.lastIndexOf(','),
|
|
791
|
+
clausePrefix.lastIndexOf(':'),
|
|
792
|
+
clausePrefix.lastIndexOf('—')
|
|
793
|
+
);
|
|
794
|
+
const localPrefix = clausePrefix.slice(localBoundary + 1).trim().toLowerCase();
|
|
795
|
+
const directNegation = /\b(?:never|do not|does not|must not|may not|should not|will not|cannot|can't|is forbidden to|are forbidden to|is not (?:permitted|allowed) to|are not (?:permitted|allowed) to|not (?:permitted|allowed) to)(?:\s+(?:ever|directly|automatically|implicitly|explicitly|immediately|intentionally|silently))*\s*$/;
|
|
796
|
+
if (directNegation.test(localPrefix)) return true;
|
|
797
|
+
const fullPrefix = clausePrefix.toLowerCase();
|
|
798
|
+
if (/\b(?:but|yet|however|except|instead)\b[^.!?;\n]*$/.test(fullPrefix)) return false;
|
|
799
|
+
return /\b(?:never|do not|does not|must not|may not|should not|will not|cannot|can't|is forbidden to|are forbidden to|is not (?:permitted|allowed) to|are not (?:permitted|allowed) to|not (?:permitted|allowed) to)\b(?:(?!\b(?:but|yet|however|except|instead)\b).){0,160}\b(?:and|or|nor)(?:\s+then)?(?:\s+(?:ever|directly|automatically|implicitly|explicitly|immediately|intentionally|silently))*\s*$/.test(fullPrefix);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function brainstormProjectionHasUnauthorizedAuthority(content) {
|
|
803
|
+
const actionPattern = /\b(?:ask|question|contact|invoke|start|dispatch|launch|route|forward|run|execute|hand(?:\s+|-)?off|write|edit|update|mutate|delegate|claim)\b/gi;
|
|
804
|
+
const actions = [...content.matchAll(actionPattern)];
|
|
805
|
+
for (let index = 0; index < actions.length; index += 1) {
|
|
806
|
+
const match = actions[index];
|
|
807
|
+
const normalizedAction = match[0].toLowerCase().replace(/[-\s]+/g, ' ');
|
|
808
|
+
const action = normalizedAction === 'handoff' ? 'hand off' : normalizedAction;
|
|
809
|
+
const start = match.index + match[0].length;
|
|
810
|
+
const nextAction = actions[index + 1]?.index ?? content.length;
|
|
811
|
+
const punctuation = content.slice(start).search(/[.!?;\n]/);
|
|
812
|
+
const punctuationEnd = punctuation < 0 ? content.length : start + punctuation;
|
|
813
|
+
const tail = content.slice(start, Math.min(start + 100, nextAction, punctuationEnd));
|
|
814
|
+
const hasTarget = ['ask', 'question', 'contact'].includes(action)
|
|
815
|
+
? /\b(?:the )?user\b/i.test(tail)
|
|
816
|
+
: ['invoke', 'start', 'dispatch', 'launch', 'route', 'forward', 'run', 'execute', 'hand off'].includes(action)
|
|
817
|
+
? /\b(?:Specs|Fix|Develop)\b/i.test(tail)
|
|
818
|
+
: ['write', 'edit', 'update', 'mutate'].includes(action)
|
|
819
|
+
? /\b(?:files?|task state|shared state)\b/i.test(tail)
|
|
820
|
+
: action === 'delegate'
|
|
821
|
+
? /\b(?:work|tasks?)\b/i.test(tail)
|
|
822
|
+
: /\bapproval\b/i.test(tail);
|
|
823
|
+
if (hasTarget && !brainstormProjectionIsLocallyNegated(content, match.index)) return true;
|
|
824
|
+
}
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function brainstormProjectionIssues(files) {
|
|
829
|
+
const compact = (value) => String(value).replace(/\s+/g, ' ').trim();
|
|
830
|
+
const skill = compact(files.skill);
|
|
831
|
+
const framework = compact(files.framework);
|
|
832
|
+
const agent = compact(files.agent);
|
|
833
|
+
const issues = new Set();
|
|
834
|
+
const specialistBoundary = 'Do not ask the user directly, write files, mutate shared task state, delegate work, invoke Specs/Fix/Develop, or claim approval.';
|
|
835
|
+
const specialistHandoff = 'Non-bug exploration may end in chat; feature/docs delivery may only prepare a future explicit Specs invocation; bug handoff requires evidenced root cause and the user\'s explicit fix request.';
|
|
836
|
+
const agentAuthorityRemainder = agent
|
|
837
|
+
.replace(specialistBoundary, '')
|
|
838
|
+
.replace(specialistHandoff, '');
|
|
839
|
+
const adaptiveClauses = {
|
|
840
|
+
'adaptive-direct-precedence': 'Route Direct first, then apply controls only to requests that remain in Brainstorm.',
|
|
841
|
+
'adaptive-ordered-depth': 'With no Deep signal, use Standard. `--deep` raises Standard to Deep.',
|
|
842
|
+
'adaptive-leading-flags': 'Parse controls only from the leading consecutive token segment.',
|
|
843
|
+
'adaptive-lens-trigger-skip': 'failure isolation for partial or cascading failure across boundaries',
|
|
844
|
+
'adaptive-evidence-semantics': 'Missing evidence forces feasibility `unknown` and confidence `low`.',
|
|
845
|
+
'adaptive-numeric-estimates': 'A numeric estimate requires range, unit, basis, evidence, and assumptions; otherwise report `unknown`.',
|
|
846
|
+
'adaptive-pre-tool-redaction': 'Before an external visual tool or adviser handoff, minimize context and redact secrets, credentials, private keys, access tokens, and unnecessary PII.',
|
|
847
|
+
'adaptive-adviser-gate': '`--advice` invokes `brainstormer` only after the material-choice gate;',
|
|
848
|
+
'adaptive-decision-freshness': '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.',
|
|
849
|
+
'adaptive-non-authority': 'Neither overlay writes, approves, persists, dispatches, or completes work.'
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
if (!skill.includes('leave Brainstorm before scout, questions, approval, or persistence.')
|
|
853
|
+
|| !skill.includes('Hydration is not a terminal route; continue to exactly one intent route below.')) {
|
|
854
|
+
issues.add('front-door-routing');
|
|
855
|
+
}
|
|
856
|
+
if (!skill.includes('Then use `cf-debug` until root cause is evidenced.')
|
|
857
|
+
|| !skill.includes('Hand off to `cf-fix` only when the user explicitly requested a fix')) {
|
|
858
|
+
issues.add('bug-routing');
|
|
859
|
+
}
|
|
860
|
+
if (!skill.includes('Do not request design approval, persist a report, or invoke another workflow without a new explicit request.')) {
|
|
861
|
+
issues.add('exploration-stop');
|
|
862
|
+
}
|
|
863
|
+
if (!skill.includes('For a material choice, compare 2–3 mechanically distinct viable approaches')
|
|
864
|
+
|| !skill.includes('Never create strawman options.')
|
|
865
|
+
|| !agent.includes('never invent strawmen to fill a quota.')) {
|
|
866
|
+
issues.add('option-cardinality');
|
|
867
|
+
}
|
|
868
|
+
if (!agent.includes('You advise `cf-brainstorm`; you do not replace its routing, question, approval, persistence, or handoff ownership.')
|
|
869
|
+
|| !agent.includes(specialistBoundary)
|
|
870
|
+
|| brainstormProjectionHasUnauthorizedAuthority(agentAuthorityRemainder)) {
|
|
871
|
+
issues.add('specialist-boundary');
|
|
872
|
+
}
|
|
873
|
+
if (!agent.includes('If the request is a symptom without an evidenced root cause, return it to `cf-debug`.')
|
|
874
|
+
|| !agent.includes('If the controller has not identified whether the work is feature delivery, an explicitly authorized fix, or non-bug exploration, request that routing context instead of guessing.')) {
|
|
875
|
+
issues.add('specialist-routing');
|
|
876
|
+
}
|
|
877
|
+
if (!agent.includes(specialistHandoff)) {
|
|
878
|
+
issues.add('specialist-handoff');
|
|
879
|
+
}
|
|
880
|
+
if (!skill.includes('Persist only approved decisions and semantics, only with user authority')
|
|
881
|
+
|| !skill.includes('redact live secrets, credentials, private keys, access tokens, and unnecessary PII;')
|
|
882
|
+
|| !framework.includes('Do not write "user selected" unless direct user text or the native input tool confirms it.')) {
|
|
883
|
+
issues.add('approval-persistence');
|
|
884
|
+
}
|
|
885
|
+
for (const [issue, clause] of Object.entries(adaptiveClauses)) {
|
|
886
|
+
if (!skill.includes(clause)) issues.add(issue);
|
|
887
|
+
}
|
|
888
|
+
const visible = `${skill}\n${framework}\n${agent}`;
|
|
889
|
+
if (/\bAskUserQuestion\b/.test(visible)
|
|
890
|
+
|| /\b(?:a|an|one|each|every|another|this|that|the|[0-9]+)\s+a structured user-input request\b/i.test(visible)
|
|
891
|
+
|| /\ban structured user-input request\b/i.test(visible)
|
|
892
|
+
|| /\ba structured user-input request\s+(?:calls|batches)\b/i.test(visible)) {
|
|
893
|
+
issues.add('projection-grammar');
|
|
894
|
+
}
|
|
895
|
+
return [...issues].sort();
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function assertInstalledBrainstormMutationTarget(projectRoot, source) {
|
|
899
|
+
const skillRoot = path.join(projectRoot, '.agents/skills/brainstorm');
|
|
900
|
+
if (source === 'agent') {
|
|
901
|
+
const agentRoot = path.join(projectRoot, '.codex/agents');
|
|
902
|
+
const targetPath = path.join(agentRoot, 'brainstormer.toml');
|
|
903
|
+
return assertDisposableInstalledMutationTarget({
|
|
904
|
+
projectRoot,
|
|
905
|
+
installedScope: agentRoot,
|
|
906
|
+
targetPath,
|
|
907
|
+
expectedTargetPath: targetPath,
|
|
908
|
+
canonicalSourcePaths: BRAINSTORM_SOURCE_PATHS,
|
|
909
|
+
scopeLabel: '.codex/agents'
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
const relative = source === 'skill' ? 'SKILL.md' : 'references/question-framework.md';
|
|
913
|
+
const targetPath = path.join(skillRoot, relative);
|
|
914
|
+
return assertDisposableInstalledMutationTarget({
|
|
915
|
+
projectRoot,
|
|
916
|
+
installedScope: skillRoot,
|
|
917
|
+
targetPath,
|
|
918
|
+
expectedTargetPath: targetPath,
|
|
919
|
+
canonicalSourcePaths: BRAINSTORM_SOURCE_PATHS,
|
|
920
|
+
scopeLabel: '.agents/skills/brainstorm'
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
|
|
436
924
|
function assertCanonicalSpecsSourceBytesUnchanged(sourceBytes) {
|
|
437
|
-
for (const [
|
|
925
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
926
|
+
const actual = fs.readFileSync(sourcePath);
|
|
438
927
|
assert.deepEqual(
|
|
439
|
-
|
|
928
|
+
actual,
|
|
440
929
|
expected,
|
|
441
|
-
`canonical
|
|
930
|
+
`canonical source bytes changed during installed mutation: ${path.relative(PACKAGE_ROOT, sourcePath)}`
|
|
931
|
+
);
|
|
932
|
+
assert.equal(
|
|
933
|
+
sha256(actual),
|
|
934
|
+
sha256(expected),
|
|
935
|
+
`canonical source SHA changed during installed mutation: ${path.relative(PACKAGE_ROOT, sourcePath)}`
|
|
442
936
|
);
|
|
443
937
|
}
|
|
444
938
|
}
|
|
@@ -457,34 +951,94 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
457
951
|
const actual = fs.readFileSync(path.join(installedRoot, relative), 'utf8');
|
|
458
952
|
assert.equal(actual, expected, `Codex Specs projection drifted: ${relative}`);
|
|
459
953
|
}
|
|
954
|
+
const sourceSpecMaker = fs.readFileSync(SPEC_MAKER_SOURCE_PATH, 'utf8');
|
|
955
|
+
const installedSpecMakerPath = path.join(root, '.codex/agents/spec_maker.toml');
|
|
956
|
+
assert.equal(
|
|
957
|
+
fs.readFileSync(installedSpecMakerPath, 'utf8'),
|
|
958
|
+
convertCodexAgentContent(sourceSpecMaker, path.basename(SPEC_MAKER_SOURCE_PATH)),
|
|
959
|
+
'Codex spec_maker projection drifted'
|
|
960
|
+
);
|
|
460
961
|
for (const relative of OBSOLETE_SPECS_FILES) {
|
|
461
962
|
assert.equal(fs.existsSync(path.join(installedRoot, relative)), false, `obsolete Specs file installed: ${relative}`);
|
|
462
963
|
}
|
|
463
964
|
|
|
464
965
|
const files = readInstalledAuthoringProjection(root, installedRoot);
|
|
465
|
-
const sourceBytes =
|
|
466
|
-
relative,
|
|
467
|
-
fs.readFileSync(path.join(SPECS_SOURCE_ROOT, relative))
|
|
468
|
-
]));
|
|
966
|
+
const sourceBytes = canonicalProjectionSourceBytes();
|
|
469
967
|
assert.deepEqual(installedAuthoringProjectionIssues(root, installedRoot), []);
|
|
968
|
+
assert.deepEqual(installedAdaptiveCoverageIssues(root, installedRoot), []);
|
|
470
969
|
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
471
970
|
|
|
472
971
|
if (process.platform !== 'win32') {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
972
|
+
for (const [installedPath, sourcePath, assertTarget] of [
|
|
973
|
+
[
|
|
974
|
+
path.join(installedRoot, 'references/templates.md'),
|
|
975
|
+
path.join(SPECS_SOURCE_ROOT, 'references/templates.md'),
|
|
976
|
+
() => assertInstalledMutationTarget(root, installedRoot, 'references/templates.md')
|
|
977
|
+
],
|
|
978
|
+
[
|
|
979
|
+
installedSpecMakerPath,
|
|
980
|
+
SPEC_MAKER_SOURCE_PATH,
|
|
981
|
+
() => assertInstalledSpecMakerMutationTarget(root)
|
|
982
|
+
]
|
|
983
|
+
]) {
|
|
984
|
+
const backupPath = `${installedPath}.cafekit-backup`;
|
|
985
|
+
fs.renameSync(installedPath, backupPath);
|
|
986
|
+
try {
|
|
987
|
+
fs.linkSync(sourcePath, installedPath);
|
|
988
|
+
assert.throws(assertTarget, /must not share an inode with canonical source/);
|
|
989
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
990
|
+
} finally {
|
|
991
|
+
try { fs.unlinkSync(installedPath); } catch { /* best-effort disposable cleanup */ }
|
|
992
|
+
fs.renameSync(backupPath, installedPath);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const crossSourceRoot = path.join(root, 'cross-source-native-control');
|
|
997
|
+
const crossSourceProject = path.join(crossSourceRoot, 'project');
|
|
998
|
+
const crossSourceScope = path.join(crossSourceProject, '.agents/skills/specs');
|
|
999
|
+
const crossSourceTarget = path.join(crossSourceScope, 'target-a.md');
|
|
1000
|
+
const crossSourcePaths = [
|
|
1001
|
+
path.join(crossSourceRoot, 'sources/source-a.md'),
|
|
1002
|
+
path.join(crossSourceRoot, 'sources/source-b.md')
|
|
1003
|
+
];
|
|
1004
|
+
fs.mkdirSync(crossSourceScope, { recursive: true });
|
|
1005
|
+
fs.mkdirSync(path.dirname(crossSourcePaths[0]), { recursive: true });
|
|
1006
|
+
fs.writeFileSync(crossSourceTarget, 'native installed target A\n');
|
|
1007
|
+
fs.writeFileSync(crossSourcePaths[0], 'native canonical source A\n');
|
|
1008
|
+
fs.writeFileSync(crossSourcePaths[1], 'native canonical source B\n');
|
|
1009
|
+
const targetBytes = fs.readFileSync(crossSourceTarget);
|
|
1010
|
+
const sourceState = new Map(crossSourcePaths.map((sourcePath) => {
|
|
1011
|
+
const bytes = fs.readFileSync(sourcePath);
|
|
1012
|
+
return [sourcePath, { bytes, sha: sha256(bytes) }];
|
|
1013
|
+
}));
|
|
1014
|
+
assert.notEqual(crossSourcePaths[0], crossSourcePaths[1], 'cross-source control requires A != B');
|
|
1015
|
+
fs.unlinkSync(crossSourceTarget);
|
|
1016
|
+
fs.linkSync(crossSourcePaths[1], crossSourceTarget);
|
|
478
1017
|
try {
|
|
479
|
-
fs.linkSync(sourcePath, installedPath);
|
|
480
1018
|
assert.throws(
|
|
481
|
-
() =>
|
|
482
|
-
|
|
1019
|
+
() => fs.writeFileSync(
|
|
1020
|
+
assertDisposableInstalledMutationTarget({
|
|
1021
|
+
projectRoot: crossSourceProject,
|
|
1022
|
+
installedScope: crossSourceScope,
|
|
1023
|
+
targetPath: crossSourceTarget,
|
|
1024
|
+
expectedTargetPath: crossSourceTarget,
|
|
1025
|
+
canonicalSourcePaths: crossSourcePaths,
|
|
1026
|
+
scopeLabel: '.agents/skills/specs'
|
|
1027
|
+
}),
|
|
1028
|
+
'forbidden native cross-source mutation\n'
|
|
1029
|
+
),
|
|
1030
|
+
/must not share an inode with canonical source set/
|
|
483
1031
|
);
|
|
484
|
-
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
485
1032
|
} finally {
|
|
486
|
-
|
|
487
|
-
fs.
|
|
1033
|
+
fs.unlinkSync(crossSourceTarget);
|
|
1034
|
+
fs.writeFileSync(crossSourceTarget, targetBytes);
|
|
1035
|
+
fs.writeFileSync(crossSourcePaths[1], sourceState.get(crossSourcePaths[1]).bytes);
|
|
1036
|
+
}
|
|
1037
|
+
assert.deepEqual(fs.readFileSync(crossSourceTarget), targetBytes, 'cross-source control must restore exact target bytes');
|
|
1038
|
+
for (const [sourcePath, expected] of sourceState) {
|
|
1039
|
+
const actual = fs.readFileSync(sourcePath);
|
|
1040
|
+
assert.deepEqual(actual, expected.bytes, 'cross-source rejection must preserve every source byte');
|
|
1041
|
+
assert.equal(sha256(actual), expected.sha, 'cross-source rejection must preserve every source SHA');
|
|
488
1042
|
}
|
|
489
1043
|
}
|
|
490
1044
|
|
|
@@ -514,6 +1068,13 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
514
1068
|
from: IMPLEMENTATION_READINESS_CLAUSES.noInvention,
|
|
515
1069
|
to: 'Before implementation handoff, note ambiguous choices without blocking handoff.'
|
|
516
1070
|
},
|
|
1071
|
+
{
|
|
1072
|
+
name: 'no-invention-contradictory-override',
|
|
1073
|
+
issue: 'no-invention',
|
|
1074
|
+
relative: 'references/templates.md',
|
|
1075
|
+
from: IMPLEMENTATION_READINESS_CLAUSES.noInvention,
|
|
1076
|
+
to: `${IMPLEMENTATION_READINESS_CLAUSES.noInvention}\n\nException: implementation handoff may proceed with an unresolved material choice.`
|
|
1077
|
+
},
|
|
517
1078
|
{
|
|
518
1079
|
name: 'material-boundary-definition',
|
|
519
1080
|
issue: 'boundary-contract',
|
|
@@ -528,16 +1089,23 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
528
1089
|
from: IMPLEMENTATION_READINESS_CLAUSES.exactBoundaryChoices,
|
|
529
1090
|
to: 'For every required row, describe the listed choices generally.'
|
|
530
1091
|
},
|
|
1092
|
+
boundaryMutation('interaction-accessibility', 'Interaction/UI', [
|
|
1093
|
+
['input/focus/keyboard; ', ''],
|
|
1094
|
+
['accessibility; ', '']
|
|
1095
|
+
]),
|
|
531
1096
|
boundaryMutation('api-success-and-error-semantics', 'API/CLI', [
|
|
532
1097
|
['success output; ', ''],
|
|
533
1098
|
['error/status/exit; ', '']
|
|
534
1099
|
]),
|
|
535
|
-
boundaryMutation('schema-shape-and-unknown-fields', '
|
|
1100
|
+
boundaryMutation('schema-shape-and-unknown-fields', 'Data/schema', [
|
|
536
1101
|
['exact keys/nesting/types; ', ''],
|
|
537
1102
|
['unknown-field behavior; ', '']
|
|
538
1103
|
]),
|
|
539
|
-
boundaryMutation('
|
|
540
|
-
['
|
|
1104
|
+
boundaryMutation('schema-enum-and-format', 'Data/schema', [
|
|
1105
|
+
['enum/format/', '']
|
|
1106
|
+
]),
|
|
1107
|
+
boundaryMutation('state-lock-lifecycle', 'Async/state', [
|
|
1108
|
+
['writer/lock acquire/contention/release; ', 'writer/lock; ']
|
|
541
1109
|
]),
|
|
542
1110
|
boundaryMutation('filesystem-segment-grammar', 'Filesystem/security', [
|
|
543
1111
|
['trusted/untrusted segment grammar; ', '']
|
|
@@ -545,11 +1113,19 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
545
1113
|
boundaryMutation('filesystem-stale-lock-reclaim', 'Filesystem/security', [
|
|
546
1114
|
['lock/stale reclaim; ', '']
|
|
547
1115
|
]),
|
|
1116
|
+
boundaryMutation('runtime-rollout-and-recovery', 'Runtime/deploy', [
|
|
1117
|
+
['rollout/rollback; ', ''],
|
|
1118
|
+
['operator recovery', '']
|
|
1119
|
+
]),
|
|
548
1120
|
boundaryMutation('retention-clock-and-endpoints', 'Time/retention', [
|
|
549
1121
|
['clock source; ', ''],
|
|
550
1122
|
['unit/precision/timezone; ', ''],
|
|
551
1123
|
['endpoints and inclusion/comparator; ', '']
|
|
552
1124
|
]),
|
|
1125
|
+
boundaryMutation('ai-model-safety-and-eval', 'AI/model', [
|
|
1126
|
+
['safety/privacy; ', ''],
|
|
1127
|
+
['eval oracle', '']
|
|
1128
|
+
]),
|
|
553
1129
|
boundaryMutation('proof-level-partition', 'Integration/proof', [
|
|
554
1130
|
['proof level (`source`/`installed`/`live`)', 'proof level']
|
|
555
1131
|
]),
|
|
@@ -595,6 +1171,13 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
595
1171
|
from: IMPLEMENTATION_READINESS_CLAUSES.disposableReviewControls,
|
|
596
1172
|
to: 'Run mutation or destructive negative controls against the available project copy.'
|
|
597
1173
|
},
|
|
1174
|
+
{
|
|
1175
|
+
name: 'required-proof-level-mapping',
|
|
1176
|
+
issue: 'proof-chain',
|
|
1177
|
+
relative: 'references/templates.md',
|
|
1178
|
+
from: IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
|
|
1179
|
+
to: 'Map one required proof level to one probe; other levels may remain implicit.'
|
|
1180
|
+
},
|
|
598
1181
|
{
|
|
599
1182
|
name: 'artifact-path-and-digest-or-ephemeral',
|
|
600
1183
|
issue: 'proof-chain',
|
|
@@ -652,6 +1235,7 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
652
1235
|
to: 'Any privacy/security claim names the sensitive data at risk.'
|
|
653
1236
|
}
|
|
654
1237
|
];
|
|
1238
|
+
assert.equal(readinessMutations.length, 30, 'installed readiness mutation set must match canonical source');
|
|
655
1239
|
|
|
656
1240
|
for (const { name, issue, relative, from, to } of readinessMutations) {
|
|
657
1241
|
const target = assertInstalledMutationTarget(root, installedRoot, relative);
|
|
@@ -675,6 +1259,152 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
675
1259
|
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
676
1260
|
}
|
|
677
1261
|
|
|
1262
|
+
const integrationRow = IMPLEMENTATION_READINESS_BOUNDARY_ROWS
|
|
1263
|
+
.find(([label]) => label === 'Integration/proof');
|
|
1264
|
+
const integrationLine = `| ${integrationRow.join(' | ')} |`;
|
|
1265
|
+
const templatesTarget = assertInstalledMutationTarget(
|
|
1266
|
+
root,
|
|
1267
|
+
installedRoot,
|
|
1268
|
+
'references/templates.md'
|
|
1269
|
+
);
|
|
1270
|
+
const templatesBytes = fs.readFileSync(templatesTarget, 'utf8');
|
|
1271
|
+
const extendedTemplates = templatesBytes.replace(
|
|
1272
|
+
integrationLine,
|
|
1273
|
+
`${integrationLine}\n| other:domain-specific | task-specific material choices and observable oracle |`
|
|
1274
|
+
);
|
|
1275
|
+
assert.notEqual(extendedTemplates, templatesBytes, 'open-boundary positive control must change installed templates');
|
|
1276
|
+
try {
|
|
1277
|
+
fs.writeFileSync(assertInstalledMutationTarget(root, installedRoot, 'references/templates.md'), extendedTemplates);
|
|
1278
|
+
assert.deepEqual(installedAuthoringProjectionIssues(root, installedRoot), []);
|
|
1279
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
1280
|
+
} finally {
|
|
1281
|
+
fs.writeFileSync(assertInstalledMutationTarget(root, installedRoot, 'references/templates.md'), templatesBytes);
|
|
1282
|
+
}
|
|
1283
|
+
assert.deepEqual(installedAuthoringProjectionIssues(root, installedRoot), [], 'open-boundary restore');
|
|
1284
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
1285
|
+
|
|
1286
|
+
const adaptiveMutations = [
|
|
1287
|
+
['frontmatter-risk-bypass', 'skill', ADAPTIVE_COVERAGE_CLAUSES.frontmatterGate,
|
|
1288
|
+
'skip for any clear one-file or two-file change.', ['risk-first-routing']],
|
|
1289
|
+
['destructive-routine-direct', 'skill', ADAPTIVE_COVERAGE_CLAUSES.directGate,
|
|
1290
|
+
'Work directly when the change is routine and likely limited to one or two files.', ['risk-first-routing']],
|
|
1291
|
+
['destructive-direct-exception', 'skill', ADAPTIVE_COVERAGE_CLAUSES.directGate,
|
|
1292
|
+
`${ADAPTIVE_COVERAGE_CLAUSES.directGate} Exception: destructive one-file work labeled routine may go direct.`, ['risk-first-routing']],
|
|
1293
|
+
['user-risk-downgrade', 'skill', ADAPTIVE_COVERAGE_CLAUSES.riskFirst,
|
|
1294
|
+
`${ADAPTIVE_COVERAGE_CLAUSES.riskFirst} A user may lower critical risk to routine.`, ['risk-first-routing']],
|
|
1295
|
+
['four-subsystem-split', 'skill', ADAPTIVE_COVERAGE_CLAUSES.splitRoute,
|
|
1296
|
+
'Split four or more independent subsystems; otherwise use one Specs packet for substantial work.', ['risk-first-routing']],
|
|
1297
|
+
['forced-single-kind', 'templates', ADAPTIVE_COVERAGE_CLAUSES.openKinds,
|
|
1298
|
+
'Choose one primary change kind and ignore unfamiliar kinds or surfaces.', ['coverage-profile-shape']],
|
|
1299
|
+
['missing-profile-column', 'templates', '| ID | Outcome | Change kinds | Material surfaces | Ambiguity/action | Risk/evidence | Required proof |',
|
|
1300
|
+
'| ID | Outcome | Change kinds | Material surfaces | Risk/evidence | Required proof |', ['coverage-profile-shape']],
|
|
1301
|
+
['duplicate-profile-heading', 'templates', '## Coverage profile\n',
|
|
1302
|
+
'## Coverage profile\n\n## Coverage profile\n', ['coverage-profile-shape']],
|
|
1303
|
+
['truncated-profile-row', 'templates', `| ${ADAPTIVE_COVERAGE_PROFILE_ROW.join(' | ')} |`,
|
|
1304
|
+
'| CP-01 | <externally observable outcome> |', ['coverage-profile-shape']],
|
|
1305
|
+
['examples-promote-to-design', 'templates', `| ${ADAPTIVE_COVERAGE_AMBIGUITY_ROWS[2].join(' | ')} |`,
|
|
1306
|
+
'| `examples-needed` | add examples and promote to `design-needed` if behavior changes |', ['ambiguity-actions']],
|
|
1307
|
+
['examples-select-retention', 'templates', 'retention of 30 versus 90 days is\n`decision-needed`',
|
|
1308
|
+
'retention of 30 versus 90 days may remain\n`examples-needed`', ['ambiguity-actions']],
|
|
1309
|
+
['global-critical-ceremony', 'templates', ADAPTIVE_COVERAGE_CLAUSES.scopedUnion,
|
|
1310
|
+
'Each task copies its CP values; authoring, review, edge, and proof obligations union across every task.', ['scoped-coverage']],
|
|
1311
|
+
['scoped-union-contradiction', 'templates', ADAPTIVE_COVERAGE_CLAUSES.scopedUnion,
|
|
1312
|
+
`${ADAPTIVE_COVERAGE_CLAUSES.scopedUnion} Exception: critical proof obligations apply across every CP row.`, ['scoped-coverage']],
|
|
1313
|
+
['stale-profile-after-c2', 'templates', ADAPTIVE_COVERAGE_CLAUSES.profileRederivation,
|
|
1314
|
+
'Keep existing coverage rows after accepted plan changes.', ['profile-lifecycle']],
|
|
1315
|
+
['planned-proof-blocks-start', 'templates', ADAPTIVE_COVERAGE_CLAUSES.plannedProof,
|
|
1316
|
+
'`Required proof` is execution evidence: known but unrun proof blocks `pending`; `UNKNOWN` may proceed; missing evidence may still reach `done`/C3.', ['proof-lifecycle']],
|
|
1317
|
+
['source-promotes-live', 'templates', ADAPTIVE_COVERAGE_CLAUSES.proofSeparation,
|
|
1318
|
+
'Source proof may promote installed and live proof.', ['proof-lifecycle']],
|
|
1319
|
+
['unmapped-proof-level', 'templates', IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
|
|
1320
|
+
'A task may map only one required proof level to its probe.', ['proof-lifecycle']],
|
|
1321
|
+
['critical-reviewer-omitted', 'review', ADAPTIVE_COVERAGE_CLAUSES.reviewRisk,
|
|
1322
|
+
'Keep Fact Checker as the baseline and choose any remaining reviewer; critical rows need no matching risk role.', ['reviewer-routing', 'scoped-coverage']],
|
|
1323
|
+
['reviewer-lens-overflow', 'review', ADAPTIVE_COVERAGE_CLAUSES.reviewCapacity,
|
|
1324
|
+
'Each reviewer owns exactly one lens; skip excess material lenses when the fixed reviewer count is full.', ['reviewer-routing']],
|
|
1325
|
+
['spec-maker-local-taxonomy', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAuthority,
|
|
1326
|
+
'this agent owns a separate risk and coverage taxonomy.', ['spec-maker-authority']],
|
|
1327
|
+
['spec-maker-examples-decide', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity,
|
|
1328
|
+
'Use examples to settle every ambiguous observable behavior.', ['spec-maker-authority']],
|
|
1329
|
+
['spec-maker-skips-brainstorm', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute,
|
|
1330
|
+
'Apply the risk-first route before C1 and stop only for direct work.', ['spec-maker-authority']],
|
|
1331
|
+
['static-proves-live', 'skill', ADAPTIVE_COVERAGE_CLAUSES.liveLimit,
|
|
1332
|
+
'Source/static checks prove live-model adherence.', ['proof-lifecycle']]
|
|
1333
|
+
];
|
|
1334
|
+
assert.equal(adaptiveMutations.length, 23, 'installed adaptive mutation set must match canonical source');
|
|
1335
|
+
|
|
1336
|
+
const adaptiveTarget = (source) => {
|
|
1337
|
+
if (source === 'specMaker') {
|
|
1338
|
+
const target = assertInstalledSpecMakerMutationTarget(root);
|
|
1339
|
+
const bytes = fs.readFileSync(target);
|
|
1340
|
+
const content = bytes.toString('utf8');
|
|
1341
|
+
return {
|
|
1342
|
+
target,
|
|
1343
|
+
bytes,
|
|
1344
|
+
content: parseGeneratedTomlString(content, 'developer_instructions'),
|
|
1345
|
+
render: (value) => replaceGeneratedTomlString(content, 'developer_instructions', value)
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
const relative = {
|
|
1349
|
+
skill: 'SKILL.md',
|
|
1350
|
+
templates: 'references/templates.md',
|
|
1351
|
+
review: 'references/review.md'
|
|
1352
|
+
}[source];
|
|
1353
|
+
assert.ok(relative, `unknown adaptive mutation source: ${source}`);
|
|
1354
|
+
const target = assertInstalledMutationTarget(root, installedRoot, relative);
|
|
1355
|
+
const bytes = fs.readFileSync(target);
|
|
1356
|
+
return { target, bytes, content: bytes.toString('utf8'), render: (value) => value };
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
for (const [name, source, from, to, expected] of adaptiveMutations) {
|
|
1360
|
+
const targetState = adaptiveTarget(source);
|
|
1361
|
+
const anchorIndex = targetState.content.indexOf(from);
|
|
1362
|
+
assert.ok(anchorIndex >= 0, `${name} mutation anchor must exist in installed bytes`);
|
|
1363
|
+
assert.equal(
|
|
1364
|
+
targetState.content.indexOf(from, anchorIndex + from.length),
|
|
1365
|
+
-1,
|
|
1366
|
+
`${name} mutation anchor must be unique in installed bytes`
|
|
1367
|
+
);
|
|
1368
|
+
const weakened = `${targetState.content.slice(0, anchorIndex)}${to}${targetState.content.slice(anchorIndex + from.length)}`;
|
|
1369
|
+
try {
|
|
1370
|
+
fs.writeFileSync(adaptiveTarget(source).target, targetState.render(weakened));
|
|
1371
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
1372
|
+
assert.deepEqual(installedAdaptiveCoverageIssues(root, installedRoot), [...expected].sort(), name);
|
|
1373
|
+
} finally {
|
|
1374
|
+
fs.writeFileSync(adaptiveTarget(source).target, targetState.bytes);
|
|
1375
|
+
}
|
|
1376
|
+
assert.deepEqual(fs.readFileSync(adaptiveTarget(source).target), targetState.bytes, `${name} byte restore`);
|
|
1377
|
+
assert.deepEqual(installedAdaptiveCoverageIssues(root, installedRoot), [], `${name} restore`);
|
|
1378
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const intactAdaptive = readInstalledAuthoringProjection(root, installedRoot);
|
|
1382
|
+
const adaptivePositiveControls = [
|
|
1383
|
+
['open material surface', intactAdaptive.templates.replace(
|
|
1384
|
+
integrationLine,
|
|
1385
|
+
`${integrationLine}\n| other:domain-specific | task-specific material choices and observable oracle |`
|
|
1386
|
+
)],
|
|
1387
|
+
['task CP reference requirement', intactAdaptive.templates.replace(
|
|
1388
|
+
ADAPTIVE_COVERAGE_CLAUSES.scopedUnion,
|
|
1389
|
+
`${ADAPTIVE_COVERAGE_CLAUSES.scopedUnion} Require every task to reference a CP row.`
|
|
1390
|
+
)]
|
|
1391
|
+
];
|
|
1392
|
+
for (const [name, templates] of adaptivePositiveControls) {
|
|
1393
|
+
assert.notEqual(templates, intactAdaptive.templates, `${name} positive control must change installed templates`);
|
|
1394
|
+
const target = assertInstalledMutationTarget(root, installedRoot, 'references/templates.md');
|
|
1395
|
+
const original = fs.readFileSync(target);
|
|
1396
|
+
try {
|
|
1397
|
+
fs.writeFileSync(target, templates);
|
|
1398
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
1399
|
+
assert.deepEqual(installedAdaptiveCoverageIssues(root, installedRoot), [], name);
|
|
1400
|
+
} finally {
|
|
1401
|
+
fs.writeFileSync(assertInstalledMutationTarget(root, installedRoot, 'references/templates.md'), original);
|
|
1402
|
+
}
|
|
1403
|
+
assert.deepEqual(fs.readFileSync(target), original, `${name} byte restore`);
|
|
1404
|
+
assert.deepEqual(installedAdaptiveCoverageIssues(root, installedRoot), [], `${name} restore`);
|
|
1405
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
678
1408
|
const mutations = [
|
|
679
1409
|
['skill', (value) => value.replace('Task files are flat beside `plan.md`', 'Task files may be nested')],
|
|
680
1410
|
['review', (value) => value.replace('list at 15', 'list without a cap')],
|
|
@@ -685,6 +1415,12 @@ function assertInstalledAuthoringProjection(root, installedRoot) {
|
|
|
685
1415
|
const changed = { ...files, [key]: mutate(files[key]) };
|
|
686
1416
|
assert.notDeepEqual(authoringProjectionIssues(changed), []);
|
|
687
1417
|
}
|
|
1418
|
+
assert.equal(
|
|
1419
|
+
fs.readFileSync(installedSpecMakerPath, 'utf8'),
|
|
1420
|
+
convertCodexAgentContent(sourceSpecMaker, path.basename(SPEC_MAKER_SOURCE_PATH)),
|
|
1421
|
+
'Codex spec_maker projection must be byte-exact after mutation restores'
|
|
1422
|
+
);
|
|
1423
|
+
assertCanonicalSpecsSourceBytesUnchanged(sourceBytes);
|
|
688
1424
|
}
|
|
689
1425
|
|
|
690
1426
|
test('Codex payload transform emits native skill and subagent syntax', () => {
|
|
@@ -697,13 +1433,165 @@ test('Codex payload transform emits native skill and subagent syntax', () => {
|
|
|
697
1433
|
transformed,
|
|
698
1434
|
/spawn_agent\(agent_type="implementer", fork_turns="none", message="Implement it", task_name="code_feature"\)/
|
|
699
1435
|
);
|
|
700
|
-
assert.match(transformed, /\$
|
|
1436
|
+
assert.match(transformed, /\$cf-specs auth/);
|
|
701
1437
|
assert.match(transformed, /`send_message`/);
|
|
702
1438
|
assert.match(transformed, /`exec_command`/);
|
|
703
1439
|
assert.match(transformed, /`apply_patch`/);
|
|
704
1440
|
assert.doesNotMatch(transformed, /Agent\(|subagent_type|\/specs\b|Claude Code/);
|
|
705
1441
|
});
|
|
706
1442
|
|
|
1443
|
+
test('Codex payload transform keeps structured user-input grammar across determiners', () => {
|
|
1444
|
+
const cases = [
|
|
1445
|
+
['standalone bare token', 'AskUserQuestion', 'a structured user-input request'],
|
|
1446
|
+
['standalone code token', '`AskUserQuestion`', 'a structured user-input request'],
|
|
1447
|
+
['standalone multi-backtick token', '``AskUserQuestion``', 'a structured user-input request'],
|
|
1448
|
+
['standalone padded multi-backtick token', '`` AskUserQuestion ``', 'a structured user-input request'],
|
|
1449
|
+
['one', 'one AskUserQuestion call', 'one structured user-input request call'],
|
|
1450
|
+
['one multi-backtick', 'one ``AskUserQuestion`` call', 'one structured user-input request call'],
|
|
1451
|
+
['one padded multi-backtick', 'one `` AskUserQuestion `` call', 'one structured user-input request call'],
|
|
1452
|
+
['each', 'each AskUserQuestion call', 'each structured user-input request call'],
|
|
1453
|
+
['every', 'every AskUserQuestion batch', 'every structured user-input request batch'],
|
|
1454
|
+
['another', 'another AskUserQuestion call', 'another structured user-input request call'],
|
|
1455
|
+
['this', 'this AskUserQuestion call', 'this structured user-input request call'],
|
|
1456
|
+
['that', 'that AskUserQuestion call', 'that structured user-input request call'],
|
|
1457
|
+
['a', 'a AskUserQuestion call', 'a structured user-input request call'],
|
|
1458
|
+
['an', 'an AskUserQuestion call', 'a structured user-input request call'],
|
|
1459
|
+
['capitalized each', 'Each AskUserQuestion call', 'Each structured user-input request call'],
|
|
1460
|
+
['capitalized an', 'An AskUserQuestion call', 'A structured user-input request call'],
|
|
1461
|
+
['the', 'the AskUserQuestion call', 'the structured user-input request call'],
|
|
1462
|
+
['numeric', '27 AskUserQuestion calls', '27 structured user-input request calls'],
|
|
1463
|
+
['plural calls', 'AskUserQuestion calls', 'structured user-input request calls'],
|
|
1464
|
+
['plural batches', 'AskUserQuestion batches', 'structured user-input request batches']
|
|
1465
|
+
];
|
|
1466
|
+
|
|
1467
|
+
for (const [name, input, expected] of cases) {
|
|
1468
|
+
const actual = normalizeCodexBody(input, '/fixture/instructions.md');
|
|
1469
|
+
assert.equal(actual, expected, name);
|
|
1470
|
+
assert.equal(normalizeCodexBody(actual, '/fixture/instructions.md'), expected, `${name} idempotence`);
|
|
1471
|
+
assert.doesNotMatch(actual, /\bAskUserQuestion\b/, `${name} Claude-only token`);
|
|
1472
|
+
assert.doesNotMatch(actual, /\ban structured user-input request\b/i, `${name} incompatible article`);
|
|
1473
|
+
assert.doesNotMatch(
|
|
1474
|
+
actual,
|
|
1475
|
+
/\b(?:a|an|one|each|every|another|this|that|the|[0-9]+)\s+a structured user-input request\b/i,
|
|
1476
|
+
`${name} doubled article`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
assert.equal(
|
|
1481
|
+
normalizeCodexBody('one\nAskUserQuestion call', '/fixture/instructions.md'),
|
|
1482
|
+
'one\nstructured user-input request call',
|
|
1483
|
+
'a single Markdown soft break must preserve its determiner relationship'
|
|
1484
|
+
);
|
|
1485
|
+
assert.equal(
|
|
1486
|
+
normalizeCodexBody('AskUserQuestion\nCalls', '/fixture/instructions.md'),
|
|
1487
|
+
'structured user-input request\nCalls',
|
|
1488
|
+
'a single Markdown soft break must preserve a plural relationship'
|
|
1489
|
+
);
|
|
1490
|
+
assert.equal(
|
|
1491
|
+
normalizeCodexBody('one\n\nAskUserQuestion call', '/fixture/instructions.md'),
|
|
1492
|
+
'one\n\na structured user-input request call',
|
|
1493
|
+
'a paragraph break must not bind a prior determiner'
|
|
1494
|
+
);
|
|
1495
|
+
assert.equal(
|
|
1496
|
+
normalizeCodexBody('MyAskUserQuestionHelper AskUserQuestionFactory', '/fixture/instructions.md'),
|
|
1497
|
+
'MyAskUserQuestionHelper AskUserQuestionFactory',
|
|
1498
|
+
'balanced token matching must preserve identifiers'
|
|
1499
|
+
);
|
|
1500
|
+
assert.equal(
|
|
1501
|
+
normalizeCodexBody('[tool](https://example.test/AskUserQuestion)', '/fixture/instructions.md'),
|
|
1502
|
+
'[tool](https://example.test/AskUserQuestion)',
|
|
1503
|
+
'Markdown link destinations must stay byte-exact'
|
|
1504
|
+
);
|
|
1505
|
+
assert.equal(
|
|
1506
|
+
normalizeCodexBody('https://example.test/AskUserQuestion', '/fixture/instructions.md'),
|
|
1507
|
+
'https://example.test/AskUserQuestion',
|
|
1508
|
+
'raw URL tokens must stay byte-exact'
|
|
1509
|
+
);
|
|
1510
|
+
assert.equal(
|
|
1511
|
+
normalizeCodexBody('one ``AskUserQuestion``` call', '/fixture/instructions.md'),
|
|
1512
|
+
'one ``AskUserQuestion``` call',
|
|
1513
|
+
'mismatched backtick runs must stay byte-exact'
|
|
1514
|
+
);
|
|
1515
|
+
assert.equal(
|
|
1516
|
+
normalizeCodexBody('``Use AskUserQuestion here``', '/fixture/instructions.md'),
|
|
1517
|
+
'``Use AskUserQuestion here``',
|
|
1518
|
+
'non-token code spans must stay byte-exact'
|
|
1519
|
+
);
|
|
1520
|
+
assert.equal(
|
|
1521
|
+
normalizeCodexBody('```js\nAskUserQuestion\n````', '/fixture/instructions.md'),
|
|
1522
|
+
'```js\nAskUserQuestion\n````',
|
|
1523
|
+
'a longer closing backtick fence must protect its body'
|
|
1524
|
+
);
|
|
1525
|
+
assert.equal(
|
|
1526
|
+
normalizeCodexBody('~~~text\nAskUserQuestion\n~~~~', '/fixture/instructions.md'),
|
|
1527
|
+
'~~~text\nAskUserQuestion\n~~~~',
|
|
1528
|
+
'a longer closing tilde fence must protect its body'
|
|
1529
|
+
);
|
|
1530
|
+
const markerCollision = '\uE000CAFEKIT_0_ASK_999\uE001';
|
|
1531
|
+
assert.equal(
|
|
1532
|
+
normalizeCodexBody(markerCollision, '/fixture/instructions.md'),
|
|
1533
|
+
markerCollision,
|
|
1534
|
+
'literal internal-marker-shaped input must stay byte-exact'
|
|
1535
|
+
);
|
|
1536
|
+
const codeMarkerCollision = '\uE000CAFEKIT_0_CODE_0\uE001 and ``not token``';
|
|
1537
|
+
assert.equal(
|
|
1538
|
+
normalizeCodexBody(codeMarkerCollision, '/fixture/instructions.md'),
|
|
1539
|
+
codeMarkerCollision,
|
|
1540
|
+
'mask restoration must not replace a literal marker-shaped prefix'
|
|
1541
|
+
);
|
|
1542
|
+
assert.equal(
|
|
1543
|
+
normalizeCodexBody('one AskUserQuestion call', '/fixture/session.cjs'),
|
|
1544
|
+
'one AskUserQuestion call',
|
|
1545
|
+
'non-instruction assets must preserve executable text'
|
|
1546
|
+
);
|
|
1547
|
+
assert.equal(
|
|
1548
|
+
normalizeCodexBody('No structured input token here.', '/fixture/instructions.md'),
|
|
1549
|
+
'No structured input token here.',
|
|
1550
|
+
'token-free instruction text must stay byte-exact'
|
|
1551
|
+
);
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
test('Codex structured-input corpus oracle stays differential and production-aware', () => {
|
|
1555
|
+
const sourceRoot = path.join(PACKAGE_ROOT, 'src/claude');
|
|
1556
|
+
const occurrenceFiles = allFiles(sourceRoot, (file) => /\.(?:md|mdx|txt|cjs|js)$/i.test(file))
|
|
1557
|
+
.filter((file) => fs.readFileSync(file, 'utf8').includes('AskUserQuestion'));
|
|
1558
|
+
const relative = (file) => path.relative(PACKAGE_ROOT, file).split(path.sep).join('/');
|
|
1559
|
+
const actualPaths = occurrenceFiles.map(relative).sort();
|
|
1560
|
+
const expectedPaths = [
|
|
1561
|
+
'src/claude/agents/spec-maker.md',
|
|
1562
|
+
'src/claude/hooks/session.cjs',
|
|
1563
|
+
'src/claude/skills/git/SKILL.md',
|
|
1564
|
+
'src/claude/skills/inspect/SKILL.md'
|
|
1565
|
+
];
|
|
1566
|
+
assert.deepEqual(actualPaths, expectedPaths, 'every source occurrence needs an explicit projection oracle');
|
|
1567
|
+
|
|
1568
|
+
const instructionExpectedSnippets = new Map([
|
|
1569
|
+
['src/claude/skills/git/SKILL.md', 'Present options via a structured user-input request — header'],
|
|
1570
|
+
['src/claude/skills/inspect/SKILL.md', '**Fallback to a structured user-input request:**']
|
|
1571
|
+
]);
|
|
1572
|
+
for (const [relativePath, expectedSnippet] of instructionExpectedSnippets) {
|
|
1573
|
+
const sourcePath = path.join(PACKAGE_ROOT, relativePath);
|
|
1574
|
+
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
1575
|
+
const sentinel = '__CAFEKIT_STRUCTURED_INPUT_SENTINEL__';
|
|
1576
|
+
const withSentinel = source.replace(/`?\bAskUserQuestion\b`?/g, sentinel);
|
|
1577
|
+
assert.notEqual(withSentinel, source, `${relativePath} differential control must replace a token`);
|
|
1578
|
+
const expected = normalizeCodexBody(withSentinel, sourcePath)
|
|
1579
|
+
.replaceAll(sentinel, 'a structured user-input request');
|
|
1580
|
+
const actual = normalizeCodexBody(source, sourcePath);
|
|
1581
|
+
assert.equal(actual, expected, `${relativePath} independent differential oracle`);
|
|
1582
|
+
assert.match(actual, new RegExp(expectedSnippet.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const specMaker = fs.readFileSync(SPEC_MAKER_SOURCE_PATH, 'utf8');
|
|
1586
|
+
const convertedAgent = convertCodexAgentContent(specMaker, path.basename(SPEC_MAKER_SOURCE_PATH));
|
|
1587
|
+
assert.doesNotMatch(convertedAgent, /\bAskUserQuestion\b/);
|
|
1588
|
+
assert.doesNotMatch(convertedAgent, /structured user-input request/);
|
|
1589
|
+
|
|
1590
|
+
const sessionPath = path.join(PACKAGE_ROOT, 'src/claude/hooks/session.cjs');
|
|
1591
|
+
const session = normalizeCodexBody(fs.readFileSync(sessionPath, 'utf8'), sessionPath);
|
|
1592
|
+
assert.equal((session.match(/\bAskUserQuestion\b/g) || []).length, 2);
|
|
1593
|
+
});
|
|
1594
|
+
|
|
707
1595
|
test('Codex payload transform preserves executable keyword arguments', () => {
|
|
708
1596
|
const transformed = normalizeCodexBody(
|
|
709
1597
|
'parser = ArgumentParser(description="Analyze it")\n' +
|
|
@@ -1046,6 +1934,43 @@ test('Codex dry-run leaves both managed roots untouched', () => {
|
|
|
1046
1934
|
});
|
|
1047
1935
|
});
|
|
1048
1936
|
|
|
1937
|
+
test('Codex install preserves the adaptive diagnostic-only Debug contract', () => {
|
|
1938
|
+
inTempProject((root) => {
|
|
1939
|
+
const result = install(root);
|
|
1940
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
1941
|
+
|
|
1942
|
+
const installedSkill = fs.readFileSync(
|
|
1943
|
+
path.join(root, '.agents/skills/debug/SKILL.md'), 'utf8'
|
|
1944
|
+
);
|
|
1945
|
+
assert.match(installedSkill, /^name: cf-debug$/m);
|
|
1946
|
+
assert.match(installedSkill, /## Proportional depth/);
|
|
1947
|
+
assert.match(installedSkill, /Evidence Timeline/);
|
|
1948
|
+
assert.match(installedSkill, /### Elimination Path/);
|
|
1949
|
+
assert.match(installedSkill, /### Recurrence-Prevention Handoff/);
|
|
1950
|
+
assert.match(installedSkill, /`cf-debug` is read-only for product code/);
|
|
1951
|
+
|
|
1952
|
+
const installedAgent = fs.readFileSync(
|
|
1953
|
+
path.join(root, '.codex/agents/debugger.toml'), 'utf8'
|
|
1954
|
+
);
|
|
1955
|
+
assert.equal(
|
|
1956
|
+
installedAgent,
|
|
1957
|
+
convertCodexAgentContent(fs.readFileSync(DEBUG_AGENT_SOURCE_PATH, 'utf8'), 'debugger.md')
|
|
1958
|
+
);
|
|
1959
|
+
assert.match(installedAgent, /Never implement the repair/);
|
|
1960
|
+
|
|
1961
|
+
for (const relative of DEBUG_REFERENCE_FILES) {
|
|
1962
|
+
const source = fs.readFileSync(
|
|
1963
|
+
path.join(PACKAGE_ROOT, 'src/claude/references/debugger', relative), 'utf8'
|
|
1964
|
+
);
|
|
1965
|
+
const installed = fs.readFileSync(
|
|
1966
|
+
path.join(root, '.codex/references/debugger', relative), 'utf8'
|
|
1967
|
+
);
|
|
1968
|
+
assert.equal(installed, normalizeCodexBody(source, relative), relative);
|
|
1969
|
+
}
|
|
1970
|
+
assert.equal(fs.existsSync(DEBUG_SKILL_SOURCE_PATH), true);
|
|
1971
|
+
});
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1049
1974
|
test('Codex Windows hook launchers stay project-bound without Git from nested cwd', () => {
|
|
1050
1975
|
inTempProject((root) => {
|
|
1051
1976
|
const projectRoot = path.join(root, 'project with spaces');
|
|
@@ -1110,12 +2035,12 @@ test('Codex Windows hook launchers stay project-bound without Git from nested cw
|
|
|
1110
2035
|
});
|
|
1111
2036
|
});
|
|
1112
2037
|
|
|
1113
|
-
test('Codex
|
|
2038
|
+
test('Codex installed Specs and spec-maker reject adaptive coverage mutations', () => {
|
|
1114
2039
|
inTempProject((root) => {
|
|
1115
2040
|
const userInstructions = '# User rules\n\nKeep this exact.\n';
|
|
1116
2041
|
fs.writeFileSync(path.join(root, 'AGENTS.md'), userInstructions);
|
|
1117
2042
|
|
|
1118
|
-
const first = install(root);
|
|
2043
|
+
const first = install(root, ['--with-document-skills']);
|
|
1119
2044
|
assert.equal(first.status, 0, `${first.stdout}\n${first.stderr}`);
|
|
1120
2045
|
assert.equal(fs.existsSync(path.join(root, '.claude')), false);
|
|
1121
2046
|
assert.equal(fs.existsSync(path.join(root, '.codex', 'config.toml')), false);
|
|
@@ -1127,6 +2052,8 @@ test('Codex fresh install is exact while refresh and upgrade preserve existing f
|
|
|
1127
2052
|
'.codex/rules/workflow.md',
|
|
1128
2053
|
'.codex/rules/hook-protocols.md',
|
|
1129
2054
|
'.codex/rules/state-sync.md',
|
|
2055
|
+
'.codex/rules/review-audit-self-decision.md',
|
|
2056
|
+
'.codex/rules/process-management.md',
|
|
1130
2057
|
'.codex/scripts/spec-ground.cjs',
|
|
1131
2058
|
'.codex/scripts/validate-spec-output.cjs',
|
|
1132
2059
|
'.agents/.gitignore',
|
|
@@ -1199,6 +2126,12 @@ test('Codex fresh install is exact while refresh and upgrade preserve existing f
|
|
|
1199
2126
|
const name = path.basename(fileName, '.md').replace(/-/g, '_');
|
|
1200
2127
|
const agentPath = path.join(root, '.codex', 'agents', `${name}.toml`);
|
|
1201
2128
|
const content = fs.readFileSync(agentPath, 'utf8');
|
|
2129
|
+
const sourcePath = path.join(PACKAGE_ROOT, 'src', 'claude', 'agents', fileName);
|
|
2130
|
+
assert.equal(
|
|
2131
|
+
content,
|
|
2132
|
+
convertCodexAgentContent(fs.readFileSync(sourcePath, 'utf8'), fileName),
|
|
2133
|
+
`Codex agent projection drifted: ${fileName}`
|
|
2134
|
+
);
|
|
1202
2135
|
assert.equal(parseGeneratedTomlString(content, 'name'), name);
|
|
1203
2136
|
assert.ok(parseGeneratedTomlString(content, 'description').length > 8);
|
|
1204
2137
|
assert.ok(parseGeneratedTomlString(content, 'developer_instructions').length > 20);
|
|
@@ -1217,6 +2150,27 @@ test('Codex fresh install is exact while refresh and upgrade preserve existing f
|
|
|
1217
2150
|
assert.equal(new Set(skillNames).size, skillNames.length);
|
|
1218
2151
|
assert.ok(skillNames.every((name) => /^[a-z0-9-]+$/.test(name)));
|
|
1219
2152
|
|
|
2153
|
+
const scoutSkill = fs.readFileSync(
|
|
2154
|
+
path.join(root, '.agents', 'skills', 'inspect', 'SKILL.md'),
|
|
2155
|
+
'utf8'
|
|
2156
|
+
);
|
|
2157
|
+
assert.match(scoutSkill, /^name:\s*cf-scout$/m);
|
|
2158
|
+
assert.equal(
|
|
2159
|
+
fs.existsSync(path.join(root, '.agents', 'skills', 'scout')),
|
|
2160
|
+
false,
|
|
2161
|
+
'public Scout rename must keep the manifest-owned inspect directory'
|
|
2162
|
+
);
|
|
2163
|
+
const askSkill = fs.readFileSync(
|
|
2164
|
+
path.join(root, '.agents', 'skills', 'question', 'SKILL.md'),
|
|
2165
|
+
'utf8'
|
|
2166
|
+
);
|
|
2167
|
+
assert.match(askSkill, /^name:\s*cf-ask$/m);
|
|
2168
|
+
assert.equal(
|
|
2169
|
+
fs.existsSync(path.join(root, '.agents', 'skills', 'ask')),
|
|
2170
|
+
false,
|
|
2171
|
+
'public Ask rename must keep the manifest-owned question directory'
|
|
2172
|
+
);
|
|
2173
|
+
|
|
1220
2174
|
const catalog = spawnSync(
|
|
1221
2175
|
process.execPath,
|
|
1222
2176
|
[path.join(root, '.codex', 'scripts', 'generate-skill-catalog.cjs'), '--json'],
|
|
@@ -1230,12 +2184,13 @@ test('Codex fresh install is exact while refresh and upgrade preserve existing f
|
|
|
1230
2184
|
fs.realpathSync(path.join(root, '.agents', 'skills'))
|
|
1231
2185
|
);
|
|
1232
2186
|
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
2187
|
+
for (const retired of MANIFEST.obsolete.skills) {
|
|
2188
|
+
assert.equal(
|
|
2189
|
+
fs.existsSync(path.join(root, '.agents', 'skills', retired)),
|
|
2190
|
+
false,
|
|
2191
|
+
`retired skill should not be installed: ${retired}`
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
1239
2194
|
|
|
1240
2195
|
const multimodalScript = fs.readFileSync(
|
|
1241
2196
|
path.join(root, '.agents', 'skills', 'ai-multimodal', 'scripts', 'gemini_batch_process.py'),
|
|
@@ -1256,10 +2211,10 @@ test('Codex fresh install is exact while refresh and upgrade preserve existing f
|
|
|
1256
2211
|
assert.doesNotMatch(visible, /If you are Codex CLI or any other runtime, ignore this entire Codex block/);
|
|
1257
2212
|
assert.doesNotMatch(
|
|
1258
2213
|
visible.replaceAll(foreignRuntimeBoundary, ''),
|
|
1259
|
-
/\bAgent\(|subagent_type|`Agent`|\bSendMessage\b|\/
|
|
2214
|
+
/\bAgent\(|subagent_type|`Agent`|\bSendMessage\b|\/cf:|\bcf:|Claude Code/,
|
|
1260
2215
|
);
|
|
1261
2216
|
assert.doesNotMatch(visible, /@@PRIVACY_PROMPT_START@@|Claude Tasks/);
|
|
1262
|
-
assert.match(visible, /\$
|
|
2217
|
+
assert.match(visible, /\$cf-specs/);
|
|
1263
2218
|
|
|
1264
2219
|
const agentsMd = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8');
|
|
1265
2220
|
assert.ok(agentsMd.startsWith(userInstructions));
|
|
@@ -1316,6 +2271,732 @@ test('Codex fresh install is exact while refresh and upgrade preserve existing f
|
|
|
1316
2271
|
});
|
|
1317
2272
|
});
|
|
1318
2273
|
|
|
2274
|
+
test('Codex installed Brainstorm skill reference and agent preserve proportional routing parity', () => {
|
|
2275
|
+
inTempProject((root) => {
|
|
2276
|
+
const sourceBytes = canonicalBrainstormSourceBytes();
|
|
2277
|
+
const result = install(root);
|
|
2278
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2279
|
+
|
|
2280
|
+
const installedSkillPath = path.join(root, '.agents/skills/brainstorm/SKILL.md');
|
|
2281
|
+
const installedReferencePath = path.join(
|
|
2282
|
+
root,
|
|
2283
|
+
'.agents/skills/brainstorm/references/question-framework.md'
|
|
2284
|
+
);
|
|
2285
|
+
const installedAgentPath = path.join(root, '.codex/agents/brainstormer.toml');
|
|
2286
|
+
const sourceSkill = fs.readFileSync(BRAINSTORM_SKILL_SOURCE_PATH, 'utf8');
|
|
2287
|
+
const sourceReference = fs.readFileSync(BRAINSTORM_REFERENCE_SOURCE_PATH, 'utf8');
|
|
2288
|
+
const sourceAgent = fs.readFileSync(BRAINSTORM_AGENT_SOURCE_PATH, 'utf8');
|
|
2289
|
+
|
|
2290
|
+
assert.equal(
|
|
2291
|
+
fs.readFileSync(installedSkillPath, 'utf8'),
|
|
2292
|
+
normalizeCodexBody(sourceSkill, BRAINSTORM_SKILL_SOURCE_PATH),
|
|
2293
|
+
'installed Brainstorm skill must equal the generic Codex projection'
|
|
2294
|
+
);
|
|
2295
|
+
assert.equal(
|
|
2296
|
+
fs.readFileSync(installedReferencePath, 'utf8'),
|
|
2297
|
+
normalizeCodexBody(sourceReference, BRAINSTORM_REFERENCE_SOURCE_PATH),
|
|
2298
|
+
'installed Brainstorm reference must equal the generic Codex projection'
|
|
2299
|
+
);
|
|
2300
|
+
assert.equal(
|
|
2301
|
+
fs.readFileSync(installedAgentPath, 'utf8'),
|
|
2302
|
+
convertCodexAgentContent(sourceAgent, path.basename(BRAINSTORM_AGENT_SOURCE_PATH)),
|
|
2303
|
+
'installed brainstormer agent must equal the Codex TOML projection'
|
|
2304
|
+
);
|
|
2305
|
+
|
|
2306
|
+
assert.deepEqual(brainstormProjectionIssues(readInstalledBrainstormProjection(root)), []);
|
|
2307
|
+
assertCanonicalBrainstormSourceBytesUnchanged(sourceBytes);
|
|
2308
|
+
|
|
2309
|
+
const safelyStrengthened = readInstalledBrainstormProjection(root);
|
|
2310
|
+
safelyStrengthened.agent += '\nThe specialist may never ask the user directly or contact the user. Do not write files or mutate shared task state. Do not delegate work or delegate tasks. The specialist is not permitted to launch Specs or run Develop.';
|
|
2311
|
+
assert.deepEqual(
|
|
2312
|
+
brainstormProjectionIssues(safelyStrengthened),
|
|
2313
|
+
[],
|
|
2314
|
+
'safe specialist prohibitions must not produce authority issues'
|
|
2315
|
+
);
|
|
2316
|
+
|
|
2317
|
+
const mutations = [
|
|
2318
|
+
{
|
|
2319
|
+
name: 'adaptive-direct-precedence-removed', source: 'skill',
|
|
2320
|
+
from: 'Route Direct first, then\napply controls only to requests that remain in Brainstorm.',
|
|
2321
|
+
to: 'Apply controls before Direct classification.', expected: ['adaptive-direct-precedence']
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
name: 'adaptive-ordered-depth-removed', source: 'skill',
|
|
2325
|
+
from: 'With no Deep signal, use Standard.', to: 'Deep is always the default.',
|
|
2326
|
+
expected: ['adaptive-ordered-depth']
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
name: 'adaptive-leading-flags-removed', source: 'skill',
|
|
2330
|
+
from: 'Parse controls only from the leading consecutive token segment.',
|
|
2331
|
+
to: 'Parse flag-like tokens anywhere.', expected: ['adaptive-leading-flags']
|
|
2332
|
+
},
|
|
2333
|
+
{
|
|
2334
|
+
name: 'adaptive-failure-isolation-removed', source: 'skill',
|
|
2335
|
+
from: 'failure isolation for partial or cascading failure\nacross boundaries',
|
|
2336
|
+
to: 'generic failure notes', expected: ['adaptive-lens-trigger-skip']
|
|
2337
|
+
},
|
|
2338
|
+
{
|
|
2339
|
+
name: 'adaptive-evidence-fallback-removed', source: 'skill',
|
|
2340
|
+
from: 'Missing evidence\nforces feasibility `unknown` and confidence `low`.',
|
|
2341
|
+
to: 'Missing evidence permits a confident guess.', expected: ['adaptive-evidence-semantics']
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
name: 'adaptive-numeric-evidence-removed', source: 'skill',
|
|
2345
|
+
from: 'A numeric estimate requires\nrange, unit, basis, evidence, and assumptions; otherwise report `unknown`.',
|
|
2346
|
+
to: 'A numeric estimate may be a best-effort number.', expected: ['adaptive-numeric-estimates']
|
|
2347
|
+
},
|
|
2348
|
+
{
|
|
2349
|
+
name: 'adaptive-pre-tool-redaction-removed', source: 'skill',
|
|
2350
|
+
from: 'Before an\nexternal visual tool or adviser handoff, minimize context and redact secrets,\ncredentials, private keys, access tokens, and unnecessary PII.',
|
|
2351
|
+
to: 'Forward full context to every external tool and adviser.', expected: ['adaptive-pre-tool-redaction']
|
|
2352
|
+
},
|
|
2353
|
+
{
|
|
2354
|
+
name: 'adaptive-adviser-gate-removed', source: 'skill',
|
|
2355
|
+
from: '`--advice` invokes\n`brainstormer` only after the material-choice gate;',
|
|
2356
|
+
to: '`--advice` invokes `brainstormer` before routing;', expected: ['adaptive-adviser-gate']
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
name: 'adaptive-decision-freshness-removed', source: 'skill',
|
|
2360
|
+
from: 'The first section records target\nidentity, current source revision and worktree state or `[UNVERIFIED]`, an\nevidence-as-of value, and what change invalidates the brief.',
|
|
2361
|
+
to: 'The handoff has no revision or freshness binding.', expected: ['adaptive-decision-freshness']
|
|
2362
|
+
},
|
|
2363
|
+
{
|
|
2364
|
+
name: 'adaptive-overlay-authority-removed', source: 'skill',
|
|
2365
|
+
from: 'Neither overlay\nwrites, approves, persists, dispatches, or completes work.',
|
|
2366
|
+
to: 'Overlays may persist, approve, dispatch, and complete work.', expected: ['adaptive-non-authority']
|
|
2367
|
+
},
|
|
2368
|
+
{
|
|
2369
|
+
name: 'debug-route-removed',
|
|
2370
|
+
source: 'skill',
|
|
2371
|
+
from: 'Then use `cf-debug` until',
|
|
2372
|
+
to: 'Skip diagnosis and choose a remedy before',
|
|
2373
|
+
expected: ['bug-routing']
|
|
2374
|
+
},
|
|
2375
|
+
{
|
|
2376
|
+
name: 'fix-authority-removed',
|
|
2377
|
+
source: 'skill',
|
|
2378
|
+
from: 'only when the user explicitly requested a fix',
|
|
2379
|
+
to: 'automatically whenever root cause is known',
|
|
2380
|
+
expected: ['bug-routing']
|
|
2381
|
+
},
|
|
2382
|
+
{
|
|
2383
|
+
name: 'exploration-stop-removed',
|
|
2384
|
+
source: 'skill',
|
|
2385
|
+
from: 'Do not request design approval, persist a report, or\n invoke another workflow without a new explicit request.',
|
|
2386
|
+
to: 'Request approval, persist a report, and invoke another workflow.',
|
|
2387
|
+
expected: ['exploration-stop']
|
|
2388
|
+
},
|
|
2389
|
+
{
|
|
2390
|
+
name: 'conditional-options-removed',
|
|
2391
|
+
source: 'skill',
|
|
2392
|
+
from: 'For a material choice, compare 2–3 mechanically distinct viable approaches',
|
|
2393
|
+
to: 'For every request, present one predetermined approach',
|
|
2394
|
+
expected: ['option-cardinality']
|
|
2395
|
+
},
|
|
2396
|
+
{
|
|
2397
|
+
name: 'specialist-strawman-boundary-removed',
|
|
2398
|
+
source: 'agent',
|
|
2399
|
+
from: 'never invent strawmen to fill a quota.',
|
|
2400
|
+
to: 'invent strawmen to fill the quota.',
|
|
2401
|
+
expected: ['option-cardinality']
|
|
2402
|
+
},
|
|
2403
|
+
{
|
|
2404
|
+
name: 'specialist-controller-ownership-removed',
|
|
2405
|
+
source: 'agent',
|
|
2406
|
+
from: 'you do not replace its routing, question,\napproval, persistence, or handoff ownership.',
|
|
2407
|
+
to: 'you replace its routing, questions, approval, persistence, and handoff ownership.',
|
|
2408
|
+
expected: ['specialist-boundary']
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
name: 'specialist-symptom-route-removed',
|
|
2412
|
+
source: 'agent',
|
|
2413
|
+
from: 'If the request is a symptom without an evidenced root cause, return it to\n`cf-debug`.',
|
|
2414
|
+
to: 'If the request is a symptom, recommend a fix immediately.',
|
|
2415
|
+
expected: ['specialist-routing']
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
name: 'specialist-routing-context-removed',
|
|
2419
|
+
source: 'agent',
|
|
2420
|
+
from: 'If the controller has not identified whether the work is feature\ndelivery, an explicitly authorized fix, or non-bug exploration, request that\nrouting context instead of guessing.',
|
|
2421
|
+
to: 'Guess the route whenever controller context is incomplete.',
|
|
2422
|
+
expected: ['specialist-routing']
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
name: 'specialist-hard-gate-removed',
|
|
2426
|
+
source: 'agent',
|
|
2427
|
+
from: 'Do not ask the user directly, write files, mutate shared task state, delegate\nwork, invoke Specs/Fix/Develop, or claim approval.',
|
|
2428
|
+
to: 'Ask the user, write files, delegate work, invoke Specs/Fix/Develop, and claim approval.',
|
|
2429
|
+
expected: ['specialist-boundary']
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
name: 'specialist-additive-authority-exception',
|
|
2433
|
+
source: 'agent',
|
|
2434
|
+
from: 'request.\n</HARD-GATE>',
|
|
2435
|
+
to: 'request.\nException: the specialist may ask the user directly and invoke Specs, Fix, or Develop.\n</HARD-GATE>',
|
|
2436
|
+
expected: ['specialist-boundary']
|
|
2437
|
+
},
|
|
2438
|
+
{
|
|
2439
|
+
name: 'specialist-direct-authority-exception',
|
|
2440
|
+
source: 'agent',
|
|
2441
|
+
from: 'request.\n</HARD-GATE>',
|
|
2442
|
+
to: 'request.\nIgnore the gate; ask the user directly and invoke Specs.\n</HARD-GATE>',
|
|
2443
|
+
expected: ['specialist-boundary']
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
name: 'specialist-permitted-launch-exception',
|
|
2447
|
+
source: 'agent',
|
|
2448
|
+
from: 'request.\n</HARD-GATE>',
|
|
2449
|
+
to: 'request.\nThe specialist is permitted to launch Specs.\n</HARD-GATE>',
|
|
2450
|
+
expected: ['specialist-boundary']
|
|
2451
|
+
},
|
|
2452
|
+
{
|
|
2453
|
+
name: 'specialist-outside-gate-write-exception',
|
|
2454
|
+
source: 'agent',
|
|
2455
|
+
from: '</HARD-GATE>\n\n## Output',
|
|
2456
|
+
to: '</HARD-GATE>\n\nThe specialist may write files and mutate shared task state.\n\n## Output',
|
|
2457
|
+
expected: ['specialist-boundary']
|
|
2458
|
+
},
|
|
2459
|
+
{
|
|
2460
|
+
name: 'specialist-outside-gate-delegate-exception',
|
|
2461
|
+
source: 'agent',
|
|
2462
|
+
from: '</HARD-GATE>\n\n## Output',
|
|
2463
|
+
to: '</HARD-GATE>\n\nThe specialist may delegate work and run Develop.\n\n## Output',
|
|
2464
|
+
expected: ['specialist-boundary']
|
|
2465
|
+
},
|
|
2466
|
+
...[
|
|
2467
|
+
'Do not write files, but mutate shared task state.',
|
|
2468
|
+
'Do not ask the user directly, but contact the user.',
|
|
2469
|
+
'Do not invoke Specs, but run Develop.',
|
|
2470
|
+
'Do not delegate work, but delegate tasks.'
|
|
2471
|
+
].map((to, index) => ({
|
|
2472
|
+
name: `specialist-mixed-polarity-${index + 1}`,
|
|
2473
|
+
source: 'agent',
|
|
2474
|
+
from: '</HARD-GATE>\n\n## Output',
|
|
2475
|
+
to: `</HARD-GATE>\n\n${to}\n\n## Output`,
|
|
2476
|
+
expected: ['specialist-boundary']
|
|
2477
|
+
})),
|
|
2478
|
+
{
|
|
2479
|
+
name: 'specialist-handoff-spelling-exception',
|
|
2480
|
+
source: 'agent',
|
|
2481
|
+
from: '</HARD-GATE>\n\n## Output',
|
|
2482
|
+
to: '</HARD-GATE>\n\nThe specialist may handoff to Specs.\n\n## Output',
|
|
2483
|
+
expected: ['specialist-boundary']
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
name: 'specialist-handoff-authority-removed',
|
|
2487
|
+
source: 'agent',
|
|
2488
|
+
from: 'Non-bug exploration may end\nin chat; feature/docs delivery may only prepare a future explicit Specs\ninvocation; bug handoff requires evidenced root cause and the user\'s explicit fix\nrequest.',
|
|
2489
|
+
to: 'Every route may invoke delivery workflows automatically.',
|
|
2490
|
+
expected: ['specialist-handoff']
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
name: 'decision-provenance-removed',
|
|
2494
|
+
source: 'framework',
|
|
2495
|
+
from: 'Do not write "user selected" unless direct user text or the native input tool\n confirms it.',
|
|
2496
|
+
to: 'Write "user selected" for inferred defaults.',
|
|
2497
|
+
expected: ['approval-persistence']
|
|
2498
|
+
},
|
|
2499
|
+
{
|
|
2500
|
+
name: 'installed-grammar-corruption',
|
|
2501
|
+
source: 'skill',
|
|
2502
|
+
from: '## Completion bar',
|
|
2503
|
+
to: 'one a structured user-input request call\n\n## Completion bar',
|
|
2504
|
+
expected: ['projection-grammar']
|
|
2505
|
+
}
|
|
2506
|
+
];
|
|
2507
|
+
|
|
2508
|
+
for (const mutation of mutations) {
|
|
2509
|
+
const target = assertInstalledBrainstormMutationTarget(root, mutation.source);
|
|
2510
|
+
const original = fs.readFileSync(target);
|
|
2511
|
+
const fullContent = original.toString('utf8');
|
|
2512
|
+
const projectedContent = mutation.source === 'agent'
|
|
2513
|
+
? parseGeneratedTomlString(fullContent, 'developer_instructions')
|
|
2514
|
+
: fullContent;
|
|
2515
|
+
const anchor = projectedContent.indexOf(mutation.from);
|
|
2516
|
+
assert.ok(anchor >= 0, `${mutation.name} mutation anchor must exist`);
|
|
2517
|
+
assert.equal(
|
|
2518
|
+
projectedContent.indexOf(mutation.from, anchor + mutation.from.length),
|
|
2519
|
+
-1,
|
|
2520
|
+
`${mutation.name} mutation anchor must be unique`
|
|
2521
|
+
);
|
|
2522
|
+
const weakened = `${projectedContent.slice(0, anchor)}${mutation.to}${projectedContent.slice(anchor + mutation.from.length)}`;
|
|
2523
|
+
const rendered = mutation.source === 'agent'
|
|
2524
|
+
? replaceGeneratedTomlString(fullContent, 'developer_instructions', weakened)
|
|
2525
|
+
: weakened;
|
|
2526
|
+
try {
|
|
2527
|
+
fs.writeFileSync(assertInstalledBrainstormMutationTarget(root, mutation.source), rendered);
|
|
2528
|
+
assertCanonicalBrainstormSourceBytesUnchanged(sourceBytes);
|
|
2529
|
+
assert.deepEqual(
|
|
2530
|
+
brainstormProjectionIssues(readInstalledBrainstormProjection(root)),
|
|
2531
|
+
mutation.expected,
|
|
2532
|
+
mutation.name
|
|
2533
|
+
);
|
|
2534
|
+
} finally {
|
|
2535
|
+
fs.writeFileSync(assertInstalledBrainstormMutationTarget(root, mutation.source), original);
|
|
2536
|
+
}
|
|
2537
|
+
assert.deepEqual(fs.readFileSync(target), original, `${mutation.name} byte restore`);
|
|
2538
|
+
assert.deepEqual(brainstormProjectionIssues(readInstalledBrainstormProjection(root)), []);
|
|
2539
|
+
assertCanonicalBrainstormSourceBytesUnchanged(sourceBytes);
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
test('Codex installed Develop preserves plan-native execution and references', () => {
|
|
2545
|
+
inTempProject((root) => {
|
|
2546
|
+
const sourceBytes = new Map(DEVELOP_BUNDLE.map((relative) => [
|
|
2547
|
+
path.join(DEVELOP_SOURCE_ROOT, relative),
|
|
2548
|
+
fs.readFileSync(path.join(DEVELOP_SOURCE_ROOT, relative))
|
|
2549
|
+
]));
|
|
2550
|
+
const result = install(root);
|
|
2551
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2552
|
+
|
|
2553
|
+
const installedRoot = path.join(root, '.agents/skills/develop');
|
|
2554
|
+
for (const relative of DEVELOP_BUNDLE) {
|
|
2555
|
+
const sourcePath = path.join(DEVELOP_SOURCE_ROOT, relative);
|
|
2556
|
+
assert.equal(
|
|
2557
|
+
fs.readFileSync(path.join(installedRoot, relative), 'utf8'),
|
|
2558
|
+
normalizeCodexBody(fs.readFileSync(sourcePath, 'utf8'), sourcePath),
|
|
2559
|
+
`Codex Develop projection drifted: ${relative}`
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
const installedSkill = fs.readFileSync(path.join(installedRoot, 'SKILL.md'), 'utf8');
|
|
2563
|
+
assert.match(installedSkill, /\$cf-develop <feature>/);
|
|
2564
|
+
assert.match(installedSkill, /\.codex\/scripts\/workflow-policy\.cjs/);
|
|
2565
|
+
assert.doesNotMatch(installedSkill, /\/cf:develop|\.claude\/scripts/);
|
|
2566
|
+
assert.equal(fs.existsSync(path.join(root, '.claude/skills/develop')), false);
|
|
2567
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
2568
|
+
assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
test('Codex installed Research preserves adaptive evidence semantics', () => {
|
|
2574
|
+
inTempProject((root) => {
|
|
2575
|
+
const sourceBytes = new Map([
|
|
2576
|
+
[RESEARCH_SKILL_SOURCE_PATH, fs.readFileSync(RESEARCH_SKILL_SOURCE_PATH)],
|
|
2577
|
+
[RESEARCH_AGENT_SOURCE_PATH, fs.readFileSync(RESEARCH_AGENT_SOURCE_PATH)],
|
|
2578
|
+
]);
|
|
2579
|
+
const result = install(root);
|
|
2580
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2581
|
+
|
|
2582
|
+
const installedSkillPath = path.join(root, '.agents/skills/research/SKILL.md');
|
|
2583
|
+
const installedAgentPath = path.join(root, '.codex/agents/researcher.toml');
|
|
2584
|
+
assert.equal(
|
|
2585
|
+
fs.readFileSync(installedSkillPath, 'utf8'),
|
|
2586
|
+
normalizeCodexBody(fs.readFileSync(RESEARCH_SKILL_SOURCE_PATH, 'utf8'), RESEARCH_SKILL_SOURCE_PATH)
|
|
2587
|
+
);
|
|
2588
|
+
assert.equal(
|
|
2589
|
+
fs.readFileSync(installedAgentPath, 'utf8'),
|
|
2590
|
+
convertCodexAgentContent(fs.readFileSync(RESEARCH_AGENT_SOURCE_PATH, 'utf8'), 'researcher.md')
|
|
2591
|
+
);
|
|
2592
|
+
const installedSkill = fs.readFileSync(installedSkillPath, 'utf8');
|
|
2593
|
+
const installedAgent = parseGeneratedTomlString(
|
|
2594
|
+
fs.readFileSync(installedAgentPath, 'utf8'), 'developer_instructions'
|
|
2595
|
+
);
|
|
2596
|
+
assert.match(installedSkill, /^name: cf-research$/m);
|
|
2597
|
+
assert.match(installedSkill, /Quick \| One low-risk, reversible fact or known option/);
|
|
2598
|
+
assert.match(installedSkill, /research sequentially with the same evidence bar/);
|
|
2599
|
+
assert.match(installedSkill, /Default to a concise answer in chat/);
|
|
2600
|
+
assert.match(installedSkill, /confirmed \| inferred \| unresolved/);
|
|
2601
|
+
assert.match(installedAgent, /do not implement code,\s+write files, mutate task state/);
|
|
2602
|
+
assert.match(installedAgent, /owns any\s+authorized persistence/);
|
|
2603
|
+
assert.doesNotMatch(installedSkill, /must (?:instantly |always )?delegate/i);
|
|
2604
|
+
|
|
2605
|
+
const manifest = JSON.parse(
|
|
2606
|
+
fs.readFileSync(path.join(root, '.codex/cafekit-manifest.json'), 'utf8')
|
|
2607
|
+
);
|
|
2608
|
+
assert.ok(manifest.files['.agents/skills/research/SKILL.md']);
|
|
2609
|
+
assert.ok(manifest.files['.codex/agents/researcher.toml']);
|
|
2610
|
+
assert.equal(fs.existsSync(path.join(root, '.claude/skills/research')), false);
|
|
2611
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
2612
|
+
assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
});
|
|
2616
|
+
|
|
2617
|
+
test('Codex installed Test preserves plan-native proof and references', () => {
|
|
2618
|
+
inTempProject((root) => {
|
|
2619
|
+
const sourcePaths = [
|
|
2620
|
+
...TEST_BUNDLE.map((relative) => path.join(TEST_SOURCE_ROOT, relative)),
|
|
2621
|
+
TEST_RUNNER_SOURCE_PATH,
|
|
2622
|
+
path.join(CODE_REVIEW_SOURCE_ROOT, 'SKILL.md')
|
|
2623
|
+
];
|
|
2624
|
+
const sourceBytes = new Map(sourcePaths.map((sourcePath) => [
|
|
2625
|
+
sourcePath, fs.readFileSync(sourcePath)
|
|
2626
|
+
]));
|
|
2627
|
+
const result = install(root);
|
|
2628
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2629
|
+
|
|
2630
|
+
const installedRoot = path.join(root, '.agents/skills/test');
|
|
2631
|
+
for (const relative of TEST_BUNDLE) {
|
|
2632
|
+
const sourcePath = path.join(TEST_SOURCE_ROOT, relative);
|
|
2633
|
+
assert.equal(
|
|
2634
|
+
fs.readFileSync(path.join(installedRoot, relative), 'utf8'),
|
|
2635
|
+
normalizeCodexBody(fs.readFileSync(sourcePath, 'utf8'), sourcePath),
|
|
2636
|
+
`Codex Test projection drifted: ${relative}`
|
|
2637
|
+
);
|
|
2638
|
+
}
|
|
2639
|
+
const installedSkill = fs.readFileSync(path.join(installedRoot, 'SKILL.md'), 'utf8');
|
|
2640
|
+
assert.match(installedSkill, /\$cf-test/);
|
|
2641
|
+
assert.match(installedSkill, /test-proof-v1/);
|
|
2642
|
+
assert.doesNotMatch(installedSkill, /\/cf:test/);
|
|
2643
|
+
|
|
2644
|
+
const installedRunnerPath = path.join(root, '.codex/agents/test_runner.toml');
|
|
2645
|
+
assert.equal(
|
|
2646
|
+
fs.readFileSync(installedRunnerPath, 'utf8'),
|
|
2647
|
+
convertCodexAgentContent(
|
|
2648
|
+
fs.readFileSync(TEST_RUNNER_SOURCE_PATH, 'utf8'),
|
|
2649
|
+
path.basename(TEST_RUNNER_SOURCE_PATH)
|
|
2650
|
+
),
|
|
2651
|
+
'Codex test_runner projection drifted'
|
|
2652
|
+
);
|
|
2653
|
+
const installedRunner = parseGeneratedTomlString(
|
|
2654
|
+
fs.readFileSync(installedRunnerPath, 'utf8'), 'developer_instructions'
|
|
2655
|
+
);
|
|
2656
|
+
assert.match(installedRunner, /test-proof-v1/);
|
|
2657
|
+
assert.match(installedRunner, /sole writer of[\s\S]*Status and inline Receipt/i);
|
|
2658
|
+
|
|
2659
|
+
const reviewSourcePath = path.join(CODE_REVIEW_SOURCE_ROOT, 'SKILL.md');
|
|
2660
|
+
const installedReview = fs.readFileSync(path.join(root, '.agents/skills/code-review/SKILL.md'), 'utf8');
|
|
2661
|
+
assert.equal(installedReview, normalizeCodexBody(
|
|
2662
|
+
fs.readFileSync(reviewSourcePath, 'utf8'), reviewSourcePath
|
|
2663
|
+
));
|
|
2664
|
+
assert.match(installedReview, /controller-validated `test-proof-v1`/);
|
|
2665
|
+
assert.match(installedReview, /Never create or search for a separate[\s\S]*process-first receipt/i);
|
|
2666
|
+
assert.equal(fs.existsSync(path.join(root, '.claude/skills/test')), false);
|
|
2667
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
2668
|
+
assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
});
|
|
2672
|
+
|
|
2673
|
+
function hotfixProjectionIssues(files) {
|
|
2674
|
+
const compact = (value) => String(value).replace(/\s+/g, ' ').trim();
|
|
2675
|
+
const skill = compact(files.skill);
|
|
2676
|
+
const review = compact(files.review);
|
|
2677
|
+
const parallel = compact(files.parallel);
|
|
2678
|
+
const specialized = compact(files.specialized);
|
|
2679
|
+
const issues = new Set();
|
|
2680
|
+
if (!skill.includes('name: cf-fix')
|
|
2681
|
+
|| !skill.includes('# Fix — root-cause repair workflow')
|
|
2682
|
+
|| skill.includes('name: cf-hotfix')) {
|
|
2683
|
+
issues.add('public-rename');
|
|
2684
|
+
}
|
|
2685
|
+
if (!skill.includes('## Proportional depth')
|
|
2686
|
+
|| !skill.includes('Quick mode only reduces depth; it never skips scout, pre-fix evidence, diagnosis, or before/after verification.')) {
|
|
2687
|
+
issues.add('adaptive-depth');
|
|
2688
|
+
}
|
|
2689
|
+
if (!skill.includes('`Timeline: skipped - <reason>` or `- skipped: <reason>`')
|
|
2690
|
+
|| !skill.includes('`Recurrence-Prevention Handoff`, when present, carries evidence-backed candidates only.')
|
|
2691
|
+
|| !skill.includes('routes back to diagnosis (`cf-debug`)')) {
|
|
2692
|
+
issues.add('debug-handoff');
|
|
2693
|
+
}
|
|
2694
|
+
if (!skill.includes('report `PASS | PASS_WITH_WARNINGS | FAIL | BLOCKED`')
|
|
2695
|
+
|| !skill.includes('The definition of `PASS` defers to `cf-code-review`.')
|
|
2696
|
+
|| skill.includes('Confidence score')
|
|
2697
|
+
|| !review.includes('Only `FAIL` and `PASS_WITH_WARNINGS` enter remediation retry')
|
|
2698
|
+
|| !review.includes('only a fresh literal `PASS` enters finalization')
|
|
2699
|
+
|| review.includes('"Approve anyway"')
|
|
2700
|
+
|| review.includes('"Approve with known issues"')
|
|
2701
|
+
|| review.includes('at most one Medium')) {
|
|
2702
|
+
issues.add('verdict-surface');
|
|
2703
|
+
}
|
|
2704
|
+
if (!skill.includes('The user explicitly requested or permitted delegation or parallel agents.')
|
|
2705
|
+
|| !skill.includes('The active runtime exposes an Explore/delegation capability.')
|
|
2706
|
+
|| !skill.includes('at least two distinct, non-overlapping scopes')
|
|
2707
|
+
|| !parallel.includes('The user explicitly requested or permitted delegation or parallel agents.')) {
|
|
2708
|
+
issues.add('delegation-gate');
|
|
2709
|
+
}
|
|
2710
|
+
if (!skill.includes('The original symptom no longer reproduces with the exact pre-fix command/user flow.')
|
|
2711
|
+
|| !skill.includes('Do not silently patch around the regression.')) {
|
|
2712
|
+
issues.add('side-effect-gate');
|
|
2713
|
+
}
|
|
2714
|
+
if (!skill.includes('## Bounded repair frame')
|
|
2715
|
+
|| !skill.includes('Quick/local does not add a separate framing ceremony')
|
|
2716
|
+
|| !skill.includes('**Outcome:**')
|
|
2717
|
+
|| !skill.includes('**Constraints:**')
|
|
2718
|
+
|| !skill.includes('**Non-goals:**')
|
|
2719
|
+
|| !skill.includes('**Acceptance:**')) {
|
|
2720
|
+
issues.add('bounded-repair-frame');
|
|
2721
|
+
}
|
|
2722
|
+
if (!skill.includes('after diagnosis, research only unresolved external facts')
|
|
2723
|
+
|| !skill.includes('`cf-brainstorm` to compare 2-3 options')
|
|
2724
|
+
|| !skill.includes('When diagnosis leaves one safe direct repair, skip research and')) {
|
|
2725
|
+
issues.add('deep-decision-route');
|
|
2726
|
+
}
|
|
2727
|
+
if (!skill.includes('Load only the matching')
|
|
2728
|
+
|| !specialized.includes('Load only the matching section')
|
|
2729
|
+
|| (specialized.match(/\*\*Baseline:\*\*/g) || []).length < 5
|
|
2730
|
+
|| (specialized.match(/\*\*Proof:\*\*/g) || []).length < 5) {
|
|
2731
|
+
issues.add('specialized-proof-overlays');
|
|
2732
|
+
}
|
|
2733
|
+
if (!parallel.includes('Diagnosis still starts only')
|
|
2734
|
+
|| !parallel.includes('after the required scout outputs are synthesized')
|
|
2735
|
+
|| !parallel.includes('Research begins only after Step 2 diagnosis')
|
|
2736
|
+
|| parallel.includes('scout + diagnose + research together')
|
|
2737
|
+
|| parallel.includes("You don't need to wait for scouting")) {
|
|
2738
|
+
issues.add('scout-before-diagnosis');
|
|
2739
|
+
}
|
|
2740
|
+
return [...issues].sort();
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
test('Codex installed Fix preserves the adaptive repair contract', () => {
|
|
2744
|
+
inTempProject((root) => {
|
|
2745
|
+
const sourceBytes = new Map(HOTFIX_BUNDLE.map((relative) => [
|
|
2746
|
+
path.join(HOTFIX_SOURCE_ROOT, relative),
|
|
2747
|
+
fs.readFileSync(path.join(HOTFIX_SOURCE_ROOT, relative))
|
|
2748
|
+
]));
|
|
2749
|
+
const result = install(root);
|
|
2750
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2751
|
+
|
|
2752
|
+
const installedRoot = path.join(root, '.agents/skills/hotfix');
|
|
2753
|
+
assert.equal(fs.existsSync(path.join(root, '.agents/skills/fix')), false,
|
|
2754
|
+
'public Fix rename must keep the manifest-owned hotfix directory');
|
|
2755
|
+
for (const relative of HOTFIX_BUNDLE) {
|
|
2756
|
+
const sourcePath = path.join(HOTFIX_SOURCE_ROOT, relative);
|
|
2757
|
+
assert.equal(
|
|
2758
|
+
fs.readFileSync(path.join(installedRoot, relative), 'utf8'),
|
|
2759
|
+
normalizeCodexBody(fs.readFileSync(sourcePath, 'utf8'), sourcePath),
|
|
2760
|
+
`Codex Fix projection drifted: ${relative}`
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
const readProjection = () => ({
|
|
2764
|
+
skill: fs.readFileSync(path.join(installedRoot, 'SKILL.md'), 'utf8'),
|
|
2765
|
+
review: fs.readFileSync(path.join(installedRoot, 'references/review-cycle.md'), 'utf8'),
|
|
2766
|
+
parallel: fs.readFileSync(path.join(installedRoot, 'references/parallel-patterns.md'), 'utf8'),
|
|
2767
|
+
specialized: fs.readFileSync(path.join(installedRoot, 'references/workflow-specialized.md'), 'utf8'),
|
|
2768
|
+
});
|
|
2769
|
+
const installedSkill = readProjection().skill;
|
|
2770
|
+
assert.match(installedSkill, /name: cf-fix/);
|
|
2771
|
+
assert.doesNotMatch(installedSkill, /name: cf-hotfix|\/cf:fix|cf:debug|cf:code-review/);
|
|
2772
|
+
assert.deepEqual(hotfixProjectionIssues(readProjection()), []);
|
|
2773
|
+
|
|
2774
|
+
const mutations = [
|
|
2775
|
+
{
|
|
2776
|
+
name: 'public-name-reverts', file: 'SKILL.md',
|
|
2777
|
+
from: 'name: cf-fix', to: 'name: cf-hotfix',
|
|
2778
|
+
expected: ['public-rename']
|
|
2779
|
+
},
|
|
2780
|
+
{
|
|
2781
|
+
name: 'quick-skips-diagnosis', file: 'SKILL.md',
|
|
2782
|
+
from: 'Quick mode only reduces depth', to: 'Quick mode may shorten scope',
|
|
2783
|
+
expected: ['adaptive-depth']
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
name: 'handoff-drops-skip-forms', file: 'SKILL.md',
|
|
2787
|
+
from: '`Timeline: skipped - <reason>` or `- skipped: <reason>`', to: '`Timeline: omitted`',
|
|
2788
|
+
expected: ['debug-handoff']
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
name: 'warnings-auto-accept', file: 'references/review-cycle.md',
|
|
2792
|
+
from: 'Only `FAIL` and `PASS_WITH_WARNINGS` enter remediation retry',
|
|
2793
|
+
to: 'Only `FAIL` enters remediation retry; `PASS_WITH_WARNINGS` auto-approves',
|
|
2794
|
+
expected: ['verdict-surface']
|
|
2795
|
+
},
|
|
2796
|
+
{
|
|
2797
|
+
name: 'warnings-user-approve', file: 'references/review-cycle.md',
|
|
2798
|
+
from: 'only a fresh literal `PASS` enters finalization', to: '"Approve anyway" enters finalization',
|
|
2799
|
+
expected: ['verdict-surface']
|
|
2800
|
+
},
|
|
2801
|
+
{
|
|
2802
|
+
name: 'pass-redefined-locally', file: 'references/review-cycle.md',
|
|
2803
|
+
from: 'The definition of `PASS` defers to `cf-code-review`; Fix never redefines it with local severity thresholds.',
|
|
2804
|
+
to: 'The definition of `PASS` is local: no Critical, no High, at most one Medium.',
|
|
2805
|
+
expected: ['verdict-surface']
|
|
2806
|
+
},
|
|
2807
|
+
{
|
|
2808
|
+
name: 'confidence-score-returns', file: 'SKILL.md',
|
|
2809
|
+
from: '**Report:** root cause, changes made',
|
|
2810
|
+
to: '**Report:** Confidence score, root cause, changes made',
|
|
2811
|
+
expected: ['verdict-surface']
|
|
2812
|
+
},
|
|
2813
|
+
{
|
|
2814
|
+
name: 'delegation-loses-user-clause', file: 'SKILL.md',
|
|
2815
|
+
from: 'The user explicitly requested or permitted delegation or parallel agents.',
|
|
2816
|
+
to: 'Delegation is at the agent\'s discretion.',
|
|
2817
|
+
expected: ['delegation-gate']
|
|
2818
|
+
},
|
|
2819
|
+
{
|
|
2820
|
+
name: 'sweep-permits-silent-patch', file: 'SKILL.md',
|
|
2821
|
+
from: 'Do not silently patch around the regression.', to: 'Patch around regressions quietly.',
|
|
2822
|
+
expected: ['side-effect-gate']
|
|
2823
|
+
},
|
|
2824
|
+
{
|
|
2825
|
+
name: 'quick-gains-mandatory-frame', file: 'SKILL.md',
|
|
2826
|
+
from: 'Quick/local does not add a separate framing ceremony', to: 'Quick/local always requires a separate framing ceremony',
|
|
2827
|
+
expected: ['bounded-repair-frame']
|
|
2828
|
+
},
|
|
2829
|
+
{
|
|
2830
|
+
name: 'deep-researches-before-diagnosis', file: 'SKILL.md',
|
|
2831
|
+
from: 'after diagnosis, research only unresolved external facts', to: 'research broadly before diagnosis',
|
|
2832
|
+
expected: ['deep-decision-route']
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
name: 'overlays-load-everything', file: 'references/workflow-specialized.md',
|
|
2836
|
+
from: 'Load only the matching section', to: 'Load every section',
|
|
2837
|
+
expected: ['specialized-proof-overlays']
|
|
2838
|
+
},
|
|
2839
|
+
{
|
|
2840
|
+
name: 'diagnosis-starts-before-scout', file: 'references/parallel-patterns.md',
|
|
2841
|
+
from: 'Diagnosis still starts only', to: 'Diagnosis may start',
|
|
2842
|
+
expected: ['scout-before-diagnosis']
|
|
2843
|
+
},
|
|
2844
|
+
{
|
|
2845
|
+
name: 'research-starts-before-diagnosis', file: 'references/parallel-patterns.md',
|
|
2846
|
+
from: 'Research begins only after Step 2 diagnosis', to: 'Research may begin before Step 2 diagnosis',
|
|
2847
|
+
expected: ['scout-before-diagnosis']
|
|
2848
|
+
}
|
|
2849
|
+
];
|
|
2850
|
+
for (const mutation of mutations) {
|
|
2851
|
+
const target = path.join(installedRoot, mutation.file);
|
|
2852
|
+
const original = fs.readFileSync(target);
|
|
2853
|
+
const content = original.toString('utf8');
|
|
2854
|
+
const anchor = content.indexOf(mutation.from);
|
|
2855
|
+
assert.ok(anchor >= 0, `${mutation.name} mutation anchor must exist`);
|
|
2856
|
+
assert.equal(
|
|
2857
|
+
content.indexOf(mutation.from, anchor + mutation.from.length),
|
|
2858
|
+
-1,
|
|
2859
|
+
`${mutation.name} mutation anchor must be unique`
|
|
2860
|
+
);
|
|
2861
|
+
const weakened = `${content.slice(0, anchor)}${mutation.to}${content.slice(anchor + mutation.from.length)}`;
|
|
2862
|
+
try {
|
|
2863
|
+
fs.writeFileSync(target, weakened);
|
|
2864
|
+
assert.deepEqual(hotfixProjectionIssues(readProjection()), mutation.expected, mutation.name);
|
|
2865
|
+
} finally {
|
|
2866
|
+
fs.writeFileSync(target, original);
|
|
2867
|
+
}
|
|
2868
|
+
assert.deepEqual(fs.readFileSync(target), original, `${mutation.name} byte restore`);
|
|
2869
|
+
assert.deepEqual(hotfixProjectionIssues(readProjection()), []);
|
|
2870
|
+
}
|
|
2871
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
2872
|
+
assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
|
|
2873
|
+
}
|
|
2874
|
+
});
|
|
2875
|
+
});
|
|
2876
|
+
|
|
2877
|
+
function docsProjectionIssues(files) {
|
|
2878
|
+
const compact = (value) => String(value).replace(/\s+/g, ' ').trim();
|
|
2879
|
+
const skill = compact(files.skill);
|
|
2880
|
+
const init = compact(files.init);
|
|
2881
|
+
const update = compact(files.update);
|
|
2882
|
+
const standard = compact(files.standard);
|
|
2883
|
+
const issues = new Set();
|
|
2884
|
+
if (!skill.includes('The user explicitly requested or permitted delegation or parallel agents.')
|
|
2885
|
+
|| !skill.includes('The active runtime exposes an Explore/delegation capability.')
|
|
2886
|
+
|| !skill.includes('at least two distinct, non-overlapping scopes')
|
|
2887
|
+
|| !skill.includes('Otherwise continue sequentially in the main agent')
|
|
2888
|
+
|| !skill.includes('only through the Delegation Gate above')
|
|
2889
|
+
|| !init.includes('only through the Delegation Gate in `../SKILL.md`')
|
|
2890
|
+
|| !update.includes('only through the Delegation Gate in `../SKILL.md`')
|
|
2891
|
+
|| skill.includes('when delegation is available')
|
|
2892
|
+
|| init.includes('when delegation is available')
|
|
2893
|
+
|| update.includes('when delegation is available')) {
|
|
2894
|
+
issues.add('delegation-gate');
|
|
2895
|
+
}
|
|
2896
|
+
if (!skill.includes('## Post-Task Docs Checkpoint')
|
|
2897
|
+
|| !skill.includes('`none`: report that no docs change is needed and edit nothing.')
|
|
2898
|
+
|| !skill.includes('A checkpoint never invents a new document')
|
|
2899
|
+
|| !skill.includes('never auto-selects `init` and overrides')
|
|
2900
|
+
|| !skill.includes('report the gap and recommend an explicit')
|
|
2901
|
+
|| !standard.includes('checkpoint contract in `../SKILL.md`')
|
|
2902
|
+
|| skill.includes('checkpoint may create')) {
|
|
2903
|
+
issues.add('docs-checkpoint');
|
|
2904
|
+
}
|
|
2905
|
+
if (!skill.includes('Type: Observed | Inferred | Unknown')
|
|
2906
|
+
|| !skill.includes('Confidence: High | Medium | Low')
|
|
2907
|
+
|| skill.includes('evidence is optional')) {
|
|
2908
|
+
issues.add('evidence-taxonomy');
|
|
2909
|
+
}
|
|
2910
|
+
if (!skill.includes('## Reconstruction Is Not Specs')
|
|
2911
|
+
|| skill.includes('are advisory')) {
|
|
2912
|
+
issues.add('reconstruction-boundary');
|
|
2913
|
+
}
|
|
2914
|
+
return [...issues].sort();
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
test('Codex installed Docs preserves the adaptive contract', () => {
|
|
2918
|
+
inTempProject((root) => {
|
|
2919
|
+
const sourceBytes = new Map(DOCS_ADAPTIVE_BUNDLE.map((relative) => [
|
|
2920
|
+
path.join(DOCS_ADAPTIVE_SOURCE_ROOT, relative),
|
|
2921
|
+
fs.readFileSync(path.join(DOCS_ADAPTIVE_SOURCE_ROOT, relative))
|
|
2922
|
+
]));
|
|
2923
|
+
const result = install(root, ['--with-document-skills']);
|
|
2924
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
2925
|
+
|
|
2926
|
+
const installedRoot = path.join(root, '.agents/skills/docs');
|
|
2927
|
+
for (const relative of DOCS_ADAPTIVE_BUNDLE) {
|
|
2928
|
+
const sourcePath = path.join(DOCS_ADAPTIVE_SOURCE_ROOT, relative);
|
|
2929
|
+
assert.equal(
|
|
2930
|
+
fs.readFileSync(path.join(installedRoot, relative), 'utf8'),
|
|
2931
|
+
normalizeCodexBody(fs.readFileSync(sourcePath, 'utf8'), sourcePath),
|
|
2932
|
+
`Codex Docs projection drifted: ${relative}`
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2935
|
+
const readProjection = () => ({
|
|
2936
|
+
skill: fs.readFileSync(path.join(installedRoot, 'SKILL.md'), 'utf8'),
|
|
2937
|
+
init: fs.readFileSync(path.join(installedRoot, 'references/init-workflow.md'), 'utf8'),
|
|
2938
|
+
update: fs.readFileSync(path.join(installedRoot, 'references/update-workflow.md'), 'utf8'),
|
|
2939
|
+
standard: fs.readFileSync(path.join(installedRoot, 'references/standard-docs-workflow.md'), 'utf8'),
|
|
2940
|
+
});
|
|
2941
|
+
assert.match(readProjection().skill, /name: cf-docs/);
|
|
2942
|
+
assert.deepEqual(docsProjectionIssues(readProjection()), []);
|
|
2943
|
+
|
|
2944
|
+
const mutations = [
|
|
2945
|
+
{
|
|
2946
|
+
name: 'gate-loses-user-clause', file: 'SKILL.md',
|
|
2947
|
+
from: 'The user explicitly requested or permitted delegation or parallel agents.',
|
|
2948
|
+
to: 'Delegation is at the agent\'s discretion.',
|
|
2949
|
+
expected: ['delegation-gate']
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
name: 'keeper-ungated-returns', file: 'SKILL.md',
|
|
2953
|
+
from: 'only through the Delegation Gate above', to: 'when delegation is available',
|
|
2954
|
+
expected: ['delegation-gate']
|
|
2955
|
+
},
|
|
2956
|
+
{
|
|
2957
|
+
name: 'checkpoint-invents-doc', file: 'SKILL.md',
|
|
2958
|
+
from: 'A checkpoint never invents a new document', to: 'A checkpoint may create missing documents',
|
|
2959
|
+
expected: ['docs-checkpoint']
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
name: 'taxonomy-drops-inferred', file: 'SKILL.md',
|
|
2963
|
+
from: 'Type: Observed | Inferred | Unknown', to: 'Type: Observed | Unknown',
|
|
2964
|
+
expected: ['evidence-taxonomy']
|
|
2965
|
+
},
|
|
2966
|
+
{
|
|
2967
|
+
name: 'reconstruction-becomes-advisory', file: 'SKILL.md',
|
|
2968
|
+
from: '## Reconstruction Is Not Specs',
|
|
2969
|
+
to: '## Reconstruction Is Not Specs\n\nThe prohibitions below are advisory.',
|
|
2970
|
+
expected: ['reconstruction-boundary']
|
|
2971
|
+
}
|
|
2972
|
+
];
|
|
2973
|
+
for (const mutation of mutations) {
|
|
2974
|
+
const target = path.join(installedRoot, mutation.file);
|
|
2975
|
+
const original = fs.readFileSync(target);
|
|
2976
|
+
const content = original.toString('utf8');
|
|
2977
|
+
const anchor = content.indexOf(mutation.from);
|
|
2978
|
+
assert.ok(anchor >= 0, `${mutation.name} mutation anchor must exist`);
|
|
2979
|
+
assert.equal(
|
|
2980
|
+
content.indexOf(mutation.from, anchor + mutation.from.length),
|
|
2981
|
+
-1,
|
|
2982
|
+
`${mutation.name} mutation anchor must be unique`
|
|
2983
|
+
);
|
|
2984
|
+
const weakened = `${content.slice(0, anchor)}${mutation.to}${content.slice(anchor + mutation.from.length)}`;
|
|
2985
|
+
try {
|
|
2986
|
+
fs.writeFileSync(target, weakened);
|
|
2987
|
+
assert.deepEqual(docsProjectionIssues(readProjection()), mutation.expected, mutation.name);
|
|
2988
|
+
} finally {
|
|
2989
|
+
fs.writeFileSync(target, original);
|
|
2990
|
+
}
|
|
2991
|
+
assert.deepEqual(fs.readFileSync(target), original, `${mutation.name} byte restore`);
|
|
2992
|
+
assert.deepEqual(docsProjectionIssues(readProjection()), []);
|
|
2993
|
+
}
|
|
2994
|
+
for (const [sourcePath, expected] of sourceBytes) {
|
|
2995
|
+
assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
});
|
|
2999
|
+
|
|
1319
3000
|
test('Codex install on top of existing Claude installation preserves content and adds Codex', () => {
|
|
1320
3001
|
inTempProject((root) => {
|
|
1321
3002
|
// Setup existing Claude installation with content
|
|
@@ -1404,3 +3085,134 @@ test('Codex scaffold resolver rejects symlink template', () => {
|
|
|
1404
3085
|
fs.writeFileSync(template, original);
|
|
1405
3086
|
});
|
|
1406
3087
|
});
|
|
3088
|
+
|
|
3089
|
+
test('Claude and Codex installed Route preserve proportional live-catalog semantics', () => {
|
|
3090
|
+
inTempProject((root) => {
|
|
3091
|
+
const result = installPlatforms(root, ['claude', 'codex']);
|
|
3092
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
3093
|
+
const sourceRoot = path.join(PACKAGE_ROOT, 'src/claude/skills/route');
|
|
3094
|
+
const expectedFiles = [
|
|
3095
|
+
'SKILL.md', 'references/task-taxonomy.md',
|
|
3096
|
+
'references/chaining-patterns.md', 'references/agent-timing.md'
|
|
3097
|
+
];
|
|
3098
|
+
const expectedRules = ['skill-workflow-routing.md', 'skill-domain-routing.md'];
|
|
3099
|
+
for (const runtime of ['claude', 'codex']) {
|
|
3100
|
+
const installedRoot = path.join(root, runtime === 'codex' ? '.agents/skills/route' : '.claude/skills/route');
|
|
3101
|
+
for (const relative of expectedFiles) {
|
|
3102
|
+
const source = fs.readFileSync(path.join(sourceRoot, relative), 'utf8');
|
|
3103
|
+
const installed = fs.readFileSync(path.join(installedRoot, relative), 'utf8');
|
|
3104
|
+
const expected = runtime === 'codex'
|
|
3105
|
+
? normalizeCodexBody(source, path.join(sourceRoot, relative))
|
|
3106
|
+
: source;
|
|
3107
|
+
assert.equal(installed, expected, `${runtime}:${relative}`);
|
|
3108
|
+
}
|
|
3109
|
+
for (const relative of expectedRules) {
|
|
3110
|
+
const sourcePath = path.join(PACKAGE_ROOT, 'src/claude/rules', relative);
|
|
3111
|
+
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
3112
|
+
const installed = fs.readFileSync(path.join(
|
|
3113
|
+
root, runtime === 'codex' ? '.codex/rules' : '.claude/rules', relative
|
|
3114
|
+
), 'utf8');
|
|
3115
|
+
const expected = runtime === 'codex' ? normalizeCodexBody(source, sourcePath) : source;
|
|
3116
|
+
assert.equal(installed, expected, `${runtime}:rules/${relative}`);
|
|
3117
|
+
}
|
|
3118
|
+
const route = fs.readFileSync(path.join(installedRoot, 'SKILL.md'), 'utf8');
|
|
3119
|
+
assert.match(route, /names a valid installed skill[\s\S]*one installed skill clearly covers[\s\S]*Direct factual conversation/);
|
|
3120
|
+
assert.match(route, /highest-link[\s\S]*risk[\s\S]*number of material domains/);
|
|
3121
|
+
assert.match(route, /never expand it/);
|
|
3122
|
+
const catalog = installedCatalog(root, runtime);
|
|
3123
|
+
assert.ok(catalog.skills.some((skill) => skill.public_id === 'cf:route'));
|
|
3124
|
+
assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:docs'), false);
|
|
3125
|
+
}
|
|
3126
|
+
});
|
|
3127
|
+
});
|
|
3128
|
+
|
|
3129
|
+
test('combined installs bind each catalog to its native runtime inventory', () => {
|
|
3130
|
+
inTempProject((root) => {
|
|
3131
|
+
const result = installPlatforms(root, ['claude', 'codex']);
|
|
3132
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
3133
|
+
fs.cpSync(
|
|
3134
|
+
path.join(PACKAGE_ROOT, 'src/claude/skills/docs'),
|
|
3135
|
+
path.join(root, '.claude/skills/docs'),
|
|
3136
|
+
{ recursive: true }
|
|
3137
|
+
);
|
|
3138
|
+
const claude = installedCatalog(root, 'claude');
|
|
3139
|
+
const codex = installedCatalog(root, 'codex');
|
|
3140
|
+
assert.equal(claude.root, fs.realpathSync(path.join(root, '.claude/skills')));
|
|
3141
|
+
assert.equal(codex.root, fs.realpathSync(path.join(root, '.agents/skills')));
|
|
3142
|
+
assert.equal(claude.skills.some((skill) => skill.public_id === 'cf:docs'), true);
|
|
3143
|
+
assert.equal(codex.skills.some((skill) => skill.public_id === 'cf:docs'), false);
|
|
3144
|
+
});
|
|
3145
|
+
});
|
|
3146
|
+
|
|
3147
|
+
test('Claude and Codex installed rules preserve the ported review and process guidance', () => {
|
|
3148
|
+
inTempProject((root) => {
|
|
3149
|
+
const result = installPlatforms(root, ['claude', 'codex']);
|
|
3150
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
3151
|
+
const ported = ['review-audit-self-decision.md', 'process-management.md'];
|
|
3152
|
+
const renameOnly = (content) => content
|
|
3153
|
+
.replace(/\/cf:([a-z0-9-]+)/gi, (_match, name) => `$cf-${name}`)
|
|
3154
|
+
.replace(/\bcf:([a-z0-9-]+)/gi, (_match, name) => `cf-${name}`);
|
|
3155
|
+
for (const relative of ported) {
|
|
3156
|
+
const sourcePath = path.join(PACKAGE_ROOT, 'src/claude/rules', relative);
|
|
3157
|
+
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
3158
|
+
assert.equal(
|
|
3159
|
+
fs.existsSync(path.join(PACKAGE_ROOT, 'src/codex/rules', relative)),
|
|
3160
|
+
false,
|
|
3161
|
+
`${relative} must not be shadowed by a Codex native override`
|
|
3162
|
+
);
|
|
3163
|
+
assert.equal(
|
|
3164
|
+
fs.readFileSync(path.join(root, '.claude/rules', relative), 'utf8'),
|
|
3165
|
+
source,
|
|
3166
|
+
`claude:rules/${relative}`
|
|
3167
|
+
);
|
|
3168
|
+
const installedCodex = fs.readFileSync(path.join(root, '.codex/rules', relative), 'utf8');
|
|
3169
|
+
assert.equal(installedCodex, normalizeCodexBody(source, sourcePath), `codex:rules/${relative}`);
|
|
3170
|
+
assert.equal(
|
|
3171
|
+
installedCodex,
|
|
3172
|
+
renameOnly(source),
|
|
3173
|
+
`codex:rules/${relative} must differ from source only by skill-name renames`
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
const agentsMd = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8');
|
|
3177
|
+
const pointerLines = agentsMd
|
|
3178
|
+
.split('\n')
|
|
3179
|
+
.filter((line) => ported.some((relative) => line.includes(relative)));
|
|
3180
|
+
assert.equal(pointerLines.length, ported.length, 'AGENTS.md must point at both ported rules');
|
|
3181
|
+
for (const relative of ported) {
|
|
3182
|
+
assert.ok(
|
|
3183
|
+
pointerLines.some((line) => line.includes(`.codex/rules/${relative}`)),
|
|
3184
|
+
`AGENTS.md must name .codex/rules/${relative}`
|
|
3185
|
+
);
|
|
3186
|
+
}
|
|
3187
|
+
const pointerText = pointerLines.join('\n');
|
|
3188
|
+
for (const forbidden of ['cf:', 'Claude Code', 'Claude Tasks', 'SendMessage', '.claude/']) {
|
|
3189
|
+
assert.equal(
|
|
3190
|
+
pointerText.includes(forbidden),
|
|
3191
|
+
false,
|
|
3192
|
+
`AGENTS.md pointer must stay Codex-native: ${forbidden}`
|
|
3193
|
+
);
|
|
3194
|
+
}
|
|
3195
|
+
});
|
|
3196
|
+
});
|
|
3197
|
+
|
|
3198
|
+
test('installed Route degrades safely when an agent is absent', () => {
|
|
3199
|
+
inTempProject((root) => {
|
|
3200
|
+
const result = installPlatforms(root, ['claude', 'codex']);
|
|
3201
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
3202
|
+
const cases = [
|
|
3203
|
+
['claude', '.claude/agents/researcher.md', '.claude/skills/route'],
|
|
3204
|
+
['codex', '.codex/agents/researcher.toml', '.agents/skills/route'],
|
|
3205
|
+
];
|
|
3206
|
+
for (const [runtime, agentRelative, routeRelative] of cases) {
|
|
3207
|
+
fs.rmSync(path.join(root, agentRelative), { force: true });
|
|
3208
|
+
assert.equal(fs.existsSync(path.join(root, agentRelative)), false);
|
|
3209
|
+
const timing = fs.readFileSync(path.join(root, routeRelative, 'references/agent-timing.md'), 'utf8');
|
|
3210
|
+
const route = fs.readFileSync(path.join(root, routeRelative, 'SKILL.md'), 'utf8');
|
|
3211
|
+
const normalizedRoute = route.replace(/\s+/g, ' ');
|
|
3212
|
+
assert.match(timing, /If the preferred agent is absent[\s\S]*never synthesize a role/);
|
|
3213
|
+
assert.match(normalizedRoute, /continue inline when safe or return the named gap/);
|
|
3214
|
+
assert.match(normalizedRoute, /Never synthesize an unavailable agent/);
|
|
3215
|
+
assert.doesNotMatch(`${timing}\n${route}`, /researcher(?:\.md|\.toml)/i, runtime);
|
|
3216
|
+
}
|
|
3217
|
+
});
|
|
3218
|
+
});
|