@haposoft/cafekit 0.16.0-rc.7 → 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 +295 -0
- package/bin/__tests__/codex-native.test.js +2289 -34
- 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 +2131 -16
- 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 +24 -13
- 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 +87 -4
- package/src/claude/hooks/__tests__/state.test.js +254 -3
- 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 +32 -5
- 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/spec-receipt.cjs +76 -21
- package/src/claude/scripts/spec-resolver.cjs +144 -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 +48 -57
- package/src/claude/skills/specs/references/review.md +26 -8
- package/src/claude/skills/specs/references/templates.md +76 -31
- 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 +34 -5
- 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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
loadClaudeMigrationManifest,
|
|
10
|
+
parseInstallerArgs,
|
|
11
|
+
PLATFORMS
|
|
12
|
+
} = require('../lib/context');
|
|
13
|
+
const manifestLib = require('../lib/manifest');
|
|
14
|
+
const { selectDocumentSkills } = require('../phases/select-skill-bundles');
|
|
15
|
+
const { reconcileSkillInventory } = require('../phases/skill-inventory');
|
|
16
|
+
const installerPath = path.resolve(__dirname, '..', 'install.js');
|
|
17
|
+
|
|
18
|
+
function withTempProject(run) {
|
|
19
|
+
const previous = process.cwd();
|
|
20
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-optional-skills-'));
|
|
21
|
+
process.chdir(root);
|
|
22
|
+
try {
|
|
23
|
+
return run(root);
|
|
24
|
+
} finally {
|
|
25
|
+
process.chdir(previous);
|
|
26
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function withTempProjectAsync(run) {
|
|
31
|
+
const previous = process.cwd();
|
|
32
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-optional-skills-'));
|
|
33
|
+
process.chdir(root);
|
|
34
|
+
try {
|
|
35
|
+
return await run(root);
|
|
36
|
+
} finally {
|
|
37
|
+
process.chdir(previous);
|
|
38
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function silentUi() {
|
|
43
|
+
return {
|
|
44
|
+
confirm: async (_options, fallback) => fallback,
|
|
45
|
+
isCancel: () => false,
|
|
46
|
+
warn: () => {},
|
|
47
|
+
detail: () => {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('manifest separates core, optional documents, and retired skills', () => {
|
|
52
|
+
const manifest = loadClaudeMigrationManifest();
|
|
53
|
+
assert.equal(manifest.skills.required.includes('route'), true);
|
|
54
|
+
assert.deepEqual(manifest.skills.bundles.documentSkills, [
|
|
55
|
+
'ai-multimodal', 'docs', 'docx', 'pdf', 'pptx', 'xlsx'
|
|
56
|
+
]);
|
|
57
|
+
for (const skill of manifest.skills.bundles.documentSkills) {
|
|
58
|
+
assert.equal(manifest.skills.required.includes(skill), false);
|
|
59
|
+
}
|
|
60
|
+
assert.deepEqual(manifest.obsolete.skills, [
|
|
61
|
+
'backend-development', 'frontend-development', 'frontend-design',
|
|
62
|
+
'mobile-development', 'devops', 'react-best-practices'
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('document skill flags are mutually exclusive', () => {
|
|
67
|
+
assert.throws(
|
|
68
|
+
() => parseInstallerArgs(['node', 'install', '--with-document-skills', '--without-document-skills']),
|
|
69
|
+
/cannot be used together/
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('selection defaults fresh non-interactive OFF and preserves legacy upgrade ON', async () => {
|
|
74
|
+
await withTempProjectAsync(async () => {
|
|
75
|
+
const base = {
|
|
76
|
+
platforms: ['claude', 'codex'],
|
|
77
|
+
options: {},
|
|
78
|
+
documentSkills: {},
|
|
79
|
+
interactive: false,
|
|
80
|
+
ui: silentUi(),
|
|
81
|
+
t: (key) => key
|
|
82
|
+
};
|
|
83
|
+
fs.mkdirSync('.codex', { recursive: true });
|
|
84
|
+
fs.writeFileSync('.codex/cafekit.json', '{"schemaVersion":1,"platform":"codex"}\n');
|
|
85
|
+
|
|
86
|
+
await selectDocumentSkills(base);
|
|
87
|
+
assert.deepEqual(base.documentSkills.claude, {
|
|
88
|
+
enabled: false,
|
|
89
|
+
selectionSource: 'default-fresh'
|
|
90
|
+
});
|
|
91
|
+
assert.deepEqual(base.documentSkills.codex, {
|
|
92
|
+
enabled: true,
|
|
93
|
+
selectionSource: 'legacy-upgrade'
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('interactive cancellation stops before selecting a fresh bundle', async () => {
|
|
99
|
+
await withTempProjectAsync(async () => {
|
|
100
|
+
const ctx = {
|
|
101
|
+
platforms: ['claude'],
|
|
102
|
+
options: {},
|
|
103
|
+
documentSkills: {},
|
|
104
|
+
interactive: true,
|
|
105
|
+
cancelled: false,
|
|
106
|
+
ui: {
|
|
107
|
+
confirm: async () => 'cancel',
|
|
108
|
+
isCancel: (value) => value === 'cancel'
|
|
109
|
+
},
|
|
110
|
+
t: (key) => key
|
|
111
|
+
};
|
|
112
|
+
await selectDocumentSkills(ctx);
|
|
113
|
+
assert.equal(ctx.cancelled, true);
|
|
114
|
+
assert.equal(ctx.documentSkills.claude, undefined);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
for (const platformKey of ['claude', 'codex']) {
|
|
119
|
+
test(`${platformKey} prunes pristine optional skills and preserves modified skills`, () => {
|
|
120
|
+
withTempProject(() => {
|
|
121
|
+
const platform = PLATFORMS[platformKey];
|
|
122
|
+
const docsFile = path.join(platform.skillsDir, 'docs', 'SKILL.md');
|
|
123
|
+
const pdfFile = path.join(platform.skillsDir, 'pdf', 'SKILL.md');
|
|
124
|
+
fs.mkdirSync(path.dirname(docsFile), { recursive: true });
|
|
125
|
+
fs.mkdirSync(path.dirname(pdfFile), { recursive: true });
|
|
126
|
+
fs.writeFileSync(docsFile, 'owned\n');
|
|
127
|
+
fs.writeFileSync(pdfFile, 'owned then edited\n');
|
|
128
|
+
|
|
129
|
+
const ownership = {
|
|
130
|
+
schemaVersion: 1,
|
|
131
|
+
version: 'old',
|
|
132
|
+
files: {}
|
|
133
|
+
};
|
|
134
|
+
const recordRoot = platform.ownership?.recordRoot || platform.folder;
|
|
135
|
+
const key = (file) => path.relative(recordRoot, file).replace(/\\/g, '/');
|
|
136
|
+
ownership.files[key(docsFile)] = { sha256: manifestLib.hashFile(docsFile), version: 'old' };
|
|
137
|
+
ownership.files[key(pdfFile)] = { sha256: manifestLib.sha256('before edit\n'), version: 'old' };
|
|
138
|
+
|
|
139
|
+
const ctx = {
|
|
140
|
+
manifest: { obsolete: { skills: [] }, skills: { bundles: { documentSkills: ['docs', 'pdf'] } } },
|
|
141
|
+
documentSkills: { [platformKey]: { enabled: false, selectionSource: 'cli-opt-out' } },
|
|
142
|
+
ownership: { [platform.folder]: ownership },
|
|
143
|
+
trackers: {
|
|
144
|
+
[platformKey]: manifestLib.createTracker(platform.folder, 'next', platform.ownership)
|
|
145
|
+
},
|
|
146
|
+
dryRun: false,
|
|
147
|
+
ui: silentUi(),
|
|
148
|
+
results: { updated: 0, preserved: 0, preservedFiles: [] }
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
reconcileSkillInventory(ctx, platformKey);
|
|
152
|
+
assert.equal(fs.existsSync(path.dirname(docsFile)), false);
|
|
153
|
+
assert.equal(fs.existsSync(path.dirname(pdfFile)), true);
|
|
154
|
+
assert.equal(ctx.results.updated, 1);
|
|
155
|
+
assert.equal(ctx.results.preserved, 1);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
test('fresh combined install defaults optional bundle OFF', () => {
|
|
161
|
+
withTempProject((root) => {
|
|
162
|
+
const result = spawnSync(process.execPath, [
|
|
163
|
+
installerPath, '--platform', 'claude,codex', '--yes'
|
|
164
|
+
], { cwd: root, encoding: 'utf8' });
|
|
165
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
166
|
+
|
|
167
|
+
for (const platformKey of ['claude', 'codex']) {
|
|
168
|
+
const platform = PLATFORMS[platformKey];
|
|
169
|
+
assert.equal(fs.existsSync(path.join(platform.skillsDir, 'docs')), false);
|
|
170
|
+
assert.equal(fs.existsSync(path.join(platform.skillsDir, 'route', 'SKILL.md')), true);
|
|
171
|
+
const metadata = JSON.parse(fs.readFileSync(path.join(platform.folder, 'cafekit.json'), 'utf8'));
|
|
172
|
+
assert.equal(metadata.schemaVersion, 2);
|
|
173
|
+
assert.equal(metadata.documentSkills.enabled, false);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('explicit opt-in installs all six optional skills on both runtimes', () => {
|
|
179
|
+
withTempProject((root) => {
|
|
180
|
+
const result = spawnSync(process.execPath, [
|
|
181
|
+
installerPath, '--platform', 'claude,codex', '--yes', '--with-document-skills'
|
|
182
|
+
], { cwd: root, encoding: 'utf8' });
|
|
183
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
184
|
+
|
|
185
|
+
const optional = loadClaudeMigrationManifest().skills.bundles.documentSkills;
|
|
186
|
+
for (const platformKey of ['claude', 'codex']) {
|
|
187
|
+
const platform = PLATFORMS[platformKey];
|
|
188
|
+
for (const skill of optional) {
|
|
189
|
+
assert.equal(fs.existsSync(path.join(platform.skillsDir, skill, 'SKILL.md')), true, `${platformKey}:${skill}`);
|
|
190
|
+
}
|
|
191
|
+
const catalog = spawnSync(process.execPath, [
|
|
192
|
+
path.join(platform.folder, 'scripts', 'generate-skill-catalog.cjs'), '--json'
|
|
193
|
+
], { cwd: root, encoding: 'utf8' });
|
|
194
|
+
assert.equal(catalog.status, 0, catalog.stderr);
|
|
195
|
+
const catalogData = JSON.parse(catalog.stdout);
|
|
196
|
+
assert.equal(catalogData.skills.some((skill) => skill.public_id === 'cf:route'), true);
|
|
197
|
+
assert.equal(catalogData.skills.some((skill) => skill.public_id === 'cf:docs'), true);
|
|
198
|
+
const metadata = JSON.parse(fs.readFileSync(path.join(platform.folder, 'cafekit.json'), 'utf8'));
|
|
199
|
+
assert.deepEqual(metadata.documentSkills, {
|
|
200
|
+
enabled: true,
|
|
201
|
+
selectionSource: 'cli-opt-in'
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('corrupt metadata preserves an installed document bundle', () => {
|
|
208
|
+
withTempProject((root) => {
|
|
209
|
+
const install = (...args) => spawnSync(process.execPath, [
|
|
210
|
+
installerPath, '--platform', 'claude', '--yes', ...args
|
|
211
|
+
], { cwd: root, encoding: 'utf8' });
|
|
212
|
+
assert.equal(install('--with-document-skills').status, 0);
|
|
213
|
+
fs.writeFileSync(path.join(root, '.claude', 'cafekit.json'), '{broken\n');
|
|
214
|
+
|
|
215
|
+
const recovered = install();
|
|
216
|
+
assert.equal(recovered.status, 0, recovered.stderr || recovered.stdout);
|
|
217
|
+
assert.equal(fs.existsSync(path.join(root, '.claude', 'skills', 'docs', 'SKILL.md')), true);
|
|
218
|
+
const metadata = JSON.parse(fs.readFileSync(path.join(root, '.claude', 'cafekit.json'), 'utf8'));
|
|
219
|
+
assert.deepEqual(metadata.documentSkills, {
|
|
220
|
+
enabled: true,
|
|
221
|
+
selectionSource: 'metadata-recovery'
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('modified retired skill is preserved but excluded from automatic routing', () => {
|
|
227
|
+
withTempProject((root) => {
|
|
228
|
+
const retiredRoot = path.join(root, '.claude', 'skills', 'backend-development');
|
|
229
|
+
fs.mkdirSync(retiredRoot, { recursive: true });
|
|
230
|
+
fs.writeFileSync(path.join(retiredRoot, 'SKILL.md'), [
|
|
231
|
+
'---', 'name: cf:backend-development',
|
|
232
|
+
'description: "User-modified retired skill."', '---', '# Preserved', '',
|
|
233
|
+
].join('\n'));
|
|
234
|
+
const result = spawnSync(process.execPath, [
|
|
235
|
+
installerPath, '--platform', 'claude', '--yes'
|
|
236
|
+
], { cwd: root, encoding: 'utf8' });
|
|
237
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
238
|
+
assert.equal(fs.existsSync(path.join(retiredRoot, 'SKILL.md')), true);
|
|
239
|
+
|
|
240
|
+
const catalog = spawnSync(process.execPath, [
|
|
241
|
+
path.join(root, '.claude', 'scripts', 'generate-skill-catalog.cjs'), '--json'
|
|
242
|
+
], { cwd: root, encoding: 'utf8' });
|
|
243
|
+
assert.equal(catalog.status, 0, catalog.stderr);
|
|
244
|
+
const parsed = JSON.parse(catalog.stdout);
|
|
245
|
+
assert.equal(parsed.skills.some((skill) => skill.directory === 'backend-development'), false);
|
|
246
|
+
assert.ok(parsed.diagnostics.some((item) =>
|
|
247
|
+
item.directory === 'backend-development' && item.code === 'retired_skill'));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Output styles are a Claude Code surface: Claude Code discovers .claude/output-styles/
|
|
4
|
+
// alongside agents, skills and rules, and Codex CLI has no equivalent. These tests pin
|
|
5
|
+
// what installs where, and that every style stays loadable — a style with broken
|
|
6
|
+
// frontmatter is silently unavailable rather than loudly wrong.
|
|
7
|
+
|
|
8
|
+
const assert = require('node:assert/strict');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const os = require('node:os');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { spawnSync } = require('node:child_process');
|
|
13
|
+
const { test } = require('node:test');
|
|
14
|
+
|
|
15
|
+
const PACKAGE_ROOT = path.join(__dirname, '../..');
|
|
16
|
+
const INSTALLER = path.join(PACKAGE_ROOT, 'bin/install.js');
|
|
17
|
+
const SOURCE_DIR = path.join(PACKAGE_ROOT, 'src/claude/output-styles');
|
|
18
|
+
const EXPECTED = [
|
|
19
|
+
'coding-level-0-eli5.md',
|
|
20
|
+
'coding-level-1-junior.md',
|
|
21
|
+
'coding-level-2-mid.md',
|
|
22
|
+
'coding-level-3-senior.md',
|
|
23
|
+
'coding-level-4-lead.md',
|
|
24
|
+
'coding-level-5-god.md',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function inTempProject(run) {
|
|
28
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-output-styles-'));
|
|
29
|
+
try {
|
|
30
|
+
return run(root);
|
|
31
|
+
} finally {
|
|
32
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function install(root, platform) {
|
|
37
|
+
return spawnSync(
|
|
38
|
+
process.execPath,
|
|
39
|
+
[INSTALLER, '--platform', platform, '--yes'],
|
|
40
|
+
{ cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } }
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Parse the leading `---` frontmatter block into a flat key/value map. */
|
|
45
|
+
function frontmatter(content) {
|
|
46
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
47
|
+
if (!match) return null;
|
|
48
|
+
const fields = {};
|
|
49
|
+
for (const line of match[1].split('\n')) {
|
|
50
|
+
const pair = line.match(/^([a-z][a-z0-9-]*):\s*(.*)$/i);
|
|
51
|
+
if (pair) fields[pair[1]] = pair[2].trim();
|
|
52
|
+
}
|
|
53
|
+
return fields;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('the source ships exactly the six coding levels', () => {
|
|
57
|
+
const found = fs.readdirSync(SOURCE_DIR).filter((name) => name.endsWith('.md')).sort();
|
|
58
|
+
assert.deepEqual(found, EXPECTED);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('every style carries the frontmatter Claude Code reads', () => {
|
|
62
|
+
for (const name of EXPECTED) {
|
|
63
|
+
const fields = frontmatter(fs.readFileSync(path.join(SOURCE_DIR, name), 'utf8'));
|
|
64
|
+
assert.ok(fields, `${name} has no frontmatter block, so it cannot load`);
|
|
65
|
+
assert.ok(fields.name, `${name} has no name`);
|
|
66
|
+
assert.ok(fields.description, `${name} has no description`);
|
|
67
|
+
// These styles change how answers are written, not what the agent is allowed to do.
|
|
68
|
+
assert.equal(
|
|
69
|
+
fields['keep-coding-instructions'], 'true',
|
|
70
|
+
`${name} must keep the base coding instructions, or the style would replace them`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('each level declares a distinct name and description', () => {
|
|
76
|
+
const names = new Set();
|
|
77
|
+
const descriptions = new Set();
|
|
78
|
+
for (const file of EXPECTED) {
|
|
79
|
+
const fields = frontmatter(fs.readFileSync(path.join(SOURCE_DIR, file), 'utf8'));
|
|
80
|
+
names.add(fields.name);
|
|
81
|
+
descriptions.add(fields.description);
|
|
82
|
+
}
|
|
83
|
+
assert.equal(names.size, EXPECTED.length, 'two styles share a name and would be indistinguishable');
|
|
84
|
+
assert.equal(descriptions.size, EXPECTED.length, 'two styles share a description');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('a Claude install places every style where Claude Code discovers them', () => {
|
|
88
|
+
inTempProject((root) => {
|
|
89
|
+
const result = install(root, 'claude');
|
|
90
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
91
|
+
const installed = path.join(root, '.claude', 'output-styles');
|
|
92
|
+
for (const name of EXPECTED) {
|
|
93
|
+
assert.equal(fs.existsSync(path.join(installed, name)), true, `missing ${name}`);
|
|
94
|
+
assert.equal(
|
|
95
|
+
fs.readFileSync(path.join(installed, name), 'utf8'),
|
|
96
|
+
fs.readFileSync(path.join(SOURCE_DIR, name), 'utf8'),
|
|
97
|
+
`${name} was altered on the way in`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('a Codex install ships the same styles, since its rules hook injects them', () => {
|
|
104
|
+
inTempProject((root) => {
|
|
105
|
+
const result = install(root, 'codex');
|
|
106
|
+
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
107
|
+
const installed = path.join(root, '.codex', 'output-styles');
|
|
108
|
+
for (const name of EXPECTED) {
|
|
109
|
+
assert.equal(
|
|
110
|
+
fs.readFileSync(path.join(installed, name), 'utf8'),
|
|
111
|
+
fs.readFileSync(path.join(SOURCE_DIR, name), 'utf8'),
|
|
112
|
+
`${name} differs from the one canonical source`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
assert.equal(JSON.parse(fs.readFileSync(path.join(root, '.codex', 'runtime.json'), 'utf8')).codingLevel, 1);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/** Run the installed Codex rules hook and return what it injects. */
|
|
120
|
+
function runRulesHook(root, sessionId) {
|
|
121
|
+
const result = spawnSync(process.execPath, [path.join(root, '.codex', 'hooks', 'rules.cjs')], {
|
|
122
|
+
cwd: root,
|
|
123
|
+
input: JSON.stringify({ session_id: sessionId, cwd: root }),
|
|
124
|
+
encoding: 'utf8',
|
|
125
|
+
});
|
|
126
|
+
assert.equal(result.status, 0, result.stderr);
|
|
127
|
+
return result.stdout;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setCodingLevel(root, level) {
|
|
131
|
+
const file = path.join(root, '.codex', 'runtime.json');
|
|
132
|
+
const runtime = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
133
|
+
runtime.codingLevel = level;
|
|
134
|
+
fs.writeFileSync(file, `${JSON.stringify(runtime, null, 2)}\n`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
test('the Codex rules hook injects the style the coding level names', () => {
|
|
138
|
+
inTempProject((root) => {
|
|
139
|
+
assert.equal(install(root, 'codex').status, 0);
|
|
140
|
+
|
|
141
|
+
const junior = runRulesHook(root, 'session-junior');
|
|
142
|
+
assert.match(junior, /## Communication style/);
|
|
143
|
+
assert.match(junior, /Junior Developer Communication Mode/);
|
|
144
|
+
// The body uses `---` as a horizontal rule, so check a frontmatter-only marker.
|
|
145
|
+
assert.doesNotMatch(junior, /keep-coding-instructions/, 'the frontmatter must be stripped, not injected');
|
|
146
|
+
assert.doesNotMatch(junior, /^description:/m, 'the frontmatter must be stripped, not injected');
|
|
147
|
+
assert.match(junior, /## Rules/, 'the existing rules injection must survive');
|
|
148
|
+
|
|
149
|
+
setCodingLevel(root, 5);
|
|
150
|
+
const expert = runRulesHook(root, 'session-expert');
|
|
151
|
+
assert.match(expert, /God Mode Communication/);
|
|
152
|
+
assert.doesNotMatch(expert, /Junior Developer Communication Mode/);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('the Codex style is injected once per session, not once per turn', () => {
|
|
157
|
+
inTempProject((root) => {
|
|
158
|
+
assert.equal(install(root, 'codex').status, 0);
|
|
159
|
+
const first = runRulesHook(root, 'one-session');
|
|
160
|
+
assert.match(first, /## Communication style/);
|
|
161
|
+
assert.equal(runRulesHook(root, 'one-session'), '', 'a second turn in the same session must inject nothing');
|
|
162
|
+
assert.match(runRulesHook(root, 'another-session'), /## Communication style/);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('an absent or out-of-range coding level injects no style but keeps the rules', () => {
|
|
167
|
+
inTempProject((root) => {
|
|
168
|
+
assert.equal(install(root, 'codex').status, 0);
|
|
169
|
+
for (const [level, label] of [[null, 'null'], [9, 'out of range'], ['1', 'a string']]) {
|
|
170
|
+
setCodingLevel(root, level);
|
|
171
|
+
const out = runRulesHook(root, `session-${label.replace(/\s/g, '-')}`);
|
|
172
|
+
assert.doesNotMatch(out, /## Communication style/, `${label} must not select a style`);
|
|
173
|
+
assert.match(out, /## Rules/, `${label} must not break the existing rules injection`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('a Claude install seeds the default style but never overwrites the user choice', () => {
|
|
179
|
+
inTempProject((root) => {
|
|
180
|
+
assert.equal(install(root, 'claude').status, 0);
|
|
181
|
+
const settingsPath = path.join(root, '.claude', 'settings.json');
|
|
182
|
+
const seeded = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
183
|
+
assert.equal(seeded.outputStyle, 'Junior Developer Mode (Level 1)');
|
|
184
|
+
|
|
185
|
+
seeded.outputStyle = 'God Mode (Level 5)';
|
|
186
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(seeded, null, 2)}\n`);
|
|
187
|
+
|
|
188
|
+
assert.equal(install(root, 'claude').status, 0);
|
|
189
|
+
assert.equal(
|
|
190
|
+
JSON.parse(fs.readFileSync(settingsPath, 'utf8')).outputStyle, 'God Mode (Level 5)',
|
|
191
|
+
'a reinstall reset a preference the user had chosen'
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const forced = spawnSync(process.execPath, [INSTALLER, '--platform', 'claude', '--force-overwrite', '--yes'],
|
|
195
|
+
{ cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } });
|
|
196
|
+
assert.equal(forced.status, 0, `${forced.stdout}\n${forced.stderr}`);
|
|
197
|
+
assert.equal(
|
|
198
|
+
JSON.parse(fs.readFileSync(settingsPath, 'utf8')).outputStyle, 'God Mode (Level 5)',
|
|
199
|
+
'--force-overwrite repairs managed files; it must not reset taste'
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('a style edited by the user survives a reinstall', () => {
|
|
205
|
+
inTempProject((root) => {
|
|
206
|
+
assert.equal(install(root, 'claude').status, 0);
|
|
207
|
+
const target = path.join(root, '.claude', 'output-styles', 'coding-level-5-god.md');
|
|
208
|
+
const edited = `${fs.readFileSync(target, 'utf8')}\n\nAlways answer in Vietnamese.\n`;
|
|
209
|
+
fs.writeFileSync(target, edited);
|
|
210
|
+
|
|
211
|
+
assert.equal(install(root, 'claude').status, 0);
|
|
212
|
+
assert.equal(fs.readFileSync(target, 'utf8'), edited, 'the reinstall discarded a user edit');
|
|
213
|
+
});
|
|
214
|
+
});
|