@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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase helper: Codex hooks.json merge.
|
|
3
|
+
*
|
|
4
|
+
* `.codex/hooks.json` is a shared configuration file, not a CafeKit payload. It was
|
|
5
|
+
* previously copied verbatim, which made a single user-added hook turn the whole file
|
|
6
|
+
* into a user-modified artifact: a normal install then preserved CafeKit's stale hooks,
|
|
7
|
+
* and `--force-overwrite` destroyed the user's. Merging per command removes that choice.
|
|
8
|
+
*
|
|
9
|
+
* This mirrors how `.claude/settings.json` is handled, so hooks.json likewise stays
|
|
10
|
+
* outside the ownership model: CafeKit owns its own entries, the user owns theirs, and
|
|
11
|
+
* neither erases the other. Honors dry-run.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { PLATFORMS } = require('../lib/context');
|
|
17
|
+
|
|
18
|
+
const CODEX_SRC = path.join(__dirname, '../../src/codex');
|
|
19
|
+
const WINDOWS_HOOK_COMMAND = /^node "\.codex\/hooks\/([a-z0-9-]+\.cjs)"$/;
|
|
20
|
+
|
|
21
|
+
/** The hook script a command runs, which is stable across installs and platforms. */
|
|
22
|
+
function hookScript(command) {
|
|
23
|
+
if (typeof command !== 'string') return null;
|
|
24
|
+
const match = command.match(/[/\\]hooks[/\\]([a-z0-9-]+\.cjs)/i);
|
|
25
|
+
return match ? match[1] : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Rewrite a managed `commandWindows` into a path-encoded invocation. Absolute Windows
|
|
30
|
+
* paths cannot be quoted safely inside the Codex command string, so the path travels
|
|
31
|
+
* base64url and is decoded by the launcher.
|
|
32
|
+
*/
|
|
33
|
+
function materializeWindowsCommand(handler, projectRoot) {
|
|
34
|
+
if (typeof handler.commandWindows !== 'string') return handler;
|
|
35
|
+
const match = handler.commandWindows.match(WINDOWS_HOOK_COMMAND);
|
|
36
|
+
if (!match) throw new Error(`Unsupported Codex Windows hook command: ${handler.commandWindows}`);
|
|
37
|
+
const hookPath = path.join(fs.realpathSync(projectRoot), '.codex', 'hooks', match[1]);
|
|
38
|
+
const encoded = Buffer.from(hookPath, 'utf8').toString('base64url');
|
|
39
|
+
return {
|
|
40
|
+
...handler,
|
|
41
|
+
commandWindows: 'node -e "process.argv[1]=Buffer.from(process.argv[1],\'base64url\').toString(\'utf8\');require(\'module\').runMain()" ' + encoded,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Drop CafeKit hooks the manifest has retired, leaving foreign entries untouched. */
|
|
46
|
+
function pruneObsoleteCodexHooks(config, ctx) {
|
|
47
|
+
const obsolete = ctx.manifest?.obsolete?.settingsHookCommandSubstrings || [];
|
|
48
|
+
if (!config.hooks || obsolete.length === 0) return { config, removed: 0 };
|
|
49
|
+
|
|
50
|
+
let removed = 0;
|
|
51
|
+
const pruned = {};
|
|
52
|
+
for (const [eventName, groups] of Object.entries(config.hooks)) {
|
|
53
|
+
if (!Array.isArray(groups)) { pruned[eventName] = groups; continue; }
|
|
54
|
+
const keptGroups = [];
|
|
55
|
+
for (const group of groups) {
|
|
56
|
+
if (!Array.isArray(group?.hooks)) { keptGroups.push(group); continue; }
|
|
57
|
+
const keptHooks = group.hooks.filter((handler) => {
|
|
58
|
+
const command = handler?.command || '';
|
|
59
|
+
const isObsolete = obsolete.some((substring) => command.includes(substring));
|
|
60
|
+
if (isObsolete) removed += 1;
|
|
61
|
+
return !isObsolete;
|
|
62
|
+
});
|
|
63
|
+
if (keptHooks.length > 0) keptGroups.push({ ...group, hooks: keptHooks });
|
|
64
|
+
}
|
|
65
|
+
if (keptGroups.length > 0) pruned[eventName] = keptGroups;
|
|
66
|
+
}
|
|
67
|
+
return { config: removed > 0 ? { ...config, hooks: pruned } : config, removed };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Merge CafeKit-managed Codex hooks into the project's hooks.json, preserving every
|
|
72
|
+
* entry CafeKit did not author. Codex only.
|
|
73
|
+
*/
|
|
74
|
+
function mergeCodexHooks(ctx, platformKey, projectRoot = process.cwd()) {
|
|
75
|
+
if (platformKey !== 'codex') return;
|
|
76
|
+
|
|
77
|
+
const templatePath = path.join(CODEX_SRC, 'hooks.json');
|
|
78
|
+
if (!fs.existsSync(templatePath)) {
|
|
79
|
+
ctx.ui.warn('Codex hooks template not found: src/codex/hooks.json');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const managed = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
|
83
|
+
const targetPath = path.join(PLATFORMS.codex.folder, 'hooks.json');
|
|
84
|
+
|
|
85
|
+
let existing = {};
|
|
86
|
+
if (fs.existsSync(targetPath)) {
|
|
87
|
+
// A malformed hooks.json must not abort the install and must never be
|
|
88
|
+
// overwritten blind: skip the merge and say why.
|
|
89
|
+
try {
|
|
90
|
+
existing = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
ctx.ui.warn(`Codex hooks: ${targetPath} is not valid JSON (${error.message}). `
|
|
93
|
+
+ 'Skipping the hooks merge — fix the file and re-run the installer.');
|
|
94
|
+
ctx.results.errors++;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { config: base, removed } = pruneObsoleteCodexHooks({ ...existing }, ctx);
|
|
100
|
+
if (removed > 0) {
|
|
101
|
+
ctx.ui.detail(` ↻ ${ctx.dryRun ? '[dry-run] ' : ''}Codex hooks: removed ${removed} obsolete hook(s)`);
|
|
102
|
+
ctx.results.updated++;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const merged = { ...base };
|
|
106
|
+
if (typeof managed.description === 'string' && typeof merged.description !== 'string') {
|
|
107
|
+
merged.description = managed.description;
|
|
108
|
+
}
|
|
109
|
+
merged.hooks = { ...(base.hooks || {}) };
|
|
110
|
+
|
|
111
|
+
let added = 0;
|
|
112
|
+
for (const [eventName, managedGroups] of Object.entries(managed.hooks || {})) {
|
|
113
|
+
const groups = Array.isArray(merged.hooks[eventName]) ? [...merged.hooks[eventName]] : [];
|
|
114
|
+
// A script already registered anywhere under this event stays where the user put
|
|
115
|
+
// it. Matching on the script rather than the whole command survives the path
|
|
116
|
+
// differences between installs and platforms.
|
|
117
|
+
const registered = new Set(
|
|
118
|
+
groups.flatMap((group) => (group?.hooks || []).map((handler) => hookScript(handler?.command))).filter(Boolean),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
for (const managedGroup of managedGroups) {
|
|
122
|
+
const wanted = (managedGroup.hooks || [])
|
|
123
|
+
.filter((handler) => {
|
|
124
|
+
const script = hookScript(handler?.command);
|
|
125
|
+
return script && !registered.has(script);
|
|
126
|
+
})
|
|
127
|
+
.map((handler) => materializeWindowsCommand(handler, projectRoot));
|
|
128
|
+
if (wanted.length === 0) continue;
|
|
129
|
+
|
|
130
|
+
const matcher = managedGroup.matcher || '';
|
|
131
|
+
const target = groups.find((group) => (group?.matcher || '') === matcher && Array.isArray(group?.hooks));
|
|
132
|
+
if (target) {
|
|
133
|
+
target.hooks = [...target.hooks, ...wanted];
|
|
134
|
+
} else {
|
|
135
|
+
groups.push({ ...managedGroup, hooks: wanted });
|
|
136
|
+
}
|
|
137
|
+
for (const handler of wanted) registered.add(hookScript(handler.command));
|
|
138
|
+
added += wanted.length;
|
|
139
|
+
ctx.ui.detail(` ✓ ${ctx.dryRun ? '[dry-run] ' : ''}Codex hooks: ${eventName} merged (+${wanted.length})`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
merged.hooks[eventName] = groups;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (added === 0 && removed === 0) {
|
|
146
|
+
ctx.ui.detail(` → ${ctx.dryRun ? '[dry-run] ' : ''}Codex hooks: already registered`);
|
|
147
|
+
ctx.results.unchanged++;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!ctx.dryRun) {
|
|
151
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
152
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { mergeCodexHooks, pruneObsoleteCodexHooks, hookScript };
|
|
@@ -5,57 +5,32 @@ const path = require('path');
|
|
|
5
5
|
const { PLATFORMS } = require('../lib/context');
|
|
6
6
|
const { writeManagedFile, copyManagedTree } = require('../lib/managed-writer');
|
|
7
7
|
const { report, treeAction } = require('./report');
|
|
8
|
-
const { copyRulesDirectory } = require('./claude-runtime');
|
|
8
|
+
const { copyRulesDirectory, copyOutputStylesDirectory } = require('./claude-runtime');
|
|
9
9
|
const {
|
|
10
10
|
managedRange,
|
|
11
11
|
upsertManagedCodexBlock
|
|
12
12
|
} = require('../lib/codex-install');
|
|
13
|
+
const { mergeCodexHooks } = require('./codex-hooks');
|
|
13
14
|
|
|
14
15
|
const SRC = path.join(__dirname, '../../src');
|
|
15
16
|
const CODEX_SRC = path.join(SRC, 'codex');
|
|
16
17
|
|
|
18
|
+
// hooks.json is merged by `codex-hooks.js`, not copied: it is a shared config file
|
|
19
|
+
// the user also writes to, so a verbatim copy would either preserve CafeKit's stale
|
|
20
|
+
// hooks or destroy the user's.
|
|
17
21
|
const CODEX_OWN_RUNTIME = [
|
|
18
22
|
['gitignore', '.gitignore'],
|
|
19
|
-
['runtime.json', 'runtime.json']
|
|
20
|
-
['hooks.json', 'hooks.json']
|
|
23
|
+
['runtime.json', 'runtime.json']
|
|
21
24
|
];
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function materializeWindowsHookCommands(content, projectRoot = process.cwd()) {
|
|
26
|
-
const config = JSON.parse(content);
|
|
27
|
-
const canonicalRoot = fs.realpathSync(projectRoot);
|
|
28
|
-
|
|
29
|
-
for (const groups of Object.values(config.hooks || {})) {
|
|
30
|
-
for (const group of groups) {
|
|
31
|
-
for (const handler of group.hooks || []) {
|
|
32
|
-
if (typeof handler.commandWindows !== 'string') continue;
|
|
33
|
-
const match = handler.commandWindows.match(WINDOWS_HOOK_COMMAND);
|
|
34
|
-
if (!match) {
|
|
35
|
-
throw new Error(`Unsupported Codex Windows hook command: ${handler.commandWindows}`);
|
|
36
|
-
}
|
|
37
|
-
const hookPath = path.join(canonicalRoot, '.codex', 'hooks', match[1]);
|
|
38
|
-
const encodedPath = Buffer.from(hookPath, 'utf8').toString('base64url');
|
|
39
|
-
handler.commandWindows = (
|
|
40
|
-
'node -e "process.argv[1]=Buffer.from(process.argv[1],\'base64url\').toString(\'utf8\');require(\'module\').runMain()" ' +
|
|
41
|
-
encodedPath
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return `${JSON.stringify(config, null, 2)}\n`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function writeSourceFile(ctx, platformKey, src, dest, label, transform) {
|
|
26
|
+
function writeSourceFile(ctx, platformKey, src, dest, label) {
|
|
51
27
|
const platform = PLATFORMS[platformKey];
|
|
52
28
|
const { action } = writeManagedFile({
|
|
53
29
|
src,
|
|
54
30
|
dest,
|
|
55
31
|
platformFolder: platform.folder,
|
|
56
32
|
ctx,
|
|
57
|
-
tracker: ctx.trackers[platformKey]
|
|
58
|
-
transform
|
|
33
|
+
tracker: ctx.trackers[platformKey]
|
|
59
34
|
});
|
|
60
35
|
report(ctx, action, label);
|
|
61
36
|
}
|
|
@@ -68,10 +43,7 @@ function installRuntimeFiles(ctx, platformKey) {
|
|
|
68
43
|
platformKey,
|
|
69
44
|
path.join(CODEX_SRC, sourceRel),
|
|
70
45
|
path.join(platform.folder, targetRel),
|
|
71
|
-
`Codex runtime: ${targetRel}
|
|
72
|
-
sourceRel === 'hooks.json'
|
|
73
|
-
? (content) => materializeWindowsHookCommands(content, process.cwd())
|
|
74
|
-
: undefined
|
|
46
|
+
`Codex runtime: ${targetRel}`
|
|
75
47
|
);
|
|
76
48
|
}
|
|
77
49
|
writeSourceFile(
|
|
@@ -82,6 +54,8 @@ function installRuntimeFiles(ctx, platformKey) {
|
|
|
82
54
|
'Codex skills: .agents/.gitignore'
|
|
83
55
|
);
|
|
84
56
|
|
|
57
|
+
mergeCodexHooks(ctx, platformKey);
|
|
58
|
+
|
|
85
59
|
const agg = copyManagedTree({
|
|
86
60
|
src: path.join(CODEX_SRC, 'hooks'),
|
|
87
61
|
dest: path.join(platform.folder, 'hooks'),
|
|
@@ -92,6 +66,15 @@ function installRuntimeFiles(ctx, platformKey) {
|
|
|
92
66
|
report(ctx, treeAction(agg), 'Codex native hooks');
|
|
93
67
|
// Keep one canonical command analyzer in the Claude source tree, then
|
|
94
68
|
// materialize the same bytes into the installed Codex runtime.
|
|
69
|
+
// One canonical runtime schema lives in the Claude source tree; both installed
|
|
70
|
+
// runtimes reference it as ./runtime.schema.json beside their runtime.json.
|
|
71
|
+
writeSourceFile(
|
|
72
|
+
ctx,
|
|
73
|
+
platformKey,
|
|
74
|
+
path.join(SRC, 'claude', 'runtime.schema.json'),
|
|
75
|
+
path.join(platform.folder, 'runtime.schema.json'),
|
|
76
|
+
'Codex runtime schema'
|
|
77
|
+
);
|
|
95
78
|
writeSourceFile(
|
|
96
79
|
ctx,
|
|
97
80
|
platformKey,
|
|
@@ -99,6 +82,13 @@ function installRuntimeFiles(ctx, platformKey) {
|
|
|
99
82
|
path.join(platform.folder, 'hooks', 'lib', 'privacy-command-analysis.cjs'),
|
|
100
83
|
'Codex privacy command analyzer'
|
|
101
84
|
);
|
|
85
|
+
writeSourceFile(
|
|
86
|
+
ctx,
|
|
87
|
+
platformKey,
|
|
88
|
+
path.join(SRC, 'claude', 'hooks', 'lib', 'secret-keywords.cjs'),
|
|
89
|
+
path.join(platform.folder, 'hooks', 'lib', 'secret-keywords.cjs'),
|
|
90
|
+
'Codex secret keyword matcher'
|
|
91
|
+
);
|
|
102
92
|
writeSourceFile(
|
|
103
93
|
ctx,
|
|
104
94
|
platformKey,
|
|
@@ -154,9 +144,10 @@ function installCodexRuntime(ctx, platformKey) {
|
|
|
154
144
|
if (platformKey !== 'codex') return ctx;
|
|
155
145
|
installRuntimeFiles(ctx, platformKey);
|
|
156
146
|
copyRulesDirectory(ctx, platformKey);
|
|
147
|
+
copyOutputStylesDirectory(ctx, platformKey);
|
|
157
148
|
installNativeRuleOverrides(ctx, platformKey);
|
|
158
149
|
installManagedAgentsMd(ctx);
|
|
159
150
|
return ctx;
|
|
160
151
|
}
|
|
161
152
|
|
|
162
|
-
module.exports = { installCodexRuntime
|
|
153
|
+
module.exports = { installCodexRuntime };
|
|
@@ -127,12 +127,18 @@ function copyPlatformFiles(ctx, platformKey) {
|
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
//
|
|
130
|
+
// Core skills plus the optional document bundle selected for this runtime.
|
|
131
131
|
let requiredSkills = [];
|
|
132
132
|
if (hasPlatformCapability(platformKey, 'skills')) {
|
|
133
133
|
requiredSkills = ctx.manifest?.skills?.required || [];
|
|
134
|
+
if (ctx.documentSkills?.[platformKey]?.enabled) {
|
|
135
|
+
requiredSkills = [
|
|
136
|
+
...requiredSkills,
|
|
137
|
+
...(ctx.manifest?.skills?.bundles?.documentSkills || [])
|
|
138
|
+
];
|
|
139
|
+
}
|
|
134
140
|
}
|
|
135
|
-
requiredSkills
|
|
141
|
+
[...new Set(requiredSkills)]
|
|
136
142
|
.filter((skillName) => skillName !== 'specs')
|
|
137
143
|
.forEach((skillName) => {
|
|
138
144
|
const skillSource = path.join(skillsSourceDir, skillName);
|
|
@@ -183,7 +189,14 @@ function copyPlatformFiles(ctx, platformKey) {
|
|
|
183
189
|
} else {
|
|
184
190
|
report(ctx, 'missing', `agents: ${platform.agentsDir}`);
|
|
185
191
|
}
|
|
192
|
+
}
|
|
186
193
|
|
|
194
|
+
// ── Scripts ─────────────────────────────────────────────
|
|
195
|
+
// Gated on its own capability. This block used to sit inside the agents guard,
|
|
196
|
+
// so a platform with agents: false and scripts: true (omp) never received
|
|
197
|
+
// .omp/scripts/, and every Stop-time gate that requires ../scripts/*.cjs answered
|
|
198
|
+
// "Completion gate unavailable" on each session_stop.
|
|
199
|
+
if (hasPlatformCapability(platformKey, 'scripts')) {
|
|
187
200
|
const scriptsSourceDir = path.join(SRC, 'claude/scripts');
|
|
188
201
|
if (fs.existsSync(scriptsSourceDir)) {
|
|
189
202
|
const scriptsDest = getRuntimeSupportTargetDir(platformKey, 'scripts');
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Phase: Oh My Pi runtime.
|
|
5
|
+
*
|
|
6
|
+
* omp discovers `.claude/skills` and `.agents/skills` on its own, so CafeKit copies no
|
|
7
|
+
* skill payload here. What omp does not have is CafeKit's enforcement chain, so this
|
|
8
|
+
* phase provisions what carries it:
|
|
9
|
+
*
|
|
10
|
+
* .omp/hooks/ the gate scripts: the Claude set, with omp's overlay on top
|
|
11
|
+
* .omp/extensions/ the bridge omp auto-loads, which dispatches those scripts
|
|
12
|
+
* .omp/runtime.json the hooks' configuration, beside the schema that describes it
|
|
13
|
+
* .omp/rules/ the rules the hooks inject
|
|
14
|
+
*
|
|
15
|
+
* The hooks are portable: they derive their runtime directory from their own location,
|
|
16
|
+
* so the Claude bytes run unchanged under `.omp/`. Only the files that genuinely differ
|
|
17
|
+
* for omp live in `src/omp/hooks/` and are written over the Claude set afterwards, so a
|
|
18
|
+
* change to a shared hook reaches omp without anyone remembering to re-fork it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { PLATFORMS } = require('../lib/context');
|
|
24
|
+
const { writeManagedFile, copyManagedTree } = require('../lib/managed-writer');
|
|
25
|
+
const { report, treeAction } = require('./report');
|
|
26
|
+
const { copyRulesDirectory } = require('./claude-runtime');
|
|
27
|
+
|
|
28
|
+
const SRC = path.join(__dirname, '../../src');
|
|
29
|
+
const CLAUDE_SRC = path.join(SRC, 'claude');
|
|
30
|
+
const OMP_SRC = path.join(SRC, 'omp');
|
|
31
|
+
|
|
32
|
+
/** Files under `src/omp/hooks/` that replace their Claude counterpart, relative to the hooks dir. */
|
|
33
|
+
function overlayFiles(dir = path.join(OMP_SRC, 'hooks'), prefix = '') {
|
|
34
|
+
if (!fs.existsSync(dir)) return [];
|
|
35
|
+
const found = [];
|
|
36
|
+
for (const name of fs.readdirSync(dir)) {
|
|
37
|
+
const rel = prefix ? `${prefix}/${name}` : name;
|
|
38
|
+
if (fs.statSync(path.join(dir, name)).isDirectory()) found.push(...overlayFiles(path.join(dir, name), rel));
|
|
39
|
+
else found.push(rel);
|
|
40
|
+
}
|
|
41
|
+
return found.sort();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The Claude hook set is the list the migration manifest ships to `.claude/`, not a
|
|
46
|
+
* directory walk: the source tree also holds `__tests__/`, which must never install.
|
|
47
|
+
*/
|
|
48
|
+
function claudeHookFiles(ctx) {
|
|
49
|
+
const files = ctx.manifest?.runtime?.files || [];
|
|
50
|
+
return files.filter((rel) => rel.startsWith('hooks/')).map((rel) => rel.slice('hooks/'.length));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeSourceFile(ctx, platformKey, src, dest, label) {
|
|
54
|
+
const platform = PLATFORMS[platformKey];
|
|
55
|
+
const { action } = writeManagedFile({
|
|
56
|
+
src,
|
|
57
|
+
dest,
|
|
58
|
+
platformFolder: platform.folder,
|
|
59
|
+
ctx,
|
|
60
|
+
tracker: ctx.trackers[platformKey]
|
|
61
|
+
});
|
|
62
|
+
report(ctx, action, label);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function installHooks(ctx, platformKey) {
|
|
66
|
+
const platform = PLATFORMS[platformKey];
|
|
67
|
+
const overlay = new Set(overlayFiles());
|
|
68
|
+
// Claude set first, skipping anything the overlay is about to replace so a file is
|
|
69
|
+
// written once and the manifest records the bytes that actually land.
|
|
70
|
+
for (const rel of claudeHookFiles(ctx)) {
|
|
71
|
+
if (overlay.has(rel)) continue;
|
|
72
|
+
writeSourceFile(
|
|
73
|
+
ctx,
|
|
74
|
+
platformKey,
|
|
75
|
+
path.join(CLAUDE_SRC, 'hooks', rel),
|
|
76
|
+
path.join(platform.folder, 'hooks', rel),
|
|
77
|
+
`omp hooks: ${rel}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
for (const rel of overlay) {
|
|
81
|
+
writeSourceFile(
|
|
82
|
+
ctx,
|
|
83
|
+
platformKey,
|
|
84
|
+
path.join(OMP_SRC, 'hooks', rel),
|
|
85
|
+
path.join(platform.folder, 'hooks', rel),
|
|
86
|
+
`omp hooks (overlay): ${rel}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function installRuntimeFiles(ctx, platformKey) {
|
|
92
|
+
const platform = PLATFORMS[platformKey];
|
|
93
|
+
writeSourceFile(
|
|
94
|
+
ctx,
|
|
95
|
+
platformKey,
|
|
96
|
+
path.join(OMP_SRC, 'runtime.json'),
|
|
97
|
+
path.join(platform.folder, 'runtime.json'),
|
|
98
|
+
'omp runtime: runtime.json'
|
|
99
|
+
);
|
|
100
|
+
// One canonical runtime schema lives in the Claude source tree; every installed
|
|
101
|
+
// runtime references it as ./runtime.schema.json beside its runtime.json.
|
|
102
|
+
writeSourceFile(
|
|
103
|
+
ctx,
|
|
104
|
+
platformKey,
|
|
105
|
+
path.join(CLAUDE_SRC, 'runtime.schema.json'),
|
|
106
|
+
path.join(platform.folder, 'runtime.schema.json'),
|
|
107
|
+
'omp runtime schema'
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function installBridge(ctx, platformKey) {
|
|
112
|
+
const platform = PLATFORMS[platformKey];
|
|
113
|
+
const bridgeSource = path.join(OMP_SRC, 'extensions');
|
|
114
|
+
const bridgeDest = path.join(platform.folder, 'extensions');
|
|
115
|
+
if (fs.existsSync(bridgeSource)) {
|
|
116
|
+
const bridge = copyManagedTree({
|
|
117
|
+
src: bridgeSource,
|
|
118
|
+
dest: bridgeDest,
|
|
119
|
+
platformFolder: platform.folder,
|
|
120
|
+
ctx,
|
|
121
|
+
tracker: ctx.trackers[platformKey]
|
|
122
|
+
});
|
|
123
|
+
report(ctx, treeAction(bridge), 'omp bridge extension');
|
|
124
|
+
} else if (!ctx.dryRun) {
|
|
125
|
+
// A half-provisioned install is visibly empty rather than silently absent.
|
|
126
|
+
fs.mkdirSync(bridgeDest, { recursive: true });
|
|
127
|
+
ctx.ui.detail(' ✓ omp extensions directory created (bridge not present)');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function installOmpRuntime(ctx, platformKey) {
|
|
132
|
+
if (platformKey !== 'omp') return;
|
|
133
|
+
installHooks(ctx, platformKey);
|
|
134
|
+
installRuntimeFiles(ctx, platformKey);
|
|
135
|
+
installBridge(ctx, platformKey);
|
|
136
|
+
copyRulesDirectory(ctx, platformKey);
|
|
137
|
+
return ctx;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { installOmpRuntime, overlayFiles, claudeHookFiles };
|
|
@@ -110,6 +110,9 @@ function ensureGitignore(ctx) {
|
|
|
110
110
|
// Local runtime payload — reinstall with npx; keep out of git
|
|
111
111
|
'.claude/',
|
|
112
112
|
'.codex/',
|
|
113
|
+
// omp auto-executes every file in .omp/extensions/, so a committed copy
|
|
114
|
+
// would run arbitrary code on clone. Keep it out of git like the rest.
|
|
115
|
+
'.omp/',
|
|
113
116
|
'.agents/'
|
|
114
117
|
];
|
|
115
118
|
const prefix = ctx.dryRun ? '[dry-run] ' : '';
|
|
@@ -11,7 +11,8 @@ const path = require('path');
|
|
|
11
11
|
const {
|
|
12
12
|
PLATFORMS,
|
|
13
13
|
detectPlatforms,
|
|
14
|
-
getPlatformKeys
|
|
14
|
+
getPlatformKeys,
|
|
15
|
+
resolvePlatformAliases
|
|
15
16
|
} = require('../lib/context');
|
|
16
17
|
const { SUPPORTED, LANGUAGE_LABELS, OTHER_LABEL } = require('../lib/i18n');
|
|
17
18
|
|
|
@@ -36,7 +37,25 @@ function getInstalledLocale(platformKeys = getPlatformKeys()) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/** First step: pick the installer/UI language (interactive only). */
|
|
40
|
+
/**
|
|
41
|
+
* Resolve host aliases on the requested platform list, once, before anything reads it.
|
|
42
|
+
*
|
|
43
|
+
* selectLanguage() filters this list through PLATFORMS to decide which installed runtime
|
|
44
|
+
* to take a saved locale from, so an unresolved alias would empty that list and fall back
|
|
45
|
+
* to scanning every platform folder — `--platform grok` in a project that also has
|
|
46
|
+
* `.codex/` would then silently take Codex's language.
|
|
47
|
+
*/
|
|
48
|
+
function applyPlatformAliases(ctx) {
|
|
49
|
+
const requested = ctx.options.platforms || [];
|
|
50
|
+
if (requested.length === 0) return ctx;
|
|
51
|
+
const { platforms, aliased } = resolvePlatformAliases(requested);
|
|
52
|
+
ctx.options.platforms = platforms;
|
|
53
|
+
ctx.aliasedHost = aliased;
|
|
54
|
+
return ctx;
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
async function selectLanguage(ctx) {
|
|
58
|
+
applyPlatformAliases(ctx);
|
|
40
59
|
// Restore the saved locale BEFORE the interactivity check: a non-interactive
|
|
41
60
|
// upgrade (--yes) must not forget the configured language. Skipping this left
|
|
42
61
|
// ctx at the 'en' default, and patchRuntimeLocale then clobbered the user's
|
|
@@ -161,6 +180,7 @@ async function resolvePlatforms(ctx) {
|
|
|
161
180
|
ctx.ui.info(ctx.t('installingFor', {
|
|
162
181
|
names: platformNames(requested)
|
|
163
182
|
}));
|
|
183
|
+
if (ctx.aliasedHost) ctx.ui.info(ctx.t('grokCompatNotice'));
|
|
164
184
|
reportInstallMode(ctx);
|
|
165
185
|
return ctx;
|
|
166
186
|
}
|
|
@@ -244,6 +264,7 @@ function getInstalledPlatform() {
|
|
|
244
264
|
|
|
245
265
|
module.exports = {
|
|
246
266
|
selectLanguage,
|
|
267
|
+
applyPlatformAliases,
|
|
247
268
|
promptPlatformSelection,
|
|
248
269
|
resolvePlatforms,
|
|
249
270
|
getInstalledPlatform,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Resolve the optional document-skills bundle for each selected runtime. */
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { PLATFORMS } = require('../lib/context');
|
|
6
|
+
|
|
7
|
+
function readMetadata(platformKey) {
|
|
8
|
+
const target = path.join(PLATFORMS[platformKey].folder, 'cafekit.json');
|
|
9
|
+
if (!fs.existsSync(target)) return { state: 'absent', target, metadata: null };
|
|
10
|
+
try {
|
|
11
|
+
const metadata = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
12
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
13
|
+
return { state: 'invalid', target, metadata: null, reason: 'expected a JSON object' };
|
|
14
|
+
}
|
|
15
|
+
return { state: 'valid', target, metadata };
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return { state: 'invalid', target, metadata: null, reason: error.message };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function explicitSelection(options) {
|
|
22
|
+
if (options.withDocumentSkills) {
|
|
23
|
+
return { enabled: true, selectionSource: 'cli-opt-in' };
|
|
24
|
+
}
|
|
25
|
+
if (options.withoutDocumentSkills) {
|
|
26
|
+
return { enabled: false, selectionSource: 'cli-opt-out' };
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function selectDocumentSkills(ctx) {
|
|
32
|
+
const explicit = explicitSelection(ctx.options);
|
|
33
|
+
const unresolvedFresh = [];
|
|
34
|
+
|
|
35
|
+
for (const platformKey of ctx.platforms) {
|
|
36
|
+
if (explicit) {
|
|
37
|
+
ctx.documentSkills[platformKey] = { ...explicit };
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const metadataResult = readMetadata(platformKey);
|
|
42
|
+
const metadata = metadataResult.metadata;
|
|
43
|
+
if (metadata?.schemaVersion >= 2 && typeof metadata.documentSkills?.enabled === 'boolean') {
|
|
44
|
+
ctx.documentSkills[platformKey] = {
|
|
45
|
+
enabled: metadata.documentSkills.enabled,
|
|
46
|
+
selectionSource: metadata.documentSkills.selectionSource || 'persisted'
|
|
47
|
+
};
|
|
48
|
+
} else if (metadataResult.state === 'valid') {
|
|
49
|
+
ctx.documentSkills[platformKey] = {
|
|
50
|
+
enabled: true,
|
|
51
|
+
selectionSource: 'legacy-upgrade'
|
|
52
|
+
};
|
|
53
|
+
} else if (metadataResult.state === 'invalid') {
|
|
54
|
+
ctx.documentSkills[platformKey] = {
|
|
55
|
+
enabled: true,
|
|
56
|
+
selectionSource: 'metadata-recovery'
|
|
57
|
+
};
|
|
58
|
+
ctx.ui.warn(ctx.t('documentSkillsMetadataRecovery', {
|
|
59
|
+
path: metadataResult.target
|
|
60
|
+
}));
|
|
61
|
+
} else if (fs.existsSync(path.join(PLATFORMS[platformKey].folder, 'cafekit-manifest.json'))) {
|
|
62
|
+
ctx.documentSkills[platformKey] = {
|
|
63
|
+
enabled: true,
|
|
64
|
+
selectionSource: 'legacy-upgrade'
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
unresolvedFresh.push(platformKey);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let freshEnabled = false;
|
|
72
|
+
let source = 'default-fresh';
|
|
73
|
+
if (unresolvedFresh.length > 0 && ctx.interactive) {
|
|
74
|
+
const answer = await ctx.ui.confirm(
|
|
75
|
+
{ message: ctx.t('documentSkillsConfirm'), initialValue: false },
|
|
76
|
+
false
|
|
77
|
+
);
|
|
78
|
+
if (ctx.ui.isCancel(answer)) {
|
|
79
|
+
ctx.cancelled = true;
|
|
80
|
+
return ctx;
|
|
81
|
+
}
|
|
82
|
+
freshEnabled = Boolean(answer);
|
|
83
|
+
source = 'interactive';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const platformKey of unresolvedFresh) {
|
|
87
|
+
ctx.documentSkills[platformKey] = {
|
|
88
|
+
enabled: freshEnabled,
|
|
89
|
+
selectionSource: source
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return ctx;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { readMetadata, explicitSelection, selectDocumentSkills };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Ownership-aware removal for retired and deselected skill directories. */
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { PLATFORMS } = require('../lib/context');
|
|
6
|
+
const { hashFile } = require('../lib/manifest');
|
|
7
|
+
|
|
8
|
+
function listTreeEntries(root) {
|
|
9
|
+
const entries = [];
|
|
10
|
+
const visit = (current) => {
|
|
11
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
12
|
+
const target = path.join(current, entry.name);
|
|
13
|
+
entries.push({ target, entry });
|
|
14
|
+
if (entry.isDirectory() && !entry.isSymbolicLink()) visit(target);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
visit(root);
|
|
18
|
+
return entries;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function classifySkillTree(ctx, platformKey, skillName) {
|
|
22
|
+
const platform = PLATFORMS[platformKey];
|
|
23
|
+
const tracker = ctx.trackers[platformKey];
|
|
24
|
+
const target = path.join(platform.skillsDir, skillName);
|
|
25
|
+
if (!fs.existsSync(target)) return { target, removable: false, absent: true };
|
|
26
|
+
if (!fs.lstatSync(target).isDirectory()) {
|
|
27
|
+
return { target, removable: false, absent: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const entries = listTreeEntries(target);
|
|
31
|
+
const ownership = ctx.ownership?.[platform.folder]?.files || {};
|
|
32
|
+
const unsafe = entries.some(({ target: item, entry }) => {
|
|
33
|
+
if (!entry.isFile() || entry.isSymbolicLink()) return !entry.isDirectory();
|
|
34
|
+
const key = tracker.keyFor(item);
|
|
35
|
+
return ownership[key]?.sha256 !== hashFile(item);
|
|
36
|
+
});
|
|
37
|
+
return { target, removable: entries.length > 0 && !unsafe, absent: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function removeSkill(ctx, platformKey, skillName) {
|
|
41
|
+
const tracker = ctx.trackers[platformKey];
|
|
42
|
+
const classified = classifySkillTree(ctx, platformKey, skillName);
|
|
43
|
+
if (classified.absent) return;
|
|
44
|
+
|
|
45
|
+
if (!classified.removable) {
|
|
46
|
+
ctx.ui.warn(`Preserved user-owned skill: ${classified.target}`);
|
|
47
|
+
ctx.results.preserved++;
|
|
48
|
+
ctx.results.preservedFiles.push(classified.target);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const prefix = `${tracker.keyFor(classified.target).replace(/\/$/, '')}/`;
|
|
53
|
+
if (!ctx.dryRun) {
|
|
54
|
+
fs.rmSync(classified.target, { recursive: true, force: true });
|
|
55
|
+
tracker.prunePrefix(prefix);
|
|
56
|
+
}
|
|
57
|
+
ctx.ui.detail(` ↻ ${ctx.dryRun ? '[dry-run] ' : ''}Removed skill: ${classified.target}`);
|
|
58
|
+
ctx.results.updated++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function reconcileSkillInventory(ctx, platformKey) {
|
|
62
|
+
const retired = ctx.manifest?.obsolete?.skills || [];
|
|
63
|
+
for (const skillName of retired) removeSkill(ctx, platformKey, skillName);
|
|
64
|
+
|
|
65
|
+
if (!ctx.documentSkills?.[platformKey]?.enabled) {
|
|
66
|
+
const optional = ctx.manifest?.skills?.bundles?.documentSkills || [];
|
|
67
|
+
for (const skillName of optional) removeSkill(ctx, platformKey, skillName);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { listTreeEntries, classifySkillTree, removeSkill, reconcileSkillInventory };
|