@phuc1403/musketeer 0.1.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/INSTALLATION.md +22 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/bin/musketeer.js +72 -0
- package/manifest.json +238 -0
- package/package.json +46 -0
- package/src/copier.js +119 -0
- package/src/lockfile.js +61 -0
- package/src/manifest-loader.js +46 -0
- package/src/muster.js +48 -0
- package/src/provisioner/detect.js +93 -0
- package/src/provisioner/guide.js +32 -0
- package/src/provisioner/index.js +113 -0
- package/src/provisioner/mcp.js +26 -0
- package/src/provisioner/os-package-manager.js +71 -0
- package/src/provisioner/venv.js +44 -0
- package/src/reconcile.js +94 -0
- package/src/resolver.js +130 -0
- package/src/schema.js +152 -0
- package/src/settings-merger.js +0 -0
- package/template/.claude/agents/code-reviewer.md +166 -0
- package/template/.claude/agents/hallmark-auditor.md +78 -0
- package/template/.claude/agents/hallmark-explorer.md +81 -0
- package/template/.claude/agents/researcher.md +33 -0
- package/template/.claude/hooks/block-migration-edits.cjs +31 -0
- package/template/.claude/hooks/format-json-hook.js +91 -0
- package/template/.claude/hooks/inject-design-docs.cjs +86 -0
- package/template/.claude/hooks/lib/colors.cjs +122 -0
- package/template/.claude/hooks/lib/git-info-cache.cjs +191 -0
- package/template/.claude/hooks/lib/transcript-parser.cjs +277 -0
- package/template/.claude/hooks/usage-context-awareness.cjs +166 -0
- package/template/.claude/hooks/validate-cml-hook.js +145 -0
- package/template/.claude/skills/adr-writer/SKILL.md +78 -0
- package/template/.claude/skills/adr-writer/references/adr-example.md +38 -0
- package/template/.claude/skills/adr-writer/references/adr-template.md +46 -0
- package/template/.claude/skills/architecture-characteristic-writer/SKILL.md +117 -0
- package/template/.claude/skills/architecture-characteristic-writer/assets/worksheet-template.md +40 -0
- package/template/.claude/skills/architecture-characteristic-writer/references/characteristics-catalog.md +106 -0
- package/template/.claude/skills/code-review/SKILL.md +54 -0
- package/template/.claude/skills/code-review/references/adversarial-review.md +223 -0
- package/template/.claude/skills/code-review/references/checklists/api.md +52 -0
- package/template/.claude/skills/code-review/references/checklists/base.md +100 -0
- package/template/.claude/skills/code-review/references/checklists/web-app.md +54 -0
- package/template/.claude/skills/context-map/SKILL.md +80 -0
- package/template/.claude/skills/context-map/example.cml +106 -0
- package/template/.claude/skills/context-map/reference/Aggregate/Aggregate.md +1 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/Bounded Context.md +41 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/businessModel.md +6 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/domainVisionStatement.md +3 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/evolution.md +6 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/implementationTechnology.md +1 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/implements.md +1 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/knowledgeLevel.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/realizes.md +9 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/refines.md +10 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/responsibilities.md +26 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/type.md +23 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Anticorruption Layer.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Bounded Context Relationship.md +13 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Conformist.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Customer-Supplier (C-S).md +23 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Open Host Service.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Partnership (P).md +14 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Published Language.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Shared Kernel (SK).md +13 -0
- package/template/.claude/skills/context-map/reference/Context Map.md +63 -0
- package/template/.claude/skills/context-map/reference/Domain/Domain.md +30 -0
- package/template/.claude/skills/context-map/reference/Domain/supports.md +34 -0
- package/template/.claude/skills/context-map/reference/Domain/type.md +3 -0
- package/template/.claude/skills/context-map/reference/Semantic Rules.md +33 -0
- package/template/.claude/skills/hallmark/SKILL.md +552 -0
- package/template/.claude/skills/hallmark/references/anti-patterns.md +412 -0
- package/template/.claude/skills/hallmark/references/assets.md +406 -0
- package/template/.claude/skills/hallmark/references/color.md +95 -0
- package/template/.claude/skills/hallmark/references/component-cookbook.md +256 -0
- package/template/.claude/skills/hallmark/references/components/c1-outlined-chip.md +12 -0
- package/template/.claude/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -0
- package/template/.claude/skills/hallmark/references/components/c3-typographic-link.md +8 -0
- package/template/.claude/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -0
- package/template/.claude/skills/hallmark/references/components/f1-bento-grid.md +20 -0
- package/template/.claude/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -0
- package/template/.claude/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -0
- package/template/.claude/skills/hallmark/references/components/f4-step-sequence.md +11 -0
- package/template/.claude/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -0
- package/template/.claude/skills/hallmark/references/components/f6-product-card-grid.md +41 -0
- package/template/.claude/skills/hallmark/references/components/ft1-mast-headed.md +13 -0
- package/template/.claude/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -0
- package/template/.claude/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -0
- package/template/.claude/skills/hallmark/references/components/ft4-dense-typographic.md +10 -0
- package/template/.claude/skills/hallmark/references/components/ft5-statement.md +21 -0
- package/template/.claude/skills/hallmark/references/components/ft6-letter-close.md +19 -0
- package/template/.claude/skills/hallmark/references/components/ft7-newsletter-first.md +27 -0
- package/template/.claude/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -0
- package/template/.claude/skills/hallmark/references/components/h1-marquee.md +15 -0
- package/template/.claude/skills/hallmark/references/components/h2-split-diptych.md +15 -0
- package/template/.claude/skills/hallmark/references/components/h3-quote-led.md +11 -0
- package/template/.claude/skills/hallmark/references/components/h4-stat-led.md +14 -0
- package/template/.claude/skills/hallmark/references/components/h5-letter-hero.md +11 -0
- package/template/.claude/skills/hallmark/references/components/h6-photographic-fold.md +16 -0
- package/template/.claude/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -0
- package/template/.claude/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -0
- package/template/.claude/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -0
- package/template/.claude/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -0
- package/template/.claude/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -0
- package/template/.claude/skills/hallmark/references/components/n2-floating-chip.md +14 -0
- package/template/.claude/skills/hallmark/references/components/n3-side-rail.md +14 -0
- package/template/.claude/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -0
- package/template/.claude/skills/hallmark/references/components/n5-floating-pill.md +28 -0
- package/template/.claude/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -0
- package/template/.claude/skills/hallmark/references/components/n7-brutal-slab.md +22 -0
- package/template/.claude/skills/hallmark/references/components/n8-terminal-command.md +21 -0
- package/template/.claude/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -0
- package/template/.claude/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -0
- package/template/.claude/skills/hallmark/references/components/s2-hanging.md +13 -0
- package/template/.claude/skills/hallmark/references/components/s3-sticky-pinned.md +19 -0
- package/template/.claude/skills/hallmark/references/components/s4-inline-no-break.md +11 -0
- package/template/.claude/skills/hallmark/references/components/s5-bottom-anchored.md +13 -0
- package/template/.claude/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -0
- package/template/.claude/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -0
- package/template/.claude/skills/hallmark/references/components/t3-single-huge-quote.md +11 -0
- package/template/.claude/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -0
- package/template/.claude/skills/hallmark/references/contract.md +24 -0
- package/template/.claude/skills/hallmark/references/copy.md +182 -0
- package/template/.claude/skills/hallmark/references/custom-craft.md +626 -0
- package/template/.claude/skills/hallmark/references/custom-theme.md +329 -0
- package/template/.claude/skills/hallmark/references/design-md.md +116 -0
- package/template/.claude/skills/hallmark/references/export-formats.md +328 -0
- package/template/.claude/skills/hallmark/references/floating-nav.md +89 -0
- package/template/.claude/skills/hallmark/references/genres/atmospheric.md +65 -0
- package/template/.claude/skills/hallmark/references/genres/editorial.md +70 -0
- package/template/.claude/skills/hallmark/references/genres/modern-minimal.md +67 -0
- package/template/.claude/skills/hallmark/references/genres/playful.md +65 -0
- package/template/.claude/skills/hallmark/references/hero-enrichment.md +474 -0
- package/template/.claude/skills/hallmark/references/imagery-kit.md +170 -0
- package/template/.claude/skills/hallmark/references/interaction-and-states.md +207 -0
- package/template/.claude/skills/hallmark/references/layout-and-space.md +111 -0
- package/template/.claude/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -0
- package/template/.claude/skills/hallmark/references/macrostructures/02-long-document.md +34 -0
- package/template/.claude/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -0
- package/template/.claude/skills/hallmark/references/macrostructures/04-stat-led.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/05-workbench.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -0
- package/template/.claude/skills/hallmark/references/macrostructures/07-manifesto.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/08-photographic.md +34 -0
- package/template/.claude/skills/hallmark/references/macrostructures/09-quote-led.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/10-specimen.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/11-catalogue.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/12-letter.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/13-index-first.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/15-split-studio.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/21-component-playground.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures.md +89 -0
- package/template/.claude/skills/hallmark/references/microinteractions.md +260 -0
- package/template/.claude/skills/hallmark/references/motion.md +109 -0
- package/template/.claude/skills/hallmark/references/preview-examples.md +49 -0
- package/template/.claude/skills/hallmark/references/responsive.md +138 -0
- package/template/.claude/skills/hallmark/references/slop-test.md +205 -0
- package/template/.claude/skills/hallmark/references/structure.md +164 -0
- package/template/.claude/skills/hallmark/references/study.md +511 -0
- package/template/.claude/skills/hallmark/references/typography.md +243 -0
- package/template/.claude/skills/hallmark/references/verbs/audit.md +25 -0
- package/template/.claude/skills/hallmark/references/verbs/redesign.md +269 -0
- package/template/.claude/skills/hallmark-explore/SKILL.md +137 -0
- package/template/.claude/skills/hallmark-explore/references/direction-matrix.md +124 -0
- package/template/.claude/skills/hallmark-explore/references/explorer-call.md +68 -0
- package/template/.claude/skills/hallmark-explore/scripts/build_compare.py +174 -0
- package/template/.claude/skills/hallmark-explore/scripts/scaffold_exploration.py +122 -0
- package/template/.claude/skills/hallmark-loop/SKILL.md +105 -0
- package/template/.claude/skills/hallmark-loop/references/auditor-call.md +60 -0
- package/template/.claude/skills/hallmark-loop/references/capture.md +78 -0
- package/template/.claude/skills/hallmark-loop/references/loop-control.md +79 -0
- package/template/.claude/skills/handoff/SKILL.md +15 -0
- package/template/.claude/skills/install.ps1 +42 -0
- package/template/.claude/skills/install.sh +54 -0
- package/template/.claude/skills/knowledge-crunching/SKILL.md +130 -0
- package/template/.claude/skills/knowledge-crunching/assets/context.template.md +59 -0
- package/template/.claude/skills/knowledge-crunching/references/crunching-dialogue.md +113 -0
- package/template/.claude/skills/research/SKILL.md +69 -0
- package/template/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.claude/skills/skill-creator/SKILL.md +149 -0
- package/template/.claude/skills/skill-creator/agents/analyzer.md +274 -0
- package/template/.claude/skills/skill-creator/agents/comparator.md +202 -0
- package/template/.claude/skills/skill-creator/agents/grader.md +223 -0
- package/template/.claude/skills/skill-creator/assets/eval_review.html +146 -0
- package/template/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/template/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/template/.claude/skills/skill-creator/references/benchmark-optimization-guide.md +86 -0
- package/template/.claude/skills/skill-creator/references/distribution-guide.md +79 -0
- package/template/.claude/skills/skill-creator/references/eval-infrastructure-guide.md +129 -0
- package/template/.claude/skills/skill-creator/references/eval-schemas.md +121 -0
- package/template/.claude/skills/skill-creator/references/mcp-skills-integration.md +71 -0
- package/template/.claude/skills/skill-creator/references/metadata-quality-criteria.md +94 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-hosting.md +104 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-overview.md +89 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-schema.md +93 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-sources.md +103 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-troubleshooting.md +76 -0
- package/template/.claude/skills/skill-creator/references/script-quality-criteria.md +106 -0
- package/template/.claude/skills/skill-creator/references/skill-anatomy-and-requirements.md +77 -0
- package/template/.claude/skills/skill-creator/references/skill-creation-workflow.md +151 -0
- package/template/.claude/skills/skill-creator/references/skill-design-patterns.md +75 -0
- package/template/.claude/skills/skill-creator/references/skillmark-benchmark-criteria.md +102 -0
- package/template/.claude/skills/skill-creator/references/structure-organization-criteria.md +114 -0
- package/template/.claude/skills/skill-creator/references/testing-and-iteration.md +78 -0
- package/template/.claude/skills/skill-creator/references/token-efficiency-criteria.md +74 -0
- package/template/.claude/skills/skill-creator/references/troubleshooting-guide.md +81 -0
- package/template/.claude/skills/skill-creator/references/validation-checklist.md +83 -0
- package/template/.claude/skills/skill-creator/references/writing-effective-instructions.md +88 -0
- package/template/.claude/skills/skill-creator/references/yaml-frontmatter-reference.md +92 -0
- package/template/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/template/.claude/skills/skill-creator/scripts/encoding_utils.py +36 -0
- package/template/.claude/skills/skill-creator/scripts/generate_report.py +326 -0
- package/template/.claude/skills/skill-creator/scripts/improve_description.py +248 -0
- package/template/.claude/skills/skill-creator/scripts/init_skill.py +360 -0
- package/template/.claude/skills/skill-creator/scripts/package_skill.py +143 -0
- package/template/.claude/skills/skill-creator/scripts/quick_validate.py +110 -0
- package/template/.claude/skills/skill-creator/scripts/run_eval.py +310 -0
- package/template/.claude/skills/skill-creator/scripts/run_loop.py +332 -0
- package/template/.claude/skills/skill-creator/scripts/utils.py +47 -0
- package/template/.claude/skills/tdd/SKILL.md +142 -0
- package/template/.claude/skills/tdd/assets/Directory.Build.props +26 -0
- package/template/.claude/skills/tdd/deep-modules.md +15 -0
- package/template/.claude/skills/tdd/dotnet-build-config.md +21 -0
- package/template/.claude/skills/tdd/interface-design.md +31 -0
- package/template/.claude/skills/tdd/mocking.md +59 -0
- package/template/.claude/skills/tdd/refactoring.md +10 -0
- package/template/.claude/skills/tdd/test-per-layer.md +69 -0
- package/template/.claude/skills/tdd/tests.md +61 -0
- package/template/.claude/statusline.cjs +0 -0
package/src/muster.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The muster — the interactive picker. Uses @clack/prompts (ESM-only), loaded
|
|
4
|
+
// via dynamic import() for CommonJS interop. `core` is always re-added after
|
|
5
|
+
// selection (clack has no per-option lock), enforcing the locked-on invariant.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {object} manifest
|
|
9
|
+
* @param {{musketeers:string[]}} prev current lockfile company
|
|
10
|
+
* @returns {Promise<?string[]>} chosen ids (incl. core), or null if cancelled
|
|
11
|
+
*/
|
|
12
|
+
async function pick(manifest, prev) {
|
|
13
|
+
const clack = await import('@clack/prompts');
|
|
14
|
+
const entries = Object.entries(manifest.musketeers);
|
|
15
|
+
const installed = new Set(prev.musketeers);
|
|
16
|
+
const hasLock = prev.musketeers.length > 0;
|
|
17
|
+
|
|
18
|
+
// Pre-checked: locked always; else current install (if any) else defaultChecked.
|
|
19
|
+
const initialValues = entries
|
|
20
|
+
.filter(([id, m]) => m.locked || (hasLock ? installed.has(id) : m.defaultChecked))
|
|
21
|
+
.map(([id]) => id);
|
|
22
|
+
|
|
23
|
+
const options = entries.map(([id, m]) => ({
|
|
24
|
+
value: id,
|
|
25
|
+
label: m.locked ? `${m.label} (core — always included)` : m.label,
|
|
26
|
+
hint: m.description,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
clack.intro('musketeer — muster');
|
|
30
|
+
const selected = await clack.multiselect({
|
|
31
|
+
message: 'Select your company (core is always installed):',
|
|
32
|
+
options,
|
|
33
|
+
initialValues,
|
|
34
|
+
required: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (clack.isCancel(selected)) {
|
|
38
|
+
clack.cancel('Cancelled.');
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const chosen = new Set(Array.isArray(selected) ? selected : []);
|
|
43
|
+
chosen.add('core'); // enforce locked-on
|
|
44
|
+
clack.outro(`Company: ${[...chosen].join(', ')}`);
|
|
45
|
+
return [...chosen];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { pick };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// Map of secret prereq name → environment variable that satisfies it.
|
|
8
|
+
const SECRET_ENV = { 'anthropic-api-key': 'ANTHROPIC_API_KEY' };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default command runner. Captures stdout+stderr (java prints version to stderr)
|
|
12
|
+
* and never throws. Tests inject their own `ctx.run` to avoid touching the host.
|
|
13
|
+
* @param {string} cmd
|
|
14
|
+
* @returns {{code:number, stdout:string, stderr:string}}
|
|
15
|
+
*/
|
|
16
|
+
function defaultRun(cmd) {
|
|
17
|
+
const r = spawnSync(cmd, { shell: true, encoding: 'utf8', windowsHide: true });
|
|
18
|
+
return {
|
|
19
|
+
code: typeof r.status === 'number' ? r.status : 1,
|
|
20
|
+
stdout: r.stdout || '',
|
|
21
|
+
stderr: r.stderr || '',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Extract a numeric version tuple from arbitrary command output. */
|
|
26
|
+
function parseVersion(text) {
|
|
27
|
+
const m = String(text).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
28
|
+
if (!m) return null;
|
|
29
|
+
return [Number(m[1] || 0), Number(m[2] || 0), Number(m[3] || 0)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** found >= min ? (min may be "18" or "3.8") */
|
|
33
|
+
function versionGte(found, min) {
|
|
34
|
+
if (!found) return false;
|
|
35
|
+
const want = parseVersion(min) || [0, 0, 0];
|
|
36
|
+
for (let i = 0; i < 3; i++) {
|
|
37
|
+
if (found[i] > want[i]) return true;
|
|
38
|
+
if (found[i] < want[i]) return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detect whether a single prereq is satisfied. Detection NEVER mutates the host.
|
|
45
|
+
* @param {string} name prereq id
|
|
46
|
+
* @param {object} prereq manifest prereq entry
|
|
47
|
+
* @param {object} ctx { run, platform, projectDir }
|
|
48
|
+
* @returns {{name:string, present:boolean, version:?string, reason:string}}
|
|
49
|
+
*/
|
|
50
|
+
function detectPrereq(name, prereq, ctx) {
|
|
51
|
+
const run = ctx.run || defaultRun;
|
|
52
|
+
|
|
53
|
+
switch (prereq.kind) {
|
|
54
|
+
case 'secret': {
|
|
55
|
+
const envVar = SECRET_ENV[name];
|
|
56
|
+
const present = !!(envVar && process.env[envVar]);
|
|
57
|
+
return { name, present, version: null, reason: present ? `${envVar} set` : `${envVar} unset` };
|
|
58
|
+
}
|
|
59
|
+
case 'venv': {
|
|
60
|
+
const dir = path.join(ctx.projectDir, '.claude', 'skills', '.venv');
|
|
61
|
+
const present = fs.existsSync(dir);
|
|
62
|
+
return { name, present, version: null, reason: present ? 'venv exists' : 'venv missing' };
|
|
63
|
+
}
|
|
64
|
+
case 'mcp': {
|
|
65
|
+
const r = run('claude mcp list');
|
|
66
|
+
const present = r.code === 0 && /playwright/i.test(r.stdout + r.stderr);
|
|
67
|
+
return { name, present, version: null, reason: present ? 'mcp registered' : 'mcp not registered' };
|
|
68
|
+
}
|
|
69
|
+
case 'note':
|
|
70
|
+
// Nothing to probe directly; satisfied iff its `needs` are (handled by caller).
|
|
71
|
+
return { name, present: false, version: null, reason: 'note (self-bootstrap)' };
|
|
72
|
+
default: {
|
|
73
|
+
// package
|
|
74
|
+
if (!prereq.detect) return { name, present: false, version: null, reason: 'no detect command' };
|
|
75
|
+
let r = run(prereq.detect);
|
|
76
|
+
if (r.code !== 0 && name === 'python') r = run('python3 --version'); // unix fallback
|
|
77
|
+
const out = r.stdout + r.stderr;
|
|
78
|
+
const ver = parseVersion(out);
|
|
79
|
+
if (r.code !== 0) return { name, present: false, version: null, reason: 'not found' };
|
|
80
|
+
if (prereq.minVersion && !versionGte(ver, prereq.minVersion)) {
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
present: false,
|
|
84
|
+
version: ver ? ver.join('.') : null,
|
|
85
|
+
reason: `version ${ver ? ver.join('.') : '?'} < required ${prereq.minVersion}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { name, present: true, version: ver ? ver.join('.') : null, reason: 'present' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { detectPrereq, parseVersion, versionGte, defaultRun, SECRET_ENV };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolveInstallCommand } = require('./os-package-manager');
|
|
4
|
+
const { SECRET_ENV } = require('./detect');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Print a manual-install fallback for a prereq that could not be auto-installed.
|
|
8
|
+
* @param {string} name
|
|
9
|
+
* @param {object} prereq
|
|
10
|
+
* @param {object} ctx
|
|
11
|
+
*/
|
|
12
|
+
function printGuide(name, prereq, ctx) {
|
|
13
|
+
const log = ctx.log || console.log;
|
|
14
|
+
if (prereq.kind === 'secret') return printSecretGuide(name, ctx);
|
|
15
|
+
|
|
16
|
+
const { command } = resolveInstallCommand(prereq, ctx);
|
|
17
|
+
log(` ⚠ Could not auto-install "${name}". Install it manually:`);
|
|
18
|
+
if (command) log(` ${command}`);
|
|
19
|
+
else log(` (no package-manager command available — see INSTALLATION.md for "${name}")`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Secrets are never auto-installed: tell the user how to set the env var.
|
|
24
|
+
*/
|
|
25
|
+
function printSecretGuide(name, ctx) {
|
|
26
|
+
const log = ctx.log || console.log;
|
|
27
|
+
const envVar = SECRET_ENV[name] || name;
|
|
28
|
+
log(` ℹ ${name}: export ${envVar} when you use the scripts that need it (e.g. skill-creator improve/loop).`);
|
|
29
|
+
log(' musketeer never writes secrets to the repo.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { printGuide, printSecretGuide };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { detectPrereq } = require('./detect');
|
|
4
|
+
const opm = require('./os-package-manager');
|
|
5
|
+
const { ensureVenv } = require('./venv');
|
|
6
|
+
const { ensurePlaywrightMcp } = require('./mcp');
|
|
7
|
+
const guide = require('./guide');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Order prereq names so that `needs` (deps) come before their dependents
|
|
11
|
+
* (java before cm-cli, python before venv-anthropic). Stable topo sort.
|
|
12
|
+
*/
|
|
13
|
+
function orderByNeeds(names, manifest) {
|
|
14
|
+
const set = new Set(names);
|
|
15
|
+
const out = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
function visit(n) {
|
|
18
|
+
if (seen.has(n)) return;
|
|
19
|
+
seen.add(n);
|
|
20
|
+
const p = manifest.prereqs[n];
|
|
21
|
+
if (p && Array.isArray(p.needs)) {
|
|
22
|
+
for (const need of p.needs) if (set.has(need)) visit(need);
|
|
23
|
+
}
|
|
24
|
+
out.push(n);
|
|
25
|
+
}
|
|
26
|
+
for (const n of names) visit(n);
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function consentSudo(name, ctx) {
|
|
31
|
+
if (!ctx.isInteractive) return true; // CI: attempt (announced); shell sudo may still prompt
|
|
32
|
+
if (typeof ctx.prompt !== 'function') return true;
|
|
33
|
+
return !!(await ctx.prompt(`Installing ${name} requires sudo. Proceed?`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function ensureOne(name, prereq, ctx) {
|
|
37
|
+
const log = ctx.log || (() => {});
|
|
38
|
+
const det = detectPrereq(name, prereq, ctx);
|
|
39
|
+
|
|
40
|
+
switch (prereq.kind) {
|
|
41
|
+
case 'secret':
|
|
42
|
+
if (!det.present) guide.printGuide(name, prereq, ctx);
|
|
43
|
+
return { name, ok: det.present, status: det.present ? 'present' : 'guide (secret)' };
|
|
44
|
+
|
|
45
|
+
case 'note':
|
|
46
|
+
log(` • ${name}: self-bootstraps from Maven on first use (needs: ${(prereq.needs || []).join(', ') || 'none'})`);
|
|
47
|
+
return { name, ok: true, status: 'note' };
|
|
48
|
+
|
|
49
|
+
case 'venv': {
|
|
50
|
+
if (det.present) return { name, ok: true, status: 'present' };
|
|
51
|
+
const r = ensureVenv(ctx);
|
|
52
|
+
if (r.skipped) return { name, ok: false, status: 'skipped' };
|
|
53
|
+
const ok = r.code === 0;
|
|
54
|
+
if (!ok) guide.printGuide(name, prereq, ctx);
|
|
55
|
+
return { name, ok, status: ok ? 'installed' : 'failed → guide' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case 'mcp': {
|
|
59
|
+
if (det.present) return { name, ok: true, status: 'present' };
|
|
60
|
+
const r = ensurePlaywrightMcp(ctx);
|
|
61
|
+
if (r.skipped) return { name, ok: false, status: 'skipped' };
|
|
62
|
+
const ok = r.code === 0;
|
|
63
|
+
if (!ok) guide.printGuide(name, prereq, ctx);
|
|
64
|
+
return { name, ok, status: ok ? 'installed' : 'failed → guide' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
default: {
|
|
68
|
+
// package
|
|
69
|
+
if (det.present) return { name, ok: true, status: `present (${det.version || 'ok'})` };
|
|
70
|
+
const { command, needsSudo } = opm.resolveInstallCommand(prereq, ctx);
|
|
71
|
+
if (!command) {
|
|
72
|
+
guide.printGuide(name, prereq, ctx);
|
|
73
|
+
return { name, ok: false, status: 'no installer → guide' };
|
|
74
|
+
}
|
|
75
|
+
if (needsSudo && !(await consentSudo(name, ctx))) {
|
|
76
|
+
guide.printGuide(name, prereq, ctx);
|
|
77
|
+
return { name, ok: false, status: 'sudo refused → guide' };
|
|
78
|
+
}
|
|
79
|
+
const ins = opm.installPackage(name, command, ctx);
|
|
80
|
+
if (ins.skipped) return { name, ok: false, status: 'skipped' };
|
|
81
|
+
const re = detectPrereq(name, prereq, ctx);
|
|
82
|
+
if (!re.present) guide.printGuide(name, prereq, ctx);
|
|
83
|
+
return { name, ok: re.present, status: re.present ? 'installed' : 'install failed → guide' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Detect + auto-install every prereq required by the desired company.
|
|
90
|
+
* Partial-success tolerant: a failure prints a guide and never aborts reconcile.
|
|
91
|
+
* @param {string[]} prereqNames
|
|
92
|
+
* @param {object} manifest
|
|
93
|
+
* @param {object} ctx { projectDir, templateDir, platform, run, prompt, isInteractive, log }
|
|
94
|
+
* @returns {Promise<Array<{name,ok,status}>>}
|
|
95
|
+
*/
|
|
96
|
+
async function ensure(prereqNames, manifest, ctx) {
|
|
97
|
+
const log = ctx.log || (() => {});
|
|
98
|
+
const unique = [...new Set(prereqNames)].filter((n) => manifest.prereqs[n]);
|
|
99
|
+
if (unique.length === 0) return [];
|
|
100
|
+
|
|
101
|
+
const ordered = orderByNeeds(unique, manifest);
|
|
102
|
+
log('Provisioning prerequisites...');
|
|
103
|
+
const summary = [];
|
|
104
|
+
for (const name of ordered) {
|
|
105
|
+
summary.push(await ensureOne(name, manifest.prereqs[name], ctx));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
log('\nPrereq summary:');
|
|
109
|
+
for (const s of summary) log(` ${s.ok ? '✓' : '•'} ${s.name}: ${s.status}`);
|
|
110
|
+
return summary;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { ensure, orderByNeeds, ensureOne };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { defaultRun } = require('./detect');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register the Playwright MCP via the claude CLI (which owns MCP config) and
|
|
7
|
+
* install the Chromium browser. Best-effort; failures degrade to a guide.
|
|
8
|
+
* @param {object} ctx { run, log }
|
|
9
|
+
* @returns {{ran:boolean, code:number, skipped?:boolean}}
|
|
10
|
+
*/
|
|
11
|
+
function ensurePlaywrightMcp(ctx) {
|
|
12
|
+
const run = ctx.run || defaultRun;
|
|
13
|
+
const log = ctx.log || (() => {});
|
|
14
|
+
|
|
15
|
+
if (process.env.MUSKETEER_SKIP_INSTALL) {
|
|
16
|
+
log(' [skip-install] would register Playwright MCP + install Chromium');
|
|
17
|
+
return { ran: false, code: 0, skipped: true };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
log(' → registering Playwright MCP (claude mcp add) + installing Chromium (heavy download)');
|
|
21
|
+
const add = run('claude mcp add playwright -- npx @playwright/mcp@latest');
|
|
22
|
+
const chromium = run('npx playwright install chromium');
|
|
23
|
+
return { ran: true, code: add.code === 0 && chromium.code === 0 ? 0 : 1 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { ensurePlaywrightMcp };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { defaultRun } = require('./detect');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalize process.platform (or ctx override) to a manifest install key.
|
|
7
|
+
* @returns {"win"|"mac"|"linux"}
|
|
8
|
+
*/
|
|
9
|
+
function osKey(ctx) {
|
|
10
|
+
const p = ctx.platform || process.platform;
|
|
11
|
+
if (p === 'win32') return 'win';
|
|
12
|
+
if (p === 'darwin') return 'mac';
|
|
13
|
+
return 'linux';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect which Linux package manager is available (apt/dnf/pacman).
|
|
18
|
+
* Mockable via ctx.run; overridable via ctx.linuxPm.
|
|
19
|
+
* @returns {?string}
|
|
20
|
+
*/
|
|
21
|
+
function detectLinuxPm(ctx) {
|
|
22
|
+
if (ctx.linuxPm) return ctx.linuxPm;
|
|
23
|
+
const run = ctx.run || defaultRun;
|
|
24
|
+
for (const [pm, probe] of [
|
|
25
|
+
['apt', 'apt-get'],
|
|
26
|
+
['dnf', 'dnf'],
|
|
27
|
+
['pacman', 'pacman'],
|
|
28
|
+
]) {
|
|
29
|
+
const r = run(`command -v ${probe}`);
|
|
30
|
+
if (r.code === 0 && (r.stdout || '').trim()) return pm;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the OS-specific install command string for a prereq, or null when
|
|
37
|
+
* none applies (note/secret/venv/mcp, or no package manager on Linux).
|
|
38
|
+
* @returns {{command:?string, needsSudo:boolean, pm:?string}}
|
|
39
|
+
*/
|
|
40
|
+
function resolveInstallCommand(prereq, ctx) {
|
|
41
|
+
if (!prereq.install) return { command: null, needsSudo: false, pm: null };
|
|
42
|
+
const key = osKey(ctx);
|
|
43
|
+
const spec = prereq.install[key];
|
|
44
|
+
if (spec == null) return { command: null, needsSudo: false, pm: null };
|
|
45
|
+
|
|
46
|
+
if (typeof spec === 'string') {
|
|
47
|
+
return { command: spec, needsSudo: /\bsudo\b/.test(spec), pm: null };
|
|
48
|
+
}
|
|
49
|
+
// Linux object keyed by package manager.
|
|
50
|
+
const pm = detectLinuxPm(ctx);
|
|
51
|
+
if (!pm || !spec[pm]) return { command: null, needsSudo: false, pm };
|
|
52
|
+
return { command: spec[pm], needsSudo: /\bsudo\b/.test(spec[pm]), pm };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run an install command, announced. Honors MUSKETEER_SKIP_INSTALL (dry-run).
|
|
57
|
+
* @returns {{ran:boolean, code:number, skipped?:boolean}}
|
|
58
|
+
*/
|
|
59
|
+
function installPackage(name, command, ctx) {
|
|
60
|
+
const run = ctx.run || defaultRun;
|
|
61
|
+
const log = ctx.log || (() => {});
|
|
62
|
+
if (process.env.MUSKETEER_SKIP_INSTALL) {
|
|
63
|
+
log(` [skip-install] would run: ${command}`);
|
|
64
|
+
return { ran: false, code: 0, skipped: true };
|
|
65
|
+
}
|
|
66
|
+
log(` → installing ${name}: ${command}`);
|
|
67
|
+
const r = run(command);
|
|
68
|
+
return { ran: true, code: r.code };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { osKey, detectLinuxPm, resolveInstallCommand, installPackage };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { defaultRun } = require('./detect');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create the skills venv + install `anthropic` by invoking the shipped
|
|
9
|
+
* orchestrator (install.ps1 on Windows, install.sh elsewhere). Prefers the copy
|
|
10
|
+
* inside the project's .claude/skills; falls back to the package template.
|
|
11
|
+
* @param {object} ctx { run, log, platform, projectDir, templateDir, isInteractive }
|
|
12
|
+
* @returns {{ran:boolean, code:number, skipped?:boolean, reason?:string}}
|
|
13
|
+
*/
|
|
14
|
+
function ensureVenv(ctx) {
|
|
15
|
+
const run = ctx.run || defaultRun;
|
|
16
|
+
const log = ctx.log || (() => {});
|
|
17
|
+
const isWin = (ctx.platform || process.platform) === 'win32';
|
|
18
|
+
const script = isWin ? 'install.ps1' : 'install.sh';
|
|
19
|
+
|
|
20
|
+
let scriptPath = path.join(ctx.projectDir, '.claude', 'skills', script);
|
|
21
|
+
if (!fs.existsSync(scriptPath) && ctx.templateDir) {
|
|
22
|
+
const alt = path.join(ctx.templateDir, 'skills', script);
|
|
23
|
+
if (fs.existsSync(alt)) scriptPath = alt;
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(scriptPath)) {
|
|
26
|
+
log(` venv orchestrator not found (${script}); skipping`);
|
|
27
|
+
return { ran: false, code: 1, reason: 'orchestrator missing' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (process.env.MUSKETEER_SKIP_INSTALL) {
|
|
31
|
+
log(` [skip-install] would run ${script}`);
|
|
32
|
+
return { ran: false, code: 0, skipped: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const yes = ctx.isInteractive ? '' : ' --yes';
|
|
36
|
+
const cmd = isWin
|
|
37
|
+
? `powershell -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}"${yes}`
|
|
38
|
+
: `bash "${scriptPath}"${yes}`;
|
|
39
|
+
log(` → creating skills venv via ${script}`);
|
|
40
|
+
const r = run(cmd);
|
|
41
|
+
return { ran: true, code: r.code };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { ensureVenv };
|
package/src/reconcile.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { loadManifest, templateClaudeDir } = require('./manifest-loader');
|
|
4
|
+
const { resolveClosure, CORE_ID } = require('./resolver');
|
|
5
|
+
const { readLockfile, writeLockfile } = require('./lockfile');
|
|
6
|
+
const copier = require('./copier');
|
|
7
|
+
const settingsMerger = require('./settings-merger');
|
|
8
|
+
const provisioner = require('./provisioner');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build the default dependency set. Tests override any of these (manifest,
|
|
12
|
+
* templateDir, muster, provisioner, log, now) to stay hermetic.
|
|
13
|
+
*/
|
|
14
|
+
function defaultDeps() {
|
|
15
|
+
return {
|
|
16
|
+
manifest: loadManifest(),
|
|
17
|
+
templateDir: templateClaudeDir(),
|
|
18
|
+
settingsMerger,
|
|
19
|
+
provisioner,
|
|
20
|
+
muster: require('./muster'),
|
|
21
|
+
now: () => new Date().toISOString(),
|
|
22
|
+
log: (m) => process.stdout.write(m + '\n'),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The ONE idempotent command: resolve desired company → diff vs lockfile →
|
|
28
|
+
* copy/remove files → regenerate settings.json → provision prereqs → write lockfile.
|
|
29
|
+
* @param {{desiredIds:?string[], interactive:boolean, projectDir?:string}} opts
|
|
30
|
+
* @param {object} [overrides] dependency injection for tests
|
|
31
|
+
*/
|
|
32
|
+
async function run(opts, overrides = {}) {
|
|
33
|
+
const { desiredIds, interactive } = opts;
|
|
34
|
+
const projectDir = opts.projectDir || process.cwd();
|
|
35
|
+
const d = { ...defaultDeps(), ...overrides };
|
|
36
|
+
const manifest = d.manifest;
|
|
37
|
+
|
|
38
|
+
const prev = readLockfile(projectDir);
|
|
39
|
+
|
|
40
|
+
let chosen = desiredIds;
|
|
41
|
+
if (interactive) {
|
|
42
|
+
chosen = await d.muster.pick(manifest, prev, d);
|
|
43
|
+
if (chosen === null) {
|
|
44
|
+
d.log('Cancelled — no changes made.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const desired = resolveClosure(manifest, chosen || [], d.templateDir);
|
|
50
|
+
const plan = copier.planFiles(prev.files, desired.files);
|
|
51
|
+
|
|
52
|
+
d.log(`Company: ${desired.ids.join(', ')}`);
|
|
53
|
+
d.log(`Files: ${plan.copy.length} managed, ${plan.remove.length} to remove.`);
|
|
54
|
+
|
|
55
|
+
copier.apply(plan, d.templateDir, projectDir);
|
|
56
|
+
d.settingsMerger.generate(desired, projectDir);
|
|
57
|
+
|
|
58
|
+
await d.provisioner.ensure(desired.prereqs, manifest, {
|
|
59
|
+
projectDir,
|
|
60
|
+
templateDir: d.templateDir,
|
|
61
|
+
isInteractive: !!interactive,
|
|
62
|
+
log: d.log,
|
|
63
|
+
prompt: d.prompt,
|
|
64
|
+
run: d.run,
|
|
65
|
+
platform: d.platform,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
writeLockfile(projectDir, {
|
|
69
|
+
musketeers: desired.ids,
|
|
70
|
+
files: desired.files,
|
|
71
|
+
updatedAt: d.now(),
|
|
72
|
+
});
|
|
73
|
+
d.log('Done. Your .claude/ company is reconciled.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Print current company + all available musketeers (no UI).
|
|
78
|
+
*/
|
|
79
|
+
async function list(opts = {}, overrides = {}) {
|
|
80
|
+
const projectDir = opts.projectDir || process.cwd();
|
|
81
|
+
const d = { ...defaultDeps(), ...overrides };
|
|
82
|
+
const prev = readLockfile(projectDir);
|
|
83
|
+
const company = new Set(prev.musketeers.length ? prev.musketeers : []);
|
|
84
|
+
|
|
85
|
+
d.log(`Current company: ${prev.musketeers.length ? prev.musketeers.join(', ') : '(none installed yet)'}`);
|
|
86
|
+
d.log('\nAvailable musketeers:');
|
|
87
|
+
for (const [id, m] of Object.entries(d.manifest.musketeers)) {
|
|
88
|
+
const mark = company.has(id) || id === CORE_ID ? '[x]' : '[ ]';
|
|
89
|
+
const lock = m.locked ? ' (locked)' : m.defaultChecked ? '' : ' (opt-in)';
|
|
90
|
+
d.log(` ${mark} ${id}${lock} — ${m.description}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { run, list, defaultDeps };
|
package/src/resolver.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const CORE_ID = 'core';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recursively list every file under `root`, returned as POSIX-style paths
|
|
10
|
+
* relative to `root` (forward slashes, stable sorted).
|
|
11
|
+
* @param {string} root
|
|
12
|
+
* @returns {string[]}
|
|
13
|
+
*/
|
|
14
|
+
function listFiles(root) {
|
|
15
|
+
const out = [];
|
|
16
|
+
function walk(dir) {
|
|
17
|
+
let entries;
|
|
18
|
+
try {
|
|
19
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
20
|
+
} catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const e of entries) {
|
|
24
|
+
const abs = path.join(dir, e.name);
|
|
25
|
+
if (e.isDirectory()) walk(abs);
|
|
26
|
+
else if (e.isFile()) out.push(path.relative(root, abs).split(path.sep).join('/'));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
walk(root);
|
|
30
|
+
return out.sort();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Match one glob pattern against a list of relative POSIX paths.
|
|
35
|
+
* Supported forms (KISS — no full glob engine needed):
|
|
36
|
+
* "a/b/**" → every file at or below a/b
|
|
37
|
+
* "a/b/*" → direct file children of a/b
|
|
38
|
+
* "a/b.js" → exact file
|
|
39
|
+
* @param {string} pattern
|
|
40
|
+
* @param {string[]} files
|
|
41
|
+
* @returns {string[]} matched files
|
|
42
|
+
*/
|
|
43
|
+
function matchGlob(pattern, files) {
|
|
44
|
+
const p = pattern.split(path.sep).join('/');
|
|
45
|
+
if (p.endsWith('/**')) {
|
|
46
|
+
const prefix = p.slice(0, -3); // drop "/**"
|
|
47
|
+
return files.filter((f) => f === prefix || f.startsWith(prefix + '/'));
|
|
48
|
+
}
|
|
49
|
+
if (p.endsWith('/*')) {
|
|
50
|
+
const prefix = p.slice(0, -2);
|
|
51
|
+
return files.filter((f) => {
|
|
52
|
+
if (!f.startsWith(prefix + '/')) return false;
|
|
53
|
+
return !f.slice(prefix.length + 1).includes('/');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return files.filter((f) => f === p);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Expand a musketeer's `files` globs to concrete relative paths.
|
|
61
|
+
* @param {object} musketeer
|
|
62
|
+
* @param {string[]} templateFiles
|
|
63
|
+
* @returns {string[]}
|
|
64
|
+
*/
|
|
65
|
+
function expandFiles(musketeer, templateFiles) {
|
|
66
|
+
const set = new Set();
|
|
67
|
+
for (const glob of musketeer.files) {
|
|
68
|
+
for (const f of matchGlob(glob, templateFiles)) set.add(f);
|
|
69
|
+
}
|
|
70
|
+
return [...set];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the desired company into a concrete install plan.
|
|
75
|
+
* Desired = {core} ∪ requested, with intra-package `deps` expanded transitively.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} manifest validated manifest
|
|
78
|
+
* @param {string[]} requestedIds musketeer ids the user wants (core auto-added)
|
|
79
|
+
* @param {string} templateDir absolute path to template/.claude
|
|
80
|
+
* @returns {{ids:string[], files:string[], prereqs:string[], settings:object[]}}
|
|
81
|
+
*/
|
|
82
|
+
function resolveClosure(manifest, requestedIds, templateDir) {
|
|
83
|
+
const known = manifest.musketeers;
|
|
84
|
+
const wanted = new Set([CORE_ID]);
|
|
85
|
+
|
|
86
|
+
// Validate + seed
|
|
87
|
+
for (const id of requestedIds || []) {
|
|
88
|
+
if (!known[id]) {
|
|
89
|
+
const valid = Object.keys(known).join(', ');
|
|
90
|
+
throw new Error(`Unknown musketeer "${id}". Valid: ${valid}`);
|
|
91
|
+
}
|
|
92
|
+
wanted.add(id);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Transitive dep closure (empty for all v1 musketeers, kept for extensibility).
|
|
96
|
+
let changed = true;
|
|
97
|
+
while (changed) {
|
|
98
|
+
changed = false;
|
|
99
|
+
for (const id of [...wanted]) {
|
|
100
|
+
for (const dep of known[id].deps) {
|
|
101
|
+
if (!wanted.has(dep)) {
|
|
102
|
+
wanted.add(dep);
|
|
103
|
+
changed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const templateFiles = listFiles(templateDir);
|
|
110
|
+
const ids = [...wanted].sort((a, b) => (a === CORE_ID ? -1 : b === CORE_ID ? 1 : a.localeCompare(b)));
|
|
111
|
+
|
|
112
|
+
const files = new Set();
|
|
113
|
+
const prereqs = new Set();
|
|
114
|
+
const settings = [];
|
|
115
|
+
for (const id of ids) {
|
|
116
|
+
const m = known[id];
|
|
117
|
+
for (const f of expandFiles(m, templateFiles)) files.add(f);
|
|
118
|
+
for (const pr of m.prereqs) prereqs.add(pr);
|
|
119
|
+
for (const frag of m.settings) settings.push({ ...frag, musketeer: id });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
ids,
|
|
124
|
+
files: [...files].sort(),
|
|
125
|
+
prereqs: [...prereqs],
|
|
126
|
+
settings,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { resolveClosure, listFiles, matchGlob, expandFiles, CORE_ID };
|