@haposoft/cafekit 0.16.0-rc.8 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -27
- package/bin/__tests__/codex-hooks-ownership.test.js +239 -0
- package/bin/__tests__/codex-hooks.test.js +50 -0
- package/bin/__tests__/codex-native.test.js +1882 -70
- package/bin/__tests__/develop-contract.test.js +400 -23
- package/bin/__tests__/installer-safety.test.js +3 -3
- 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-schema.test.js +111 -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 +1 -1
- package/bin/install.js +10 -0
- package/bin/lib/codex-install.js +170 -5
- package/bin/lib/context.js +14 -0
- package/bin/lib/i18n.js +12 -6
- 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 +8 -2
- 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/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__/spec-gate.test.js +10 -3
- package/src/claude/hooks/__tests__/state.test.js +5 -0
- package/src/claude/hooks/__tests__/statusline.test.js +196 -0
- package/src/claude/hooks/agent.cjs +1 -1
- package/src/claude/hooks/docs-sync.cjs +12 -10
- package/src/claude/hooks/inspect-block.cjs +1 -1
- package/src/claude/hooks/lib/config.cjs +3 -0
- package/src/claude/hooks/lib/hook-state-dir.cjs +39 -0
- package/src/claude/hooks/lib/secret-keywords.cjs +42 -0
- package/src/claude/hooks/precompact.cjs +105 -0
- package/src/claude/hooks/rules.cjs +1 -1
- package/src/claude/hooks/secret-output-guardrail.cjs +43 -0
- package/src/claude/hooks/session.cjs +27 -1
- package/src/claude/hooks/spec-gate.cjs +3 -3
- package/src/claude/hooks/spec-state.cjs +2 -2
- package/src/claude/hooks/state.cjs +1 -1
- package/src/claude/hooks/task-scaffold-guard.cjs +1 -1
- package/src/claude/hooks/usage.cjs +1 -1
- package/src/claude/migration-manifest.json +29 -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/workflow.md +3 -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 +18 -5
- 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 +41 -60
- 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 +2 -1
- package/src/codex/hooks.json +5 -0
- package/src/codex/runtime.json +2 -0
- package/src/common/.env.example +1 -1
- 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
package/bin/lib/context.js
CHANGED
|
@@ -27,6 +27,9 @@ function validateManifestV2(manifest) {
|
|
|
27
27
|
if (!manifest || manifest.version !== 2) return false;
|
|
28
28
|
if (!manifest.runtime?.files || !Array.isArray(manifest.runtime.files)) return false;
|
|
29
29
|
if (!manifest.settings?.template) return false;
|
|
30
|
+
if (!Array.isArray(manifest.skills?.required)) return false;
|
|
31
|
+
if (!Array.isArray(manifest.skills?.bundles?.documentSkills)) return false;
|
|
32
|
+
if (!Array.isArray(manifest.obsolete?.skills)) return false;
|
|
30
33
|
return true;
|
|
31
34
|
}
|
|
32
35
|
|
|
@@ -205,6 +208,8 @@ function parseInstallerArgs(argv) {
|
|
|
205
208
|
yes: false,
|
|
206
209
|
withSkillsDeps: false,
|
|
207
210
|
withRtk: false,
|
|
211
|
+
withDocumentSkills: false,
|
|
212
|
+
withoutDocumentSkills: false,
|
|
208
213
|
platforms: []
|
|
209
214
|
};
|
|
210
215
|
|
|
@@ -221,6 +226,10 @@ function parseInstallerArgs(argv) {
|
|
|
221
226
|
args.withSkillsDeps = true;
|
|
222
227
|
} else if (arg === '--with-rtk') {
|
|
223
228
|
args.withRtk = true;
|
|
229
|
+
} else if (arg === '--with-document-skills') {
|
|
230
|
+
args.withDocumentSkills = true;
|
|
231
|
+
} else if (arg === '--without-document-skills') {
|
|
232
|
+
args.withoutDocumentSkills = true;
|
|
224
233
|
} else if (arg === '--platform') {
|
|
225
234
|
args.platforms.push(...String(argv[++i] || '').split(',').filter(Boolean));
|
|
226
235
|
} else if (arg.startsWith('--platform=')) {
|
|
@@ -232,6 +241,10 @@ function parseInstallerArgs(argv) {
|
|
|
232
241
|
}
|
|
233
242
|
}
|
|
234
243
|
|
|
244
|
+
if (args.withDocumentSkills && args.withoutDocumentSkills) {
|
|
245
|
+
throw new Error('--with-document-skills and --without-document-skills cannot be used together');
|
|
246
|
+
}
|
|
247
|
+
|
|
235
248
|
// Back-compat alias used throughout the phases.
|
|
236
249
|
args.upgrade = args.forceOverwrite;
|
|
237
250
|
return args;
|
|
@@ -298,6 +311,7 @@ function buildContext(argv, runId) {
|
|
|
298
311
|
results: createResults(),
|
|
299
312
|
trackers: {}, // platformKey → ownership tracker, set per platform
|
|
300
313
|
ownership: {}, // platformFolder → ownership manifest read at run start
|
|
314
|
+
documentSkills: {}, // platformKey → persisted optional-bundle selection
|
|
301
315
|
cancelled: false
|
|
302
316
|
};
|
|
303
317
|
}
|
package/bin/lib/i18n.js
CHANGED
|
@@ -58,6 +58,8 @@ const MESSAGES = {
|
|
|
58
58
|
|
|
59
59
|
// Skills / deps setup
|
|
60
60
|
skillDepsConfirm: 'Install skill dependencies? (Python venv, pip, npm, Chromium — takes a few minutes)',
|
|
61
|
+
documentSkillsConfirm: 'Install optional document skills? (docs, DOCX, PDF, PPTX, XLSX, multimodal)',
|
|
62
|
+
documentSkillsMetadataRecovery: 'Could not read {path}; preserving document skills to avoid destructive cleanup.',
|
|
61
63
|
skillsSkipped: 'Skill dependencies skipped.\n Run later: npx @haposoft/cafekit --with-skills-deps',
|
|
62
64
|
|
|
63
65
|
// rtk token-saver setup
|
|
@@ -106,8 +108,8 @@ const MESSAGES = {
|
|
|
106
108
|
|
|
107
109
|
// Next steps
|
|
108
110
|
nextStepsTitle: 'Next steps',
|
|
109
|
-
nsClaude: 'Start coding: /
|
|
110
|
-
nsCodex: 'Start with `$
|
|
111
|
+
nsClaude: 'Start coding: /cf:specs <feature>',
|
|
112
|
+
nsCodex: 'Start with `$cf-specs <feature>`; review and trust project hooks with `/hooks`',
|
|
111
113
|
nsKeys: 'Some skills need API keys: {skills}\n Copy <skill>/.env.example → .env and fill in the values',
|
|
112
114
|
nsForce: 'To force-refresh managed files: npx @haposoft/cafekit --force-overwrite',
|
|
113
115
|
dryRunOnly: 'Dry-run complete — no files were changed.\n Re-run without --dry-run to apply.',
|
|
@@ -180,6 +182,8 @@ const MESSAGES = {
|
|
|
180
182
|
changeAddressingOption:'別の名前に変更',
|
|
181
183
|
|
|
182
184
|
skillDepsConfirm: 'スキルの依存関係をインストールしますか?(Python venv、pip、npm、Chromium — 数分かかります)',
|
|
185
|
+
documentSkillsConfirm: '任意のドキュメントスキルをインストールしますか?(docs、DOCX、PDF、PPTX、XLSX、multimodal)',
|
|
186
|
+
documentSkillsMetadataRecovery: '{path} を読み取れないため、破壊的な削除を避けて document skills を保持します。',
|
|
183
187
|
skillsSkipped: 'スキルの依存関係をスキップしました。\n 後で実行: npx @haposoft/cafekit --with-skills-deps',
|
|
184
188
|
|
|
185
189
|
// rtk token-saver setup
|
|
@@ -226,8 +230,8 @@ const MESSAGES = {
|
|
|
226
230
|
preservedNote: '編集済みファイル {n} 件はそのままにしました。\n --force-overwrite で再実行するとリセットできます(.cafekit-backup/ にバックアップ保存)。',
|
|
227
231
|
|
|
228
232
|
nextStepsTitle: '次のステップ',
|
|
229
|
-
nsClaude: 'コーディング開始: /
|
|
230
|
-
nsCodex: '`$
|
|
233
|
+
nsClaude: 'コーディング開始: /cf:specs <機能名>',
|
|
234
|
+
nsCodex: '`$cf-specs <機能名>` で開始し、`/hooks` でプロジェクトフックを確認・信頼',
|
|
231
235
|
nsKeys: '一部のスキルに API キーが必要です: {skills}\n <skill>/.env.example を .env にコピーし、値を入力してください',
|
|
232
236
|
nsForce: '管理ファイルを強制更新: npx @haposoft/cafekit --force-overwrite',
|
|
233
237
|
dryRunOnly: 'ドライラン完了 — ファイルへの変更はありませんでした。\n 変更を適用するには --dry-run なしで再実行してください。',
|
|
@@ -299,6 +303,8 @@ const MESSAGES = {
|
|
|
299
303
|
changeAddressingOption:'Đổi tên khác',
|
|
300
304
|
|
|
301
305
|
skillDepsConfirm: 'Cài dependencies cho skill ngay? (Python venv, pip, npm, Chromium — mất vài phút)',
|
|
306
|
+
documentSkillsConfirm: 'Cài thêm nhóm skill tài liệu? (docs, DOCX, PDF, PPTX, XLSX, multimodal)',
|
|
307
|
+
documentSkillsMetadataRecovery: 'Không đọc được {path}; giữ nguyên skill tài liệu để tránh dọn nhầm dữ liệu.',
|
|
302
308
|
skillsSkipped: 'Đã bỏ qua dependencies skill.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-skills-deps',
|
|
303
309
|
|
|
304
310
|
// rtk token-saver setup
|
|
@@ -345,8 +351,8 @@ const MESSAGES = {
|
|
|
345
351
|
preservedNote: '{n} tệp bạn đã chỉnh sửa được giữ nguyên.\n Chạy lại với --force-overwrite để cài đè (bản sao lưu ở .cafekit-backup/).',
|
|
346
352
|
|
|
347
353
|
nextStepsTitle: 'Bước tiếp theo',
|
|
348
|
-
nsClaude: 'Bắt đầu code: /
|
|
349
|
-
nsCodex: 'Bắt đầu bằng `$
|
|
354
|
+
nsClaude: 'Bắt đầu code: /cf:specs <mô-tả-tính-năng>',
|
|
355
|
+
nsCodex: 'Bắt đầu bằng `$cf-specs <tính-năng>`; kiểm tra và trust project hooks bằng `/hooks`',
|
|
350
356
|
nsKeys: 'Một số skill cần API key: {skills}\n Sao chép <skill>/.env.example → .env rồi điền giá trị',
|
|
351
357
|
nsForce: 'Làm mới file được quản lý: npx @haposoft/cafekit --force-overwrite',
|
|
352
358
|
dryRunOnly: 'Dry-run hoàn tất — không có tệp nào thay đổi.\n Chạy lại không có --dry-run để áp dụng.',
|
package/bin/lib/skill-deps.js
CHANGED
|
@@ -271,15 +271,15 @@ function collectSkillPackages(skillsDir) {
|
|
|
271
271
|
|
|
272
272
|
/** External binaries some skills shell out to — detected, never auto-installed. */
|
|
273
273
|
const SYSTEM_TOOLS = [
|
|
274
|
-
{ cmd: 'ffmpeg', why: 'ai-multimodal (audio/video)', brew: 'brew install ffmpeg', apt: 'sudo apt install ffmpeg', win: 'winget install ffmpeg' },
|
|
275
|
-
{ cmd: 'pdftotext', why: 'pdf (text extraction, poppler)', brew: 'brew install poppler', apt: 'sudo apt install poppler-utils', win: 'choco install poppler' },
|
|
276
|
-
{ cmd: 'rsvg-convert', why: 'generate-graph (PNG export, librsvg)', brew: 'brew install librsvg', apt: 'sudo apt install librsvg2-bin', win: 'choco install rsvg-convert' },
|
|
277
|
-
{ cmd: 'tesseract', why: 'pdf/ai-multimodal (OCR)', brew: 'brew install tesseract', apt: 'sudo apt install tesseract-ocr', win: 'choco install tesseract' }
|
|
274
|
+
{ cmd: 'ffmpeg', skills: ['ai-multimodal'], why: 'ai-multimodal (audio/video)', brew: 'brew install ffmpeg', apt: 'sudo apt install ffmpeg', win: 'winget install ffmpeg' },
|
|
275
|
+
{ cmd: 'pdftotext', skills: ['pdf'], why: 'pdf (text extraction, poppler)', brew: 'brew install poppler', apt: 'sudo apt install poppler-utils', win: 'choco install poppler' },
|
|
276
|
+
{ cmd: 'rsvg-convert', skills: ['generate-graph'], why: 'generate-graph (PNG export, librsvg)', brew: 'brew install librsvg', apt: 'sudo apt install librsvg2-bin', win: 'choco install rsvg-convert' },
|
|
277
|
+
{ cmd: 'tesseract', skills: ['pdf', 'ai-multimodal'], why: 'pdf/ai-multimodal (OCR)', brew: 'brew install tesseract', apt: 'sudo apt install tesseract-ocr', win: 'choco install tesseract' }
|
|
278
278
|
];
|
|
279
279
|
|
|
280
280
|
/** Global npm CLIs some skills expect — detected, never auto-installed. */
|
|
281
281
|
const GLOBAL_NPM = [
|
|
282
|
-
{ cmd: 'agent-browser', pkg: 'agent-browser', why: 'agent-browser skill' }
|
|
282
|
+
{ cmd: 'agent-browser', pkg: 'agent-browser', skills: ['agent-browser'], why: 'agent-browser skill' }
|
|
283
283
|
];
|
|
284
284
|
|
|
285
285
|
/** Per-OS install hint for a SYSTEM_TOOLS entry. */
|
|
@@ -276,8 +276,31 @@ function copyRulesDirectory(ctx, platformKey) {
|
|
|
276
276
|
report(ctx, treeAction(agg), 'rules/ directory');
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Copy the output-styles/ tree from the one canonical source. Claude Code discovers
|
|
281
|
+
* these natively under `.claude/`; Codex CLI has no output-style feature, so its rules
|
|
282
|
+
* hook reads the selected file and injects it once per session instead.
|
|
283
|
+
*/
|
|
284
|
+
function copyOutputStylesDirectory(ctx, platformKey) {
|
|
285
|
+
|
|
286
|
+
const src = path.join(SRC, 'claude/output-styles');
|
|
287
|
+
const dest = getRuntimeSupportTargetDir(platformKey, 'output-styles');
|
|
288
|
+
if (!fs.existsSync(src)) {
|
|
289
|
+
ctx.ui.warn('output-styles/ directory not found');
|
|
290
|
+
ctx.results.missingDependencies++;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const transform = getCopyOptions(platformKey, {}).transform;
|
|
295
|
+
const agg = copyManagedTree({
|
|
296
|
+
src, dest, platformFolder: PLATFORMS[platformKey].folder, ctx, tracker: ctx.trackers[platformKey], transform
|
|
297
|
+
});
|
|
298
|
+
report(ctx, treeAction(agg), 'output-styles/ directory');
|
|
299
|
+
}
|
|
300
|
+
|
|
279
301
|
module.exports = {
|
|
280
302
|
copyRoutingFile,
|
|
303
|
+
copyOutputStylesDirectory,
|
|
281
304
|
copyClaudeRuntimeFiles,
|
|
282
305
|
removeObsoleteClaudeRuntimeFiles,
|
|
283
306
|
copyClaudeMdFile,
|
|
@@ -109,6 +109,14 @@ function mergeClaudeSettings(ctx, platformKey) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// outputStyle: a reader preference, not CafeKit functionality. Seed it once when
|
|
113
|
+
// the user has none, and never replace a choice they made — not even under
|
|
114
|
+
// --force-overwrite, which exists to repair managed files, not to reset taste.
|
|
115
|
+
if (managedSettings.outputStyle && !existingSettings.outputStyle) {
|
|
116
|
+
mergedSettings.outputStyle = managedSettings.outputStyle;
|
|
117
|
+
ctx.ui.detail(` ✓ ${ctx.dryRun ? '[dry-run] ' : ''}Settings: outputStyle seeded (${managedSettings.outputStyle})`);
|
|
118
|
+
}
|
|
119
|
+
|
|
112
120
|
// hooks: merge managed hooks per command (not per entry). An entry is keyed
|
|
113
121
|
// by its matcher; commands missing from the matching entry are appended, so
|
|
114
122
|
// a new command added to an existing matcher entry is not swallowed on
|
|
@@ -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);
|
|
@@ -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 };
|
|
@@ -115,8 +115,15 @@ async function setupNode(ctx, skillsDir) {
|
|
|
115
115
|
|
|
116
116
|
/** Detect (don't install) system binaries + global npm CLIs and print guidance. */
|
|
117
117
|
function guideManual(ctx) {
|
|
118
|
-
const
|
|
119
|
-
const
|
|
118
|
+
const installed = new Set();
|
|
119
|
+
for (const key of ctx.platforms) {
|
|
120
|
+
const skillsDir = PLATFORMS[key].skillsDir;
|
|
121
|
+
if (!fs.existsSync(skillsDir)) continue;
|
|
122
|
+
for (const name of fs.readdirSync(skillsDir)) installed.add(name);
|
|
123
|
+
}
|
|
124
|
+
const applies = (item) => (item.skills || []).some((skill) => installed.has(skill));
|
|
125
|
+
const missingTools = dep.SYSTEM_TOOLS.filter((t) => applies(t) && !dep.hasCmd(t.cmd));
|
|
126
|
+
const missingNpm = dep.GLOBAL_NPM.filter((g) => applies(g) && !dep.hasCmd(g.cmd));
|
|
120
127
|
if (missingTools.length === 0 && missingNpm.length === 0) return;
|
|
121
128
|
|
|
122
129
|
const lines = [];
|