@phuc1403/musketeer 0.9.0 → 0.10.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.
Files changed (163) hide show
  1. package/INSTALLATION.md +52 -52
  2. package/bin/musketeer.js +168 -168
  3. package/package.json +48 -48
  4. package/src/dotnet-scaffold-copier.js +79 -79
  5. package/src/provisioner/detect.js +93 -93
  6. package/src/self-update.js +77 -77
  7. package/template/.claude/agents/git-manager.md +18 -18
  8. package/template/.claude/agents/hallmark-auditor.md +78 -78
  9. package/template/.claude/agents/researcher.md +33 -33
  10. package/template/.claude/hooks/block-unsafe-adr-title.cjs +85 -85
  11. package/template/.claude/hooks/init-adr-dir.cjs +173 -173
  12. package/template/.claude/hooks/inject-adr-flags.cjs +94 -94
  13. package/template/.claude/hooks/lib/adr/command-scan.cjs +115 -115
  14. package/template/.claude/hooks/lib/characteristics/checker.cjs +357 -357
  15. package/template/.claude/hooks/lib/git-info-cache.cjs +191 -191
  16. package/template/.claude/hooks/sync-adr-toc.cjs +146 -146
  17. package/template/.claude/hooks/usage-quota-cache-refresh.cjs +166 -166
  18. package/template/.claude/hooks/validate-characteristics-hook.cjs +66 -66
  19. package/template/.claude/hooks/validate-cml-hook.js +145 -145
  20. package/template/.claude/skills/adr-writer/SKILL.md +48 -48
  21. package/template/.claude/skills/adr-writer/references/adr-example.md +35 -35
  22. package/template/.claude/skills/architecture-characteristic-writer/SKILL.md +215 -215
  23. package/template/.claude/skills/architecture-characteristic-writer/assets/worksheet-template.md +29 -29
  24. package/template/.claude/skills/architecture-characteristic-writer/references/characteristics-catalog.md +40 -40
  25. package/template/.claude/skills/architecture-characteristic-writer/scripts/ranking-table.cjs +171 -171
  26. package/template/.claude/skills/context-map/SKILL.md +80 -80
  27. package/template/.claude/skills/context-map/example.cml +106 -106
  28. package/template/.claude/skills/context-map/reference/Bounded Context/Bounded Context.md +40 -40
  29. package/template/.claude/skills/context-map/reference/Bounded Context/businessModel.md +5 -5
  30. package/template/.claude/skills/context-map/reference/Bounded Context/domainVisionStatement.md +2 -2
  31. package/template/.claude/skills/context-map/reference/Bounded Context/evolution.md +5 -5
  32. package/template/.claude/skills/context-map/reference/Bounded Context/implementationTechnology.md +1 -1
  33. package/template/.claude/skills/context-map/reference/Bounded Context/implements.md +1 -1
  34. package/template/.claude/skills/context-map/reference/Bounded Context/knowledgeLevel.md +4 -4
  35. package/template/.claude/skills/context-map/reference/Bounded Context/realizes.md +9 -9
  36. package/template/.claude/skills/context-map/reference/Bounded Context/refines.md +10 -10
  37. package/template/.claude/skills/context-map/reference/Bounded Context/responsibilities.md +26 -26
  38. package/template/.claude/skills/context-map/reference/Bounded Context/type.md +23 -23
  39. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Anticorruption Layer.md +5 -5
  40. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Bounded Context Relationship.md +12 -12
  41. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Conformist.md +5 -5
  42. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Customer-Supplier (C-S).md +22 -22
  43. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Open Host Service.md +4 -4
  44. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Partnership (P).md +13 -13
  45. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Published Language.md +4 -4
  46. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Shared Kernel (SK).md +12 -12
  47. package/template/.claude/skills/context-map/reference/Context Map.md +62 -62
  48. package/template/.claude/skills/context-map/reference/Domain/Domain.md +30 -30
  49. package/template/.claude/skills/context-map/reference/Domain/supports.md +33 -33
  50. package/template/.claude/skills/context-map/reference/Domain/type.md +3 -3
  51. package/template/.claude/skills/context-map/reference/Semantic Rules.md +32 -32
  52. package/template/.claude/skills/hallmark/SKILL.md +552 -552
  53. package/template/.claude/skills/hallmark/references/anti-patterns.md +412 -412
  54. package/template/.claude/skills/hallmark/references/assets.md +406 -406
  55. package/template/.claude/skills/hallmark/references/color.md +95 -95
  56. package/template/.claude/skills/hallmark/references/component-cookbook.md +256 -256
  57. package/template/.claude/skills/hallmark/references/components/c1-outlined-chip.md +12 -12
  58. package/template/.claude/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -16
  59. package/template/.claude/skills/hallmark/references/components/c3-typographic-link.md +8 -8
  60. package/template/.claude/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -16
  61. package/template/.claude/skills/hallmark/references/components/f1-bento-grid.md +20 -20
  62. package/template/.claude/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -20
  63. package/template/.claude/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -11
  64. package/template/.claude/skills/hallmark/references/components/f4-step-sequence.md +11 -11
  65. package/template/.claude/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -11
  66. package/template/.claude/skills/hallmark/references/components/f6-product-card-grid.md +41 -41
  67. package/template/.claude/skills/hallmark/references/components/ft1-mast-headed.md +13 -13
  68. package/template/.claude/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -10
  69. package/template/.claude/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -12
  70. package/template/.claude/skills/hallmark/references/components/ft4-dense-typographic.md +10 -10
  71. package/template/.claude/skills/hallmark/references/components/ft5-statement.md +21 -21
  72. package/template/.claude/skills/hallmark/references/components/ft6-letter-close.md +19 -19
  73. package/template/.claude/skills/hallmark/references/components/ft7-newsletter-first.md +27 -27
  74. package/template/.claude/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -25
  75. package/template/.claude/skills/hallmark/references/components/h1-marquee.md +15 -15
  76. package/template/.claude/skills/hallmark/references/components/h2-split-diptych.md +15 -15
  77. package/template/.claude/skills/hallmark/references/components/h3-quote-led.md +11 -11
  78. package/template/.claude/skills/hallmark/references/components/h4-stat-led.md +14 -14
  79. package/template/.claude/skills/hallmark/references/components/h5-letter-hero.md +11 -11
  80. package/template/.claude/skills/hallmark/references/components/h6-photographic-fold.md +16 -16
  81. package/template/.claude/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -27
  82. package/template/.claude/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -23
  83. package/template/.claude/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -27
  84. package/template/.claude/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -12
  85. package/template/.claude/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -19
  86. package/template/.claude/skills/hallmark/references/components/n2-floating-chip.md +14 -14
  87. package/template/.claude/skills/hallmark/references/components/n3-side-rail.md +14 -14
  88. package/template/.claude/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -9
  89. package/template/.claude/skills/hallmark/references/components/n5-floating-pill.md +28 -28
  90. package/template/.claude/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -24
  91. package/template/.claude/skills/hallmark/references/components/n7-brutal-slab.md +22 -22
  92. package/template/.claude/skills/hallmark/references/components/n8-terminal-command.md +21 -21
  93. package/template/.claude/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -17
  94. package/template/.claude/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -15
  95. package/template/.claude/skills/hallmark/references/components/s2-hanging.md +13 -13
  96. package/template/.claude/skills/hallmark/references/components/s3-sticky-pinned.md +19 -19
  97. package/template/.claude/skills/hallmark/references/components/s4-inline-no-break.md +11 -11
  98. package/template/.claude/skills/hallmark/references/components/s5-bottom-anchored.md +13 -13
  99. package/template/.claude/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -12
  100. package/template/.claude/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -19
  101. package/template/.claude/skills/hallmark/references/components/t3-single-huge-quote.md +11 -11
  102. package/template/.claude/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -14
  103. package/template/.claude/skills/hallmark/references/contract.md +24 -24
  104. package/template/.claude/skills/hallmark/references/copy.md +182 -182
  105. package/template/.claude/skills/hallmark/references/custom-craft.md +626 -626
  106. package/template/.claude/skills/hallmark/references/custom-theme.md +329 -329
  107. package/template/.claude/skills/hallmark/references/design-md.md +116 -116
  108. package/template/.claude/skills/hallmark/references/export-formats.md +328 -328
  109. package/template/.claude/skills/hallmark/references/floating-nav.md +89 -89
  110. package/template/.claude/skills/hallmark/references/genres/atmospheric.md +65 -65
  111. package/template/.claude/skills/hallmark/references/genres/editorial.md +70 -70
  112. package/template/.claude/skills/hallmark/references/genres/modern-minimal.md +67 -67
  113. package/template/.claude/skills/hallmark/references/genres/playful.md +65 -65
  114. package/template/.claude/skills/hallmark/references/hero-enrichment.md +474 -474
  115. package/template/.claude/skills/hallmark/references/imagery-kit.md +170 -170
  116. package/template/.claude/skills/hallmark/references/interaction-and-states.md +207 -207
  117. package/template/.claude/skills/hallmark/references/layout-and-space.md +111 -111
  118. package/template/.claude/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -35
  119. package/template/.claude/skills/hallmark/references/macrostructures/02-long-document.md +34 -34
  120. package/template/.claude/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -31
  121. package/template/.claude/skills/hallmark/references/macrostructures/04-stat-led.md +32 -32
  122. package/template/.claude/skills/hallmark/references/macrostructures/05-workbench.md +32 -32
  123. package/template/.claude/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -33
  124. package/template/.claude/skills/hallmark/references/macrostructures/07-manifesto.md +32 -32
  125. package/template/.claude/skills/hallmark/references/macrostructures/08-photographic.md +34 -34
  126. package/template/.claude/skills/hallmark/references/macrostructures/09-quote-led.md +32 -32
  127. package/template/.claude/skills/hallmark/references/macrostructures/10-specimen.md +32 -32
  128. package/template/.claude/skills/hallmark/references/macrostructures/11-catalogue.md +23 -23
  129. package/template/.claude/skills/hallmark/references/macrostructures/12-letter.md +23 -23
  130. package/template/.claude/skills/hallmark/references/macrostructures/13-index-first.md +23 -23
  131. package/template/.claude/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -23
  132. package/template/.claude/skills/hallmark/references/macrostructures/15-split-studio.md +23 -23
  133. package/template/.claude/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -23
  134. package/template/.claude/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -23
  135. package/template/.claude/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -23
  136. package/template/.claude/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -23
  137. package/template/.claude/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -23
  138. package/template/.claude/skills/hallmark/references/macrostructures/21-component-playground.md +23 -23
  139. package/template/.claude/skills/hallmark/references/macrostructures.md +89 -89
  140. package/template/.claude/skills/hallmark/references/microinteractions.md +260 -260
  141. package/template/.claude/skills/hallmark/references/motion.md +109 -109
  142. package/template/.claude/skills/hallmark/references/preview-examples.md +49 -49
  143. package/template/.claude/skills/hallmark/references/responsive.md +138 -138
  144. package/template/.claude/skills/hallmark/references/slop-test.md +205 -205
  145. package/template/.claude/skills/hallmark/references/structure.md +164 -164
  146. package/template/.claude/skills/hallmark/references/study.md +511 -511
  147. package/template/.claude/skills/hallmark/references/typography.md +243 -243
  148. package/template/.claude/skills/hallmark/references/verbs/audit.md +25 -25
  149. package/template/.claude/skills/hallmark/references/verbs/redesign.md +269 -269
  150. package/template/.claude/skills/hallmark-loop/SKILL.md +105 -105
  151. package/template/.claude/skills/hallmark-loop/references/auditor-call.md +60 -60
  152. package/template/.claude/skills/hallmark-loop/references/capture.md +78 -78
  153. package/template/.claude/skills/hallmark-loop/references/loop-control.md +79 -79
  154. package/template/.claude/skills/handoff/SKILL.md +15 -15
  155. package/template/.claude/skills/knowledge-crunching/SKILL.md +94 -94
  156. package/template/.claude/skills/research/SKILL.md +69 -69
  157. package/template/.claude/skills/tdd/SKILL.md +142 -142
  158. package/template/.claude/skills/tdd/deep-modules.md +15 -15
  159. package/template/.claude/skills/tdd/interface-design.md +31 -31
  160. package/template/.claude/skills/tdd/mocking.md +59 -59
  161. package/template/.claude/skills/tdd/refactoring.md +10 -10
  162. package/template/.claude/skills/tdd/tests.md +61 -61
  163. package/template/.claude/statusline.cjs +100 -37
@@ -1,173 +1,173 @@
1
- #!/usr/bin/env node
2
- // PreToolUse guard: set up `.adr-dir` before the first `adr new` in a project,
3
- // so the agent never has to pick between `adr init` and a hand-written file.
4
- //
5
- // Two setup paths, and picking the wrong one is destructive:
6
- // - fresh (no `.adr-dir`, no numbered ADRs yet): `adr init docs/adr` is safe —
7
- // it creates docs/adr/, writes `.adr-dir`, and adds a baseline ADR.
8
- // - migration (numbered ADRs already exist, but no `.adr-dir`): `adr init`
9
- // would ALSO add that baseline ADR, burning the next real number. The fix
10
- // is a plain `.adr-dir` file with no tool call.
11
- // Already initialized: nothing to do, both paths are a no-op.
12
- //
13
- // Also makes sure this skill's ADR template is the one `adr new` picks up, by
14
- // placing it at `<adr-dir>/templates/template.md` — the third step of the tool's
15
- // own template resolution, and the reason no env var has to be injected.
16
- //
17
- // Runs the setup itself (as a side effect) before allowing `adr new` through,
18
- // and denies a *direct* `adr init` call whenever running it would be wrong —
19
- // already initialized, or migration state — since either would burn a number.
20
- // Anything it does is echoed to stdout so it stays visible in the transcript.
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
- const { spawnSync } = require('child_process');
25
- const { invokesAdr } = require('./lib/adr/command-scan.cjs');
26
-
27
- const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
28
- const ADR_DIR = 'docs/adr';
29
- const ADR_DIR_FILE = path.join(root, '.adr-dir');
30
- const ADR_FILE = /^\d+-.*\.md$/;
31
- const SKILL_TEMPLATE = path.join(
32
- root,
33
- '.claude',
34
- 'skills',
35
- 'adr-writer',
36
- 'references',
37
- 'adr-template.md'
38
- );
39
-
40
- function allow(message) {
41
- if (message) process.stdout.write(message + '\n');
42
- process.exit(0);
43
- }
44
-
45
- function deny(reason) {
46
- process.stdout.write(
47
- JSON.stringify({
48
- hookSpecificOutput: {
49
- hookEventName: 'PreToolUse',
50
- permissionDecision: 'deny',
51
- permissionDecisionReason: reason,
52
- },
53
- })
54
- );
55
- process.exit(0);
56
- }
57
-
58
- function readAdrDir() {
59
- try {
60
- return fs.readFileSync(ADR_DIR_FILE, 'utf8').trim() || null;
61
- } catch {
62
- return null;
63
- }
64
- }
65
-
66
- function isInitialized() {
67
- return readAdrDir() !== null;
68
- }
69
-
70
- function hasNumberedAdrs() {
71
- try {
72
- return fs.readdirSync(path.join(root, ADR_DIR)).some((f) => ADR_FILE.test(f));
73
- } catch {
74
- return false; // directory doesn't exist yet -> nothing to migrate
75
- }
76
- }
77
-
78
- function runAdrInit() {
79
- // `adr` is an npm bin — a real executable on every platform, so it is called
80
- // directly rather than through a shell script interpreter. `shell: true` is
81
- // here only because npm installs it as `adr.cmd` on Windows, which
82
- // CreateProcess cannot launch on its own; every argument is a constant, so
83
- // there is nothing for a shell to interpolate.
84
- return spawnSync('adr', ['init', ADR_DIR], { cwd: root, shell: true, encoding: 'utf8' });
85
- }
86
-
87
- // `adr init` writes `.adr-dir` with `path.relative`, so on Windows it lands as
88
- // `docs\adr` with no trailing newline. That file is committed and read on every
89
- // other machine, where a backslash is an ordinary filename character and not a
90
- // separator. Rewrite it in the portable form the rest of this toolchain emits.
91
- function normalizeAdrDirFile() {
92
- const declared = readAdrDir();
93
- if (!declared) return;
94
- fs.writeFileSync(ADR_DIR_FILE, `${declared.split(path.sep).join('/')}\n`);
95
- }
96
-
97
- // The tool resolves its template as: explicit argument, then $ADR_TEMPLATE, then
98
- // `<adr-dir>/templates/template.md`, then its own bundled default. Copying this
99
- // skill's template into the third slot means a bare `adr new` produces a
100
- // musketeer ADR with no environment set up for it.
101
- //
102
- // Only ever writes when the file is absent, so a project that has customised its
103
- // own template keeps it.
104
- function ensureTemplate() {
105
- try {
106
- const dir = readAdrDir() || ADR_DIR;
107
- const target = path.join(root, dir, 'templates', 'template.md');
108
- if (fs.existsSync(target) || !fs.existsSync(SKILL_TEMPLATE)) return null;
109
- fs.mkdirSync(path.dirname(target), { recursive: true });
110
- fs.copyFileSync(SKILL_TEMPLATE, target);
111
- return `Installed the adr-writer template at ${dir}/templates/template.md.`;
112
- } catch {
113
- return null; // never block an ADR over the template copy
114
- }
115
- }
116
-
117
- function main() {
118
- const payload = JSON.parse(fs.readFileSync(0, 'utf8'));
119
- const command = payload?.tool_input?.command || '';
120
- const isInit = invokesAdr(command, 'init');
121
- const isNew = invokesAdr(command, 'new');
122
- if (!isInit && !isNew) return allow();
123
-
124
- if (isInitialized()) {
125
- if (isInit) {
126
- return deny(
127
- '.adr-dir already exists — do not run `adr init` again, it would add a duplicate ' +
128
- 'baseline ADR and burn the next number. Run `adr new` directly.'
129
- );
130
- }
131
- return allow(ensureTemplate()); // adr new, already set up: nothing else to do
132
- }
133
-
134
- if (hasNumberedAdrs()) {
135
- // Migration: numbered ADRs exist but `.adr-dir` does not. `adr init` would
136
- // still add a baseline ADR here, so it is never the right command.
137
- if (isInit) {
138
- return deny(
139
- 'Numbered ADRs already exist without `.adr-dir` — this is a migration, not a fresh ' +
140
- 'project. `adr init` would add a duplicate baseline ADR and burn the next number. ' +
141
- 'Run `adr new` directly; `.adr-dir` is set up automatically.'
142
- );
143
- }
144
- fs.writeFileSync(ADR_DIR_FILE, `${ADR_DIR}\n`);
145
- const note = ensureTemplate();
146
- return allow(
147
- `Migration detected: wrote .adr-dir (${ADR_DIR}) by hand, no baseline ADR added.` +
148
- (note ? `\n${note}` : '')
149
- );
150
- }
151
-
152
- // Fresh project.
153
- if (isInit) return allow(); // the agent's own `adr init` call is correct here — let it run
154
- const res = runAdrInit();
155
- if (res.error || res.status !== 0) {
156
- return allow(
157
- `Could not run \`adr init ${ADR_DIR}\` (${res.error?.message || res.stderr}); ` +
158
- 'letting the original command run and fail with its own error.'
159
- );
160
- }
161
- normalizeAdrDirFile();
162
- const note = ensureTemplate();
163
- return allow(
164
- `Fresh project: ran \`adr init ${ADR_DIR}\` (creates the baseline ADR).` +
165
- (note ? `\n${note}` : '')
166
- );
167
- }
168
-
169
- try {
170
- main();
171
- } catch {
172
- allow(); // fail open
173
- }
1
+ #!/usr/bin/env node
2
+ // PreToolUse guard: set up `.adr-dir` before the first `adr new` in a project,
3
+ // so the agent never has to pick between `adr init` and a hand-written file.
4
+ //
5
+ // Two setup paths, and picking the wrong one is destructive:
6
+ // - fresh (no `.adr-dir`, no numbered ADRs yet): `adr init docs/adr` is safe —
7
+ // it creates docs/adr/, writes `.adr-dir`, and adds a baseline ADR.
8
+ // - migration (numbered ADRs already exist, but no `.adr-dir`): `adr init`
9
+ // would ALSO add that baseline ADR, burning the next real number. The fix
10
+ // is a plain `.adr-dir` file with no tool call.
11
+ // Already initialized: nothing to do, both paths are a no-op.
12
+ //
13
+ // Also makes sure this skill's ADR template is the one `adr new` picks up, by
14
+ // placing it at `<adr-dir>/templates/template.md` — the third step of the tool's
15
+ // own template resolution, and the reason no env var has to be injected.
16
+ //
17
+ // Runs the setup itself (as a side effect) before allowing `adr new` through,
18
+ // and denies a *direct* `adr init` call whenever running it would be wrong —
19
+ // already initialized, or migration state — since either would burn a number.
20
+ // Anything it does is echoed to stdout so it stays visible in the transcript.
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const { spawnSync } = require('child_process');
25
+ const { invokesAdr } = require('./lib/adr/command-scan.cjs');
26
+
27
+ const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
28
+ const ADR_DIR = 'docs/adr';
29
+ const ADR_DIR_FILE = path.join(root, '.adr-dir');
30
+ const ADR_FILE = /^\d+-.*\.md$/;
31
+ const SKILL_TEMPLATE = path.join(
32
+ root,
33
+ '.claude',
34
+ 'skills',
35
+ 'adr-writer',
36
+ 'references',
37
+ 'adr-template.md'
38
+ );
39
+
40
+ function allow(message) {
41
+ if (message) process.stdout.write(message + '\n');
42
+ process.exit(0);
43
+ }
44
+
45
+ function deny(reason) {
46
+ process.stdout.write(
47
+ JSON.stringify({
48
+ hookSpecificOutput: {
49
+ hookEventName: 'PreToolUse',
50
+ permissionDecision: 'deny',
51
+ permissionDecisionReason: reason,
52
+ },
53
+ })
54
+ );
55
+ process.exit(0);
56
+ }
57
+
58
+ function readAdrDir() {
59
+ try {
60
+ return fs.readFileSync(ADR_DIR_FILE, 'utf8').trim() || null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function isInitialized() {
67
+ return readAdrDir() !== null;
68
+ }
69
+
70
+ function hasNumberedAdrs() {
71
+ try {
72
+ return fs.readdirSync(path.join(root, ADR_DIR)).some((f) => ADR_FILE.test(f));
73
+ } catch {
74
+ return false; // directory doesn't exist yet -> nothing to migrate
75
+ }
76
+ }
77
+
78
+ function runAdrInit() {
79
+ // `adr` is an npm bin — a real executable on every platform, so it is called
80
+ // directly rather than through a shell script interpreter. `shell: true` is
81
+ // here only because npm installs it as `adr.cmd` on Windows, which
82
+ // CreateProcess cannot launch on its own; every argument is a constant, so
83
+ // there is nothing for a shell to interpolate.
84
+ return spawnSync('adr', ['init', ADR_DIR], { cwd: root, shell: true, encoding: 'utf8' });
85
+ }
86
+
87
+ // `adr init` writes `.adr-dir` with `path.relative`, so on Windows it lands as
88
+ // `docs\adr` with no trailing newline. That file is committed and read on every
89
+ // other machine, where a backslash is an ordinary filename character and not a
90
+ // separator. Rewrite it in the portable form the rest of this toolchain emits.
91
+ function normalizeAdrDirFile() {
92
+ const declared = readAdrDir();
93
+ if (!declared) return;
94
+ fs.writeFileSync(ADR_DIR_FILE, `${declared.split(path.sep).join('/')}\n`);
95
+ }
96
+
97
+ // The tool resolves its template as: explicit argument, then $ADR_TEMPLATE, then
98
+ // `<adr-dir>/templates/template.md`, then its own bundled default. Copying this
99
+ // skill's template into the third slot means a bare `adr new` produces a
100
+ // musketeer ADR with no environment set up for it.
101
+ //
102
+ // Only ever writes when the file is absent, so a project that has customised its
103
+ // own template keeps it.
104
+ function ensureTemplate() {
105
+ try {
106
+ const dir = readAdrDir() || ADR_DIR;
107
+ const target = path.join(root, dir, 'templates', 'template.md');
108
+ if (fs.existsSync(target) || !fs.existsSync(SKILL_TEMPLATE)) return null;
109
+ fs.mkdirSync(path.dirname(target), { recursive: true });
110
+ fs.copyFileSync(SKILL_TEMPLATE, target);
111
+ return `Installed the adr-writer template at ${dir}/templates/template.md.`;
112
+ } catch {
113
+ return null; // never block an ADR over the template copy
114
+ }
115
+ }
116
+
117
+ function main() {
118
+ const payload = JSON.parse(fs.readFileSync(0, 'utf8'));
119
+ const command = payload?.tool_input?.command || '';
120
+ const isInit = invokesAdr(command, 'init');
121
+ const isNew = invokesAdr(command, 'new');
122
+ if (!isInit && !isNew) return allow();
123
+
124
+ if (isInitialized()) {
125
+ if (isInit) {
126
+ return deny(
127
+ '.adr-dir already exists — do not run `adr init` again, it would add a duplicate ' +
128
+ 'baseline ADR and burn the next number. Run `adr new` directly.'
129
+ );
130
+ }
131
+ return allow(ensureTemplate()); // adr new, already set up: nothing else to do
132
+ }
133
+
134
+ if (hasNumberedAdrs()) {
135
+ // Migration: numbered ADRs exist but `.adr-dir` does not. `adr init` would
136
+ // still add a baseline ADR here, so it is never the right command.
137
+ if (isInit) {
138
+ return deny(
139
+ 'Numbered ADRs already exist without `.adr-dir` — this is a migration, not a fresh ' +
140
+ 'project. `adr init` would add a duplicate baseline ADR and burn the next number. ' +
141
+ 'Run `adr new` directly; `.adr-dir` is set up automatically.'
142
+ );
143
+ }
144
+ fs.writeFileSync(ADR_DIR_FILE, `${ADR_DIR}\n`);
145
+ const note = ensureTemplate();
146
+ return allow(
147
+ `Migration detected: wrote .adr-dir (${ADR_DIR}) by hand, no baseline ADR added.` +
148
+ (note ? `\n${note}` : '')
149
+ );
150
+ }
151
+
152
+ // Fresh project.
153
+ if (isInit) return allow(); // the agent's own `adr init` call is correct here — let it run
154
+ const res = runAdrInit();
155
+ if (res.error || res.status !== 0) {
156
+ return allow(
157
+ `Could not run \`adr init ${ADR_DIR}\` (${res.error?.message || res.stderr}); ` +
158
+ 'letting the original command run and fail with its own error.'
159
+ );
160
+ }
161
+ normalizeAdrDirFile();
162
+ const note = ensureTemplate();
163
+ return allow(
164
+ `Fresh project: ran \`adr init ${ADR_DIR}\` (creates the baseline ADR).` +
165
+ (note ? `\n${note}` : '')
166
+ );
167
+ }
168
+
169
+ try {
170
+ main();
171
+ } catch {
172
+ allow(); // fail open
173
+ }
@@ -1,94 +1,94 @@
1
- #!/usr/bin/env node
2
- // PreToolUse guard: add the one flag every `adr new` call needs, so the agent
3
- // can write a bare `adr new -- "Title"` and still get a safe command.
4
- //
5
- // -q / --quiet — without it, an `-s` pattern matching more than one file
6
- // opens an interactive prompt on stdin and hangs the session. With it,
7
- // the ambiguous case throws instead, so the wrong ADR is never superseded
8
- // by a silent guess.
9
- //
10
- // Nothing else is injected. The template no longer needs an env var: `adr`
11
- // resolves `<adr-dir>/templates/template.md` on its own, and init-adr-dir.cjs
12
- // puts musketeer's template there. There is no editor to suppress either —
13
- // `adr` only opens one on an explicit `--open`.
14
- //
15
- // Uses PreToolUse's `updatedInput` (not just allow/deny) to rewrite the
16
- // command before it runs. A `-q` the agent already wrote is left alone, and
17
- // the call is a silent no-op.
18
- //
19
- // Safety: a title that happens to contain the literal text "adr new" could
20
- // make the matching regex fire inside a quoted string. Guarded by counting
21
- // quote characters before each match — an odd count means "inside an open
22
- // string", and that occurrence is left untouched rather than risk corrupting
23
- // the title.
24
-
25
- const fs = require('fs');
26
- const {
27
- invocationRe,
28
- isInsideQuotes,
29
- isInsideHeredoc,
30
- invokesAdr,
31
- } = require('./lib/adr/command-scan.cjs');
32
-
33
- const QUIET = /(?:^|\s)(?:-q|--quiet)(?=\s|$)/;
34
-
35
- // Everything between `adr new` and either the `--` option terminator or the end
36
- // of this command segment, whichever comes first.
37
- //
38
- // The title lives after `--` and may legitimately contain the text "-q"
39
- // ("Use -q for quiet builds"). Scanning it would make the hook believe the flag
40
- // was already supplied and skip an invocation that genuinely needs it, so the
41
- // title is never part of the region searched.
42
- function flagsRegion(command, from) {
43
- const rest = command.slice(from);
44
- let end = rest.length;
45
-
46
- const terminator = rest.search(/\s--(\s|$)/);
47
- if (terminator !== -1) end = terminator;
48
-
49
- for (let i = 0; i < end; i++) {
50
- if (';&|\n'.includes(rest[i]) && !isInsideQuotes(command, from + i)) {
51
- end = i;
52
- break;
53
- }
54
- }
55
- return rest.slice(0, end);
56
- }
57
-
58
- function main() {
59
- const payload = JSON.parse(fs.readFileSync(0, 'utf8'));
60
- const command = payload?.tool_input?.command || '';
61
- if (!invokesAdr(command, 'new')) return;
62
-
63
- let changed = false;
64
- const updated = command.replace(invocationRe('new'), (whole, boundary, ws, envPrefix, adrNew, offset) => {
65
- // Both guards, for the same reason the deny hooks use them: a heredoc body
66
- // is documentation being written, and injecting a flag into it would
67
- // rewrite the file's contents rather than the command.
68
- // `offset` is the boundary character; step past it to the word `adr`.
69
- const at = offset + boundary.length + ws.length + envPrefix.length;
70
- if (isInsideQuotes(command, at) || isInsideHeredoc(command, at)) return whole;
71
- if (QUIET.test(flagsRegion(command, at + adrNew.length))) return whole;
72
- changed = true;
73
- return `${boundary}${ws}${envPrefix}${adrNew} -q`;
74
- });
75
- if (!changed) return;
76
-
77
- process.stdout.write(
78
- JSON.stringify({
79
- systemMessage: 'Added -q to `adr new` — without it an ambiguous -s prompts on stdin and hangs.',
80
- hookSpecificOutput: {
81
- hookEventName: 'PreToolUse',
82
- permissionDecision: 'allow',
83
- updatedInput: { command: updated },
84
- },
85
- })
86
- );
87
- }
88
-
89
- try {
90
- main();
91
- } catch {
92
- /* fail open: leave the command untouched */
93
- }
94
- process.exit(0);
1
+ #!/usr/bin/env node
2
+ // PreToolUse guard: add the one flag every `adr new` call needs, so the agent
3
+ // can write a bare `adr new -- "Title"` and still get a safe command.
4
+ //
5
+ // -q / --quiet — without it, an `-s` pattern matching more than one file
6
+ // opens an interactive prompt on stdin and hangs the session. With it,
7
+ // the ambiguous case throws instead, so the wrong ADR is never superseded
8
+ // by a silent guess.
9
+ //
10
+ // Nothing else is injected. The template no longer needs an env var: `adr`
11
+ // resolves `<adr-dir>/templates/template.md` on its own, and init-adr-dir.cjs
12
+ // puts musketeer's template there. There is no editor to suppress either —
13
+ // `adr` only opens one on an explicit `--open`.
14
+ //
15
+ // Uses PreToolUse's `updatedInput` (not just allow/deny) to rewrite the
16
+ // command before it runs. A `-q` the agent already wrote is left alone, and
17
+ // the call is a silent no-op.
18
+ //
19
+ // Safety: a title that happens to contain the literal text "adr new" could
20
+ // make the matching regex fire inside a quoted string. Guarded by counting
21
+ // quote characters before each match — an odd count means "inside an open
22
+ // string", and that occurrence is left untouched rather than risk corrupting
23
+ // the title.
24
+
25
+ const fs = require('fs');
26
+ const {
27
+ invocationRe,
28
+ isInsideQuotes,
29
+ isInsideHeredoc,
30
+ invokesAdr,
31
+ } = require('./lib/adr/command-scan.cjs');
32
+
33
+ const QUIET = /(?:^|\s)(?:-q|--quiet)(?=\s|$)/;
34
+
35
+ // Everything between `adr new` and either the `--` option terminator or the end
36
+ // of this command segment, whichever comes first.
37
+ //
38
+ // The title lives after `--` and may legitimately contain the text "-q"
39
+ // ("Use -q for quiet builds"). Scanning it would make the hook believe the flag
40
+ // was already supplied and skip an invocation that genuinely needs it, so the
41
+ // title is never part of the region searched.
42
+ function flagsRegion(command, from) {
43
+ const rest = command.slice(from);
44
+ let end = rest.length;
45
+
46
+ const terminator = rest.search(/\s--(\s|$)/);
47
+ if (terminator !== -1) end = terminator;
48
+
49
+ for (let i = 0; i < end; i++) {
50
+ if (';&|\n'.includes(rest[i]) && !isInsideQuotes(command, from + i)) {
51
+ end = i;
52
+ break;
53
+ }
54
+ }
55
+ return rest.slice(0, end);
56
+ }
57
+
58
+ function main() {
59
+ const payload = JSON.parse(fs.readFileSync(0, 'utf8'));
60
+ const command = payload?.tool_input?.command || '';
61
+ if (!invokesAdr(command, 'new')) return;
62
+
63
+ let changed = false;
64
+ const updated = command.replace(invocationRe('new'), (whole, boundary, ws, envPrefix, adrNew, offset) => {
65
+ // Both guards, for the same reason the deny hooks use them: a heredoc body
66
+ // is documentation being written, and injecting a flag into it would
67
+ // rewrite the file's contents rather than the command.
68
+ // `offset` is the boundary character; step past it to the word `adr`.
69
+ const at = offset + boundary.length + ws.length + envPrefix.length;
70
+ if (isInsideQuotes(command, at) || isInsideHeredoc(command, at)) return whole;
71
+ if (QUIET.test(flagsRegion(command, at + adrNew.length))) return whole;
72
+ changed = true;
73
+ return `${boundary}${ws}${envPrefix}${adrNew} -q`;
74
+ });
75
+ if (!changed) return;
76
+
77
+ process.stdout.write(
78
+ JSON.stringify({
79
+ systemMessage: 'Added -q to `adr new` — without it an ambiguous -s prompts on stdin and hangs.',
80
+ hookSpecificOutput: {
81
+ hookEventName: 'PreToolUse',
82
+ permissionDecision: 'allow',
83
+ updatedInput: { command: updated },
84
+ },
85
+ })
86
+ );
87
+ }
88
+
89
+ try {
90
+ main();
91
+ } catch {
92
+ /* fail open: leave the command untouched */
93
+ }
94
+ process.exit(0);