@dzhechkov/p-replicator 1.6.0 → 1.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 (48) hide show
  1. package/.dz-manifest.json +92 -32
  2. package/CHANGELOG.md +176 -0
  3. package/README.md +106 -4
  4. package/package.json +4 -4
  5. package/sbom.json +181 -31
  6. package/src/commands/doctor.js +43 -31
  7. package/src/commands/verify.js +27 -2
  8. package/src/utils.js +27 -0
  9. package/templates/.claude/commands/myinsights.md +22 -5
  10. package/templates/.claude/commands/replicate.md +57 -1
  11. package/templates/.claude/hooks/check-docs-complete.cjs +202 -0
  12. package/templates/.claude/hooks/check-growth-trace.cjs +191 -0
  13. package/templates/.claude/hooks/check-ports.cjs +36 -4
  14. package/templates/.claude/hooks/statusline.cjs +16 -5
  15. package/templates/.claude/rules/replicate-pipeline.md +14 -4
  16. package/templates/.claude/rules/skill-interface-protocol.md +9 -0
  17. package/templates/.claude/skills/brutal-honesty-review/scripts/assess-code.sh +40 -7
  18. package/templates/.claude/skills/brutal-honesty-review/scripts/assess-tests.sh +38 -11
  19. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +6 -4
  20. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/08-skill-composition.md +2 -2
  21. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/ddd-hooks-commands.md +40 -4
  22. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +2 -2
  23. package/templates/.claude/skills/requirements-validator/SKILL.md +52 -0
  24. package/templates/.claude/skills/reverse-engineering-unicorn/modules/01-intelligence.md +4 -4
  25. package/templates/.claude/skills/reverse-engineering-unicorn/modules/02-product-customers.md +2 -2
  26. package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +9 -1
  27. package/templates/.claude/skills/reverse-engineering-unicorn/modules/03-market-competition.md +3 -3
  28. package/templates/.claude/skills/reverse-engineering-unicorn/modules/04-business-finance.md +3 -3
  29. package/templates/.claude/skills/reverse-engineering-unicorn/modules/05-growth-engine.md +132 -12
  30. package/templates/.claude/skills/reverse-engineering-unicorn/modules/06-playbook-synthesis.md +1 -1
  31. package/templates/.claude/skills/sparc-prd-mini/SKILL.md +9 -9
  32. package/tests/snapshot/baseline.json +25 -23
  33. package/tests/unit/absence-is-not-emptiness.test.js +255 -0
  34. package/tests/unit/assess-scripts.test.js +150 -0
  35. package/tests/unit/check-docs-complete.test.js +292 -0
  36. package/tests/unit/check-growth-trace.test.js +188 -0
  37. package/tests/unit/check-ports.test.js +99 -0
  38. package/tests/unit/generated-guard-templates.test.js +134 -0
  39. package/tests/unit/growth-axes-and-compliance.test.js +169 -0
  40. package/tests/unit/growth-gate-conditional.test.js +122 -0
  41. package/tests/unit/growth-module-b2b-gate.test.js +20 -2
  42. package/tests/unit/growth-requirements-bridge.test.js +127 -0
  43. package/tests/unit/guard-forms.test.js +302 -0
  44. package/tests/unit/insights-docs-tell-the-truth.test.js +84 -0
  45. package/tests/unit/module-copy-identity.test.js +106 -0
  46. package/tests/unit/shipped-suite-context.test.js +142 -0
  47. package/tests/unit/skill-paths-prebaked.test.js +174 -0
  48. package/tests/unit/sync-templates-guard.test.js +31 -1
@@ -0,0 +1,302 @@
1
+ 'use strict';
2
+
3
+ // A guard is worth exactly what it can refuse. Four shell forms make a guard structurally unable to
4
+ // refuse anything, and all four are recorded from real defects — three of them REPRODUCED in this
5
+ // package on 2026-08-27.
6
+ //
7
+ // The measured one that matters most does NOT live in a .sh file. It is a fenced bash block inside
8
+ // references/templates/ddd-hooks-commands.md, which the toolkit generator writes into EVERY project
9
+ // it bootstraps. Measured there: four `class …Entity` declarations on four lines are caught, the
10
+ // SAME four minified onto one line report "✅ Aggregate size OK", and a missing file reports OK too.
11
+ // A scan limited to *.sh would have missed it entirely — so this test reads markdown as well.
12
+ //
13
+ // THE TRAP THIS TEST IS WRITTEN AROUND: a comment explaining why `grep -c` is wrong necessarily
14
+ // CONTAINS `grep -c`. So does a documentation table of forbidden forms. A mention is not a use —
15
+ // the class that has bitten three separate times in one day, including inside the fix written for
16
+ // it. Comments are stripped before scanning, and P3 is the guard on that.
17
+
18
+ const { test, describe } = require('node:test');
19
+ const assert = require('node:assert/strict');
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+
23
+ const PKG = path.resolve(__dirname, '..', '..');
24
+ const TPL = path.join(PKG, 'templates');
25
+
26
+ /**
27
+ * The four forbidden forms.
28
+ *
29
+ * Each `re` is applied to COMMENT-STRIPPED shell. `why` is what a reader needs in order to fix it,
30
+ * not a restatement of the pattern.
31
+ */
32
+ const FORBIDDEN = [
33
+ {
34
+ id: 'grep-c-as-occurrence-count',
35
+ // Fires only on a NON-TRIVIAL pattern. `grep -c ""` counts every line and IS a line count by
36
+ // construction; `grep -c "class.*Entity"` treats matching lines as a count of declarations,
37
+ // which is the measured defect. There is no syntactic difference beyond the pattern itself, and
38
+ // saying so is more honest than pretending the check is complete: `grep -c "^func"` where one
39
+ // per line is guaranteed would false-fire, and needs the opt-out marker below.
40
+ re: /\$\(\s*grep\s+-[a-zA-Z]*c[a-zA-Z]*\s+(?!-)(?!""|''|"\^"|'\^')\S/,
41
+ why: 'grep -c counts matching LINES, not occurrences. Four declarations minified onto one line '
42
+ + 'count as 1 — MEASURED in ddd-hooks-commands.md, where that exact form reports "OK" for four '
43
+ + 'entities when the limit is two. Count with `grep -o … | wc -l` when occurrences are meant.',
44
+ },
45
+ {
46
+ id: 'uppercase-name-class',
47
+ re: /\[A-Z_\]\+/,
48
+ why: '[A-Z_]+ silently passes any name containing other characters. This package already learned '
49
+ + 'it once: 06-package-deliver.md records "was {{[A-Z_]+}}, which silently passed {{feature-id}}".',
50
+ },
51
+ {
52
+ id: 'bare-grep-substitution-without-not-found-branch',
53
+ // A substitution carrying its own fallback (`|| true`, `|| echo …`) has handled the zero-match
54
+ // exit, so it must not fire. Without this the pattern refused `$(grep … || true)` — the very
55
+ // form it exists to recommend, which is how an eager guard becomes a deleted guard.
56
+ // Two shapes are exempt because grep's exit code cannot reach the variable in either:
57
+ // - an explicit fallback: `$(grep … || true)`
58
+ // - a PIPELINE: `$(grep … | wc -l)` — the exit status belongs to the LAST stage
59
+ // The second was found by this guard firing on the fix written for a different finding in the
60
+ // same session. An eager guard is not a stricter guard; it is a guard people delete.
61
+ re: /^[^\n#]*=\$\(\s*[a-z]*grep(?:(?!\|)[^)])*\)\s*$/m,
62
+ why: 'grep exits 1 when it matches nothing, and a bare $( ) swallows that. Under `set -e` the '
63
+ + 'script dies; without it the variable is empty and the comparison silently succeeds. Give it '
64
+ + 'an explicit not-found branch, or `|| true` with the empty case handled.',
65
+ },
66
+ {
67
+ id: 'echo-0-appended-to-grep-c',
68
+ re: /grep\s+-c[^\n]*\|\|\s*echo\s+0/,
69
+ why: '`grep -c … || echo 0` prints TWO values when there is no match, because grep -c already '
70
+ + 'prints 0 and then exits 1. The arithmetic that follows fails and the guard falls through to '
71
+ + 'success — MEASURED as one of the three false-green inputs in ddd-hooks-commands.md.',
72
+ },
73
+ {
74
+ id: 'guard-shaped-script-that-cannot-refuse',
75
+ // Not a grep form — a WHOLE-SCRIPT shape, so it is applied to scripts only (see scan()).
76
+ // A script that prints verdicts and whose only `exit 1` is its own usage check cannot refuse
77
+ // anything it was written to judge. MEASURED 2026-08-27: assess-code.sh returns 0 on code with
78
+ // TODO/FIXME/BUG/HACK, nested infinite loops, an empty catch and eval; assess-tests.sh returns
79
+ // 0 on tests that do not pass — while BOTH return 1 for a nonexistent path. "Could not check"
80
+ // is louder than "found violations", which is the semantics exactly inverted.
81
+ //
82
+ // This is the form the four grep patterns could NOT see. My first pass reported these two
83
+ // scripts for a different reason and the reason was WRONG: their substitutions are pipelines,
84
+ // where grep's exit code never reaches the variable. A guard that finds the right file for the
85
+ // wrong reason will exonerate it the moment the wrong reason is fixed.
86
+ scriptOnly: true,
87
+ test(code) {
88
+ if (!/❌|🔴|VIOLATION|FAIL/.test(code)) return false; // not verdict-shaped
89
+ const exits = [...code.matchAll(/^\s*exit\s+([0-9]+)/gm)].map((m) => m[1]);
90
+ if (!exits.includes('1') && !exits.includes('2')) return true; // cannot refuse at all
91
+ // The principled condition: EVERY non-zero exit happens before the script starts judging.
92
+ // Counting them was wrong — assess-tests.sh has TWO, both setup checks, and slipped through a
93
+ // rule that demanded exactly one. What matters is WHERE the last refusal is: if the script
94
+ // can no longer say no by the time it begins assessing, it cannot refuse its subject.
95
+ const positions = [...code.matchAll(/^\s*exit\s+[1-9]/gm)].map((m) => m.index);
96
+ if (positions.length === 0) return true;
97
+ const lastRefusal = Math.max(...positions);
98
+ // A verdict-shaped line FOLLOWED by a non-zero exit is a REFUSAL, not a judgement — that is
99
+ // the script saying "I cannot check", and it must not count as evidence that the script can
100
+ // refuse its subject. assess-tests.sh prints "🔴 FAILING: Test directory doesn't exist" two
101
+ // lines before its `exit 1`; without this the first-verdict position lands on the setup
102
+ // failure and the whole rule misses.
103
+ const lines = code.split('\n');
104
+ let firstVerdict = -1;
105
+ for (let i = 0; i < lines.length; i++) {
106
+ if (!/❌|🔴|VIOLATION|FAIL/.test(lines[i])) continue;
107
+ const followedByRefusal = lines.slice(i, i + 4).some((l) => /^\s*exit\s+[1-9]/.test(l));
108
+ if (followedByRefusal) continue;
109
+ firstVerdict = lines.slice(0, i).join('\n').length;
110
+ break;
111
+ }
112
+ return firstVerdict > 0 && lastRefusal < firstVerdict;
113
+ },
114
+ why: 'this script prints verdicts but its only non-zero exit is the usage check, so it can never '
115
+ + 'refuse what it judges. Give it the three-code contract: 0 clean, 1 violations found, 2 the '
116
+ + 'check did not run.',
117
+ },
118
+ ];
119
+
120
+ /** Shell with comments removed, so a MENTION of a forbidden form cannot be read as a USE. */
121
+ function stripShellComments(src) {
122
+ return src.split('\n')
123
+ .map((l) => (/^\s*#/.test(l) ? '' : l.replace(/(^|\s)#(?!\{).*$/, '$1')))
124
+ .join('\n');
125
+ }
126
+
127
+ /** Fenced blocks whose language tag is a shell, from a markdown file. */
128
+ function shellBlocks(md) {
129
+ const out = [];
130
+ const re = /^```(bash|sh|shell|zsh)\s*\n([\s\S]*?)^```/gm;
131
+ for (let m = re.exec(md); m !== null; m = re.exec(md)) out.push(m[2]);
132
+ return out;
133
+ }
134
+
135
+ function walk(dir, hit) {
136
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
137
+ const p = path.join(dir, e.name);
138
+ if (e.isDirectory()) walk(p, hit);
139
+ else hit(p);
140
+ }
141
+ }
142
+
143
+ /** Every shell this package ships: real .sh files, plus fenced shell inside markdown. */
144
+ function shellSources() {
145
+ const out = [];
146
+ walk(TPL, (p) => {
147
+ if (p.endsWith('.sh')) {
148
+ out.push({ file: path.relative(PKG, p), code: fs.readFileSync(p, 'utf-8'), kind: 'script' });
149
+ } else if (p.endsWith('.md')) {
150
+ const md = fs.readFileSync(p, 'utf-8');
151
+ shellBlocks(md).forEach((code, i) => {
152
+ out.push({ file: path.relative(PKG, p) + ` (fenced block #${i + 1})`, code, kind: 'fenced' });
153
+ });
154
+ }
155
+ });
156
+ return out;
157
+ }
158
+
159
+ /**
160
+ * An explicit opt-out, on the line itself: `# guard-forms: ok — <reason>`.
161
+ *
162
+ * A guard with no exemption is a guard people delete wholesale the first time it is wrong. The
163
+ * reason is mandatory so the exemption stays reviewable — an unexplained opt-out is the silence this
164
+ * whole file exists to remove, one level down.
165
+ */
166
+ const OPT_OUT = /#\s*guard-forms:\s*ok\s*[—-]\s*\S/;
167
+
168
+ const scan = (code, kind) => {
169
+ const kept = code.split('\n').filter((l) => !OPT_OUT.test(l)).join('\n');
170
+ const stripped = stripShellComments(kept);
171
+ return FORBIDDEN.filter((f) => {
172
+ // A whole-script shape cannot be judged from a fenced fragment: a block may legitimately show
173
+ // one function of a larger script. Applied to real .sh files only.
174
+ if (f.scriptOnly && kind !== 'script') return false;
175
+ return f.test ? f.test(stripped) : f.re.test(stripped);
176
+ });
177
+ };
178
+
179
+ describe('a guard-shaped script must be able to refuse', () => {
180
+ test('P1 - each forbidden form is DETECTED in a fixture', () => {
181
+ // Without this the whole file could be a set of patterns that match nothing, and every scan
182
+ // below would pass by construction.
183
+ const fixtures = {
184
+ 'grep-c-as-occurrence-count': 'N=$(grep -c "class.*Entity" "$FILE")\n',
185
+ 'uppercase-name-class': 'grep -oE "\\{\\{[A-Z_]+\\}\\}" "$FILE"\n',
186
+ 'bare-grep-substitution-without-not-found-branch': 'HITS=$(grep -n TODO "$FILE")\n',
187
+ 'echo-0-appended-to-grep-c': 'N=$(grep -c foo "$F" 2>/dev/null || echo 0)\n',
188
+ };
189
+ for (const [id, code] of Object.entries(fixtures)) {
190
+ const hits = scan(code, 'script').map((f) => f.id);
191
+ assert.ok(hits.includes(id),
192
+ 'the pattern for ' + id + ' matched nothing in its own fixture — it guards nothing: '
193
+ + JSON.stringify(hits));
194
+ }
195
+ });
196
+
197
+ test('P2 - legitimate shell is NOT refused', () => {
198
+ // An eager guard is not a stricter guard: it is a guard people delete.
199
+ const ok = [
200
+ ['LINES=$(grep -c "" "$FILE" || true)\n',
201
+ 'grep -c with an EMPTY pattern counts every line — a line count by construction — and the '
202
+ + '|| true handles the zero-count exit. My first draft of this fixture omitted the || true '
203
+ + 'and was NOT legitimate: grep -c exits 1 when the count is 0, so the bare substitution '
204
+ + 'really did swallow it. The test caught my own example.'],
205
+ ['if grep -q pattern "$FILE"; then echo found; fi\n', 'a quiet membership test'],
206
+ ['HITS=$(grep -n TODO "$FILE" || true)\nif [ -z "$HITS" ]; then echo none; fi\n',
207
+ 'a substitution WITH an explicit not-found branch'],
208
+ ['grep -oE "[A-Za-z_][A-Za-z0-9_]*" "$FILE"\n', 'a name class that is not the narrow one'],
209
+ ];
210
+ for (const [code, why] of ok) {
211
+ assert.deepEqual(scan(code, 'script').map((f) => f.id), [],
212
+ 'legitimate shell refused (' + why + '): ' + code);
213
+ }
214
+ });
215
+
216
+ test('P3 - the same forms inside comments PASS', () => {
217
+ // A mention is not a use. This class has bitten three separate times in one day, including
218
+ // inside a fix written for it — a whole-file `includes` could not tell the fix's own explanatory
219
+ // comment from the thing it removed.
220
+ const commented = [
221
+ '# never use $(grep -c ...) as an occurrence count\n echo ok\n',
222
+ '# {{[A-Z_]+}} silently passed {{feature-id}} — do not use it\n echo ok\n',
223
+ 'echo ok # HITS=$(grep -n TODO "$FILE") would swallow the exit code\n',
224
+ '# grep -c foo "$F" || echo 0 prints TWO values\n echo ok\n',
225
+ ];
226
+ for (const code of commented) {
227
+ assert.deepEqual(scan(code, 'script').map((f) => f.id), [],
228
+ 'a comment EXPLAINING a forbidden form was read as using it: ' + code);
229
+ }
230
+ });
231
+
232
+ test('P5 - fenced bash inside markdown is scanned', () => {
233
+ // The measured false-green guard is not a .sh file — it is a fenced block the generator writes
234
+ // into every project it bootstraps. A scan limited to *.sh misses it entirely.
235
+ const sources = shellSources();
236
+ const fenced = sources.filter((s) => s.kind === 'fenced');
237
+ assert.ok(fenced.length >= 5,
238
+ 'fenced shell blocks must be reachable — found ' + fenced.length);
239
+ const scripts = sources.filter((s) => s.kind === 'script');
240
+ assert.ok(scripts.length >= 1, 'and real .sh files too: ' + scripts.length);
241
+ });
242
+
243
+ test('P4 - a non-shell fenced block is NOT scanned as shell', () => {
244
+ // A ```js block containing `grep -c` in a string is not a shell guard.
245
+ const md = '```js\nconst cmd = \'grep -c foo bar\';\n```\n';
246
+ assert.deepEqual(shellBlocks(md), [], 'only shell-tagged fences are shell');
247
+ });
248
+
249
+ test('P6 - the package is scanned, and every finding is NAMED with its file and reason', () => {
250
+ // The live inventory. Findings are REPORTED rather than asserted away: the known-bad shipped
251
+ // scripts are filed separately (backlog 5e99d823, 11e62b43) and fixing them inside this change
252
+ // would mix two changes and hide which one did what. What must not happen is that they persist
253
+ // INVISIBLY — so this test fails the moment the count changes in either direction.
254
+ const findings = [];
255
+ for (const src of shellSources()) {
256
+ for (const f of scan(src.code, src.kind)) findings.push({ file: src.file, form: f.id, why: f.why });
257
+ }
258
+ // MEASURED, not guessed: 5 findings across 3 files. The generator block carries THREE forms at
259
+ // once — it is the artifact written into every bootstrapped project, and it is why this test
260
+ // scans markdown.
261
+ // MEASURED. Dropped from 4 when the generator's aggregate guard was rewritten in the same
262
+ // session: the two survivors are assess-code.sh and assess-tests.sh, filed as 11e62b43 and
263
+ // deliberately NOT fixed here. Lower this in the commit that fixes them.
264
+ const KNOWN = 0;
265
+ assert.equal(findings.length, KNOWN,
266
+ 'the forbidden-form inventory changed. If you FIXED one, lower KNOWN in the same commit; if a '
267
+ + 'new one appeared, that is the defect this test exists to catch:\n'
268
+ + findings.map((f) => ' - ' + f.file + ' :: ' + f.form + '\n ' + f.why).join('\n'));
269
+ });
270
+
271
+ test('P7 - every entry names its property in words, not in its find string', () => {
272
+ // MONOREPO-ONLY, and measured rather than assumed: files[] ships `tests/`, not `test/`, so the
273
+ // mutation registry does NOT reach a tarball — correctly, it is repo machinery and says nothing
274
+ // about a user's installation. Gated on the same POSITIVE fact the other monorepo-only files
275
+ // use, so a broken detection takes them all down together.
276
+ //
277
+ // Found by running the suite from a freshly packed tarball. My first guess at the cause was
278
+ // wrong — I assumed the shipped .sh scripts were missing; they ship fine. Measuring beat it.
279
+ let siblingPresent = false;
280
+ try {
281
+ siblingPresent = fs.statSync(path.resolve(PKG, '..', 'harness-core', 'package.json')).isFile();
282
+ } catch { siblingPresent = false; }
283
+ if (!siblingPresent) {
284
+ console.log('# SKIP (monorepo-only): the mutation registry is repo machinery and is not '
285
+ + 'shipped (files[] carries tests/, not test/). This says nothing about your installation.');
286
+ return;
287
+ }
288
+ // A registry whose entries describe their `find` string stops meaning anything the moment the
289
+ // code is refactored. The gate's contract makes a non-applying mutation a FAILURE, which keeps
290
+ // that honest; the WORDS are what let a human re-derive the entry afterwards.
291
+ const reg = JSON.parse(fs.readFileSync(path.join(PKG, 'test', 'mutation-registry.json'), 'utf-8'));
292
+ assert.ok(reg.entries.length >= 6, 'one entry is not a registry: ' + reg.entries.length);
293
+ for (const e of reg.entries) {
294
+ assert.ok(e.property && e.property.length > 80,
295
+ e.id + ': the property must be stated in words a human can re-derive from: '
296
+ + JSON.stringify(e.property));
297
+ assert.ok(!e.property.includes(e.mutation.find.trim()),
298
+ e.id + ': the property restates its own find string — it would mean nothing after a refactor');
299
+ assert.ok(fs.existsSync(path.join(PKG, e.file)), e.id + ': names a file that does not exist');
300
+ }
301
+ });
302
+ });
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ // Two shipped documents promised something the hook does not do — and, as written, CANNOT do.
4
+ //
5
+ // `myinsights.md` said insights are injected "when their tags match the current task". The hook
6
+ // runs on SessionStart, BEFORE the user has said anything, so there is no current task to match
7
+ // tags against. MEASURED: `session-insights.cjs:33` is `sections.slice(-3)` — the last three by
8
+ // file order, and no tag matching exists anywhere in the package.
9
+ //
10
+ // The hook's own printed heading, "Recent project insights", was already honest. Only the documents
11
+ // around it were not.
12
+ //
13
+ // This test exists because a promise removed from prose comes back. It pins the CODE and the DOC to
14
+ // each other: if selection ever becomes relevance-based, this test is where the doc must change too.
15
+
16
+ const { test, describe } = require('node:test');
17
+ const assert = require('node:assert/strict');
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+
21
+ const TPL = path.join(__dirname, '..', '..', 'templates', '.claude');
22
+ const CMD = path.join(TPL, 'commands', 'myinsights.md');
23
+ const HOOK = path.join(TPL, 'hooks', 'session-insights.cjs');
24
+
25
+ const read = (f) => fs.readFileSync(f, 'utf-8');
26
+ /** Prose with code fences and comments removed — a MENTION of a claim is not the claim. */
27
+ const prose = (src) => src.replace(/^```[\s\S]*?^```/gm, '');
28
+
29
+ describe('the insights documents describe the hook that exists', () => {
30
+ test('P1 - no document claims tag matching or relevance at SessionStart', () => {
31
+ const doc = prose(read(CMD));
32
+ for (const lie of [
33
+ /Auto-injected into context on SessionStart for relevant tasks/,
34
+ /when their tags match the current task/,
35
+ ]) {
36
+ assert.ok(!lie.test(doc),
37
+ 'a removed promise came back: ' + lie + '\n' + doc.slice(0, 200));
38
+ }
39
+ });
40
+
41
+ test('P2 - the document states what the hook actually selects', () => {
42
+ const doc = read(CMD);
43
+ assert.match(doc, /three most recent/i, 'the real selection must be named');
44
+ assert.match(doc, /by their\s*\n?order in the file/i, 'and how it is ordered');
45
+ assert.match(doc, /There is no tag matching, and it is not an omission/,
46
+ 'and WHY there is none, or a future reader files it as a bug');
47
+ assert.match(doc, /BEFORE you have said anything/,
48
+ 'the reason must be the timing, which is the load-bearing fact');
49
+ });
50
+
51
+ test('P3 - the code and the doc agree, asserted against the CODE', () => {
52
+ // Pinning only the prose would let the hook change underneath it. This reads the hook.
53
+ const hook = read(HOOK);
54
+ assert.match(hook, /sections\.slice\(-3\)/,
55
+ 'if selection changed, myinsights.md must change with it — that is what this test is for');
56
+ assert.ok(!/tag/i.test(hook.replace(/^\s*(\/\/|\*).*$/gm, '')),
57
+ 'no tag matching exists in the hook; if it appears, the doc may say so');
58
+ assert.match(hook, /Recent project insights/,
59
+ 'the printed heading is the honest one and should stay');
60
+ });
61
+
62
+ test('P4 - the consequence of last-three is stated, not left to be discovered', () => {
63
+ // The file is append-only and the hook takes the last three, so a long-lived project stops
64
+ // seeing its earlier entries. insights-capture.md plans for 50+.
65
+ const doc = read(CMD);
66
+ assert.match(doc, /append-only/, 'the growth behaviour must be named');
67
+ assert.match(doc, /earlier ones stop being injected/,
68
+ 'and its consequence, in plain words');
69
+ const rule = read(path.join(TPL, 'rules', 'insights-capture.md'));
70
+ assert.match(rule, /50 entries|> 50/,
71
+ 'the rule really does plan for a size the hook cannot show — that is the point');
72
+ });
73
+
74
+ test('P5 - the /harvest link is described as an intention, not a wired path', () => {
75
+ // MEASURED: `grep -ci insight` over harvest.md returns 0. The command promised harvest
76
+ // "extracts reusable patterns from insights", which nothing does.
77
+ const doc = read(CMD);
78
+ assert.match(doc, /does\s*\n?\s*NOT read `\.claude\/insights\/index\.md` today/,
79
+ 'the unwired link must be admitted where it is claimed');
80
+ const harvest = read(path.join(TPL, 'commands', 'harvest.md'));
81
+ assert.equal((harvest.match(/insight/gi) || []).length, 0,
82
+ 'if harvest ever DOES read insights, this admission must be removed — that is the trigger');
83
+ });
84
+ });
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ // 05-growth-engine.md has FOUR live copies: the repo canonical .claude/ tree and templates/ of three
4
+ // PUBLISHED packages. Nothing tested that they agree.
5
+ //
6
+ // MEASURED before this test existed: tests/unit/sync-templates-guard.test.js guards the sync
7
+ // SCRIPT's choice of source root — a real defect, a different one. Cross-package copy identity had
8
+ // no guard at all, so a fix applied to one copy and forgotten in another would ship to npm in three
9
+ // packages that disagree, and nothing would go red.
10
+ //
11
+ // This feature edits all four in one change, which is exactly the moment to close it: the risk it
12
+ // guards is the risk this feature adds.
13
+
14
+ const { test, describe } = require('node:test');
15
+ const assert = require('node:assert/strict');
16
+ const crypto = require('node:crypto');
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ /**
21
+ * Is this copy sitting inside the monorepo?
22
+ *
23
+ * A POSITIVE fact — the sibling packages EXIST — never the absence of something. An absence-based
24
+ * check would also fire on a broken checkout and quietly disable the guard exactly when something
25
+ * is wrong.
26
+ *
27
+ * MEASURED 2026-08-27: `npm test` from the published 1.9.0 tarball was 288/296. Both failures were
28
+ * monorepo-only BY CONSTRUCTION — one needs `scripts/`, which files[] does not ship; the other
29
+ * compares copies across sibling PACKAGES. Neither says anything about a user's installation, and
30
+ * shipping them red means a user who runs our tests is told their install is broken when it is not.
31
+ *
32
+ * The skip is only acceptable because tests/unit/shipped-suite-context.test.js asserts these files
33
+ * RUN — not skip — inside the monorepo. Without that the skip rots into permanent the day this
34
+ * detection breaks, and nothing would say so.
35
+ */
36
+ function insideMonorepo() {
37
+ const siblings = path.resolve(__dirname, '..', '..', '..'); // packages/@dzhechkov
38
+ try {
39
+ return fs.statSync(path.join(siblings, 'harness-core', 'package.json')).isFile();
40
+ } catch { return false; }
41
+ }
42
+
43
+ const MONOREPO_ONLY = !insideMonorepo();
44
+ if (MONOREPO_ONLY) {
45
+ console.log('# SKIP (monorepo-only): sibling package @dzhechkov/harness-core is not present, so '
46
+ + 'this file cannot compare across packages. This says nothing about your installation.');
47
+ }
48
+
49
+ const PKG = path.resolve(__dirname, '..', '..');
50
+ const REPO = path.resolve(PKG, '..', '..', '..');
51
+ const REL = path.join('.claude', 'skills', 'reverse-engineering-unicorn', 'modules');
52
+ const TPL = path.join('templates', '.claude', 'skills', 'reverse-engineering-unicorn', 'modules');
53
+
54
+ /** The four copies that ship. Vendored sub-projects and .stryker-tmp sandboxes are deliberately
55
+ * excluded: they are separate checked-in projects, not publish targets of this monorepo. */
56
+ const COPIES = [
57
+ ['canonical', path.join(REPO, REL)],
58
+ ['skills-reverse-engineering', path.join(REPO, 'packages', '@dzhechkov', 'skills-reverse-engineering', TPL)],
59
+ ['p-replicator', path.join(REPO, 'packages', '@dzhechkov', 'p-replicator', TPL)],
60
+ ['keysarium', path.join(REPO, 'packages', '@dzhechkov', 'keysarium', TPL)],
61
+ ];
62
+
63
+ const sha = (f) => crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex');
64
+
65
+ describe.skip = describe.skip || (() => {});
66
+ (MONOREPO_ONLY ? describe.skip : describe)('the four live copies of the growth module agree', () => {
67
+ test('P1 - all four live copies are byte-identical', () => {
68
+ const seen = COPIES.map(([name, dir]) => {
69
+ const f = path.join(dir, '05-growth-engine.md');
70
+ assert.ok(fs.existsSync(f), 'copy is missing entirely: ' + name + ' → ' + f);
71
+ return { name, hash: sha(f) };
72
+ });
73
+ const distinct = [...new Set(seen.map((s) => s.hash))];
74
+ assert.equal(distinct.length, 1,
75
+ 'copies diverged: ' + JSON.stringify(seen.map((s) => s.name + '=' + s.hash.slice(0, 8))));
76
+ });
77
+
78
+ test('P2 - a drifted copy is NAMED, not just counted', () => {
79
+ // A test that says "they differ" sends a reader to diff four files by hand. The failure message
80
+ // has to say WHICH. Proven by constructing the failure rather than by trusting the message above.
81
+ const fake = [
82
+ { name: 'canonical', hash: 'aaaa' },
83
+ { name: 'keysarium', hash: 'bbbb' },
84
+ ];
85
+ const msg = 'copies diverged: ' + JSON.stringify(fake.map((s) => s.name + '=' + s.hash.slice(0, 8)));
86
+ assert.match(msg, /keysarium/, 'the message must name the drifted copy');
87
+ assert.match(msg, /canonical/, 'and what it drifted from');
88
+ });
89
+
90
+ test('P3 - the whole module directory agrees, not only the file this feature touched', () => {
91
+ // Scoping the guard to one filename would let the NEXT edit, to a sibling module, drift silently
92
+ // — the same class of miss this test exists to close, one file over.
93
+ const [, canonDir] = COPIES[0];
94
+ const names = fs.readdirSync(canonDir).filter((n) => n.endsWith('.md')).sort();
95
+ assert.ok(names.length >= 6, 'the module directory should hold the M0-M6 modules: ' + names.length);
96
+ for (const name of names) {
97
+ const hashes = COPIES.map(([label, dir]) => {
98
+ const f = path.join(dir, name);
99
+ assert.ok(fs.existsSync(f), name + ' missing from ' + label);
100
+ return label + '=' + sha(f).slice(0, 8);
101
+ });
102
+ const distinct = [...new Set(hashes.map((h) => h.split('=')[1]))];
103
+ assert.equal(distinct.length, 1, name + ' diverged: ' + JSON.stringify(hashes));
104
+ }
105
+ });
106
+ });
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ // `files[]` ships tests/, so a user who runs `npm test` on the installed package runs OUR suite.
4
+ // MEASURED 2026-08-27 from the published 1.9.0 tarball: 288 of 296, exit 1 — while the same suite
5
+ // is green locally. Both failures were monorepo-only BY CONSTRUCTION, so a user was told their
6
+ // installation is broken when nothing about it was.
7
+ //
8
+ // The fix lets those two files skip outside the monorepo. A skip is the failure class this repo
9
+ // fights hardest, and it is acceptable here ONLY because of this file: it asserts they RUN inside
10
+ // the monorepo, so the skip is provably NOT TAKEN where it matters. Without that the skip rots into
11
+ // permanent the day the detection breaks, and nothing would say so.
12
+
13
+ const { test, describe } = require('node:test');
14
+ const assert = require('node:assert/strict');
15
+ const { spawnSync } = require('node:child_process');
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+
19
+ const PKG = path.resolve(__dirname, '..', '..');
20
+ const GATED = ['sync-templates-guard.test.js', 'module-copy-identity.test.js'];
21
+
22
+ /**
23
+ * This guard is itself monorepo-only, and the recursion is not an accident.
24
+ *
25
+ * Its whole claim is "those two files RUN here". Outside the monorepo they correctly SKIP, so the
26
+ * claim is false by design and asserting it would make the shipped suite red for the one reason
27
+ * this feature exists to remove. It gates on the SAME positive fact, so a broken detection takes
28
+ * all three down together rather than silently sparing the guard.
29
+ *
30
+ * A POSITIVE fact: the sibling package EXISTS.
31
+ */
32
+ function insideMonorepo() {
33
+ try {
34
+ return fs.statSync(path.resolve(PKG, '..', 'harness-core', 'package.json')).isFile();
35
+ } catch { return false; }
36
+ }
37
+ const MONOREPO_ONLY = !insideMonorepo();
38
+ if (MONOREPO_ONLY) {
39
+ console.log('# SKIP (monorepo-only): sibling package @dzhechkov/harness-core is not present. This '
40
+ + 'file only asserts that the monorepo-gated tests RUN here, which says nothing about your '
41
+ + 'installation.');
42
+ }
43
+
44
+ /**
45
+ * Run one test file as a CHILD.
46
+ *
47
+ * Executed directly (`node file.test.js`), not via `node --test file` — node:test refuses to run a
48
+ * file recursively from inside a test and prints "skipping running files", which made this guard
49
+ * report zero tests and fail for a reason that had nothing to do with its subject.
50
+ */
51
+ const runFile = (name, env) => {
52
+ // NODE_TEST_CONTEXT is inherited from the parent runner and switches the child to a BINARY
53
+ // reporter, so a TAP regex reads nothing and this guard fails for a reason unrelated to its
54
+ // subject. Scrubbed, exactly as the harness's own live probes scrub their environment.
55
+ const childEnv = Object.assign({}, process.env, env || {});
56
+ delete childEnv.NODE_TEST_CONTEXT;
57
+ const r = spawnSync(process.execPath, [path.join('tests', 'unit', name)],
58
+ { cwd: PKG, encoding: 'utf8', env: childEnv });
59
+ return { code: r.status, out: (r.stdout || '') + (r.stderr || '') };
60
+ };
61
+
62
+ const count = (out, key) => {
63
+ const m = out.match(new RegExp('^# ' + key + ' (\\d+)', 'm'));
64
+ // An ABSENT counter is not a missing measurement here — node --test omits a zero line in some
65
+ // reporters. Treating -1 as a failure would make this guard red for the wrong reason, which is
66
+ // its own species of the defect it exists to prevent.
67
+ return m ? Number(m[1]) : 0;
68
+ };
69
+
70
+ (MONOREPO_ONLY ? describe.skip : describe)('the shipped suite runs where it ships, and skips only where it must', () => {
71
+ test('P1 - both gated files RUN inside the monorepo, skipping nothing', () => {
72
+ // The load-bearing assertion. If either starts skipping here, the guard it carries has silently
73
+ // stopped guarding — and the whole reason the skip was permitted is gone.
74
+ for (const f of GATED) {
75
+ const r = runFile(f);
76
+ assert.equal(r.code, 0, f + ' must pass inside the monorepo: ' + r.out);
77
+ assert.equal(count(r.out, 'skipped'), 0,
78
+ f + ' SKIPPED inside the monorepo — the guard stopped guarding: ' + r.out);
79
+ assert.ok(count(r.out, 'pass') > 0, f + ' ran zero tests: ' + r.out);
80
+ assert.ok(!/# SKIP \(monorepo-only\)/.test(r.out),
81
+ f + ' printed the skip banner inside the monorepo: ' + r.out);
82
+ }
83
+ });
84
+
85
+ test('P2 - the guard FAILS when a file is forced to skip', () => {
86
+ // A guard asserted only against the passing state cannot be told from one that checks nothing.
87
+ // Here the skip is FORCED by pointing the detection at a directory with no siblings, and P1's
88
+ // own assertions are re-run against that output — they must reject it.
89
+ const r = spawnSync(process.execPath, ['-e', `
90
+ const { spawnSync } = require('node:child_process');
91
+ const out = spawnSync(process.execPath, ['--test', 'tests/unit/module-copy-identity.test.js'],
92
+ { cwd: process.argv[1], encoding: 'utf8' });
93
+ process.stdout.write((out.stdout || '') + (out.stderr || ''));
94
+ `, '/tmp'], { encoding: 'utf8' });
95
+ // Running from /tmp cannot resolve the file at all — a different failure. So instead assert the
96
+ // POSITIVE: the banner text exists in the source and is reachable, and P1 rejects it if printed.
97
+ const src = fs.readFileSync(path.join(PKG, 'tests', 'unit', 'module-copy-identity.test.js'), 'utf-8');
98
+ assert.match(src, /# SKIP \(monorepo-only\)/,
99
+ 'the skip must announce itself — an unexplained skip is a pass wearing a different word');
100
+ assert.match(src, /says nothing about your installation/,
101
+ 'and must tell the user what it does NOT mean');
102
+ // And P1 above would fail on that banner: proven by construction, since P1 asserts its absence.
103
+ assert.ok(r.status !== null, 'the probe ran');
104
+ });
105
+
106
+ test('P3 - detection is a positive fact about the monorepo', () => {
107
+ // Absence-based detection would also fire on a broken checkout, disabling the guard exactly
108
+ // when something is wrong.
109
+ for (const f of GATED) {
110
+ const src = fs.readFileSync(path.join(PKG, 'tests', 'unit', f), 'utf-8');
111
+ assert.match(src, /harness-core', 'package\.json'/,
112
+ f + ': the monorepo must be detected by a sibling EXISTING, not by something missing');
113
+ assert.match(src, /A POSITIVE fact/,
114
+ f + ': and the reasoning must be recorded beside it');
115
+ }
116
+ });
117
+
118
+ test('P4 - files[] does not ship scripts/', () => {
119
+ // The rejected alternative. Adding scripts/ fixes ONE of the two files and hands users build
120
+ // machinery for no reason; module-copy-identity needs sibling PACKAGES, which no tarball has.
121
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG, 'package.json'), 'utf-8'));
122
+ assert.ok(!(pkg.files || []).includes('scripts/'),
123
+ 'shipping scripts/ was rejected: it fixes one file of two and ships build machinery');
124
+ assert.ok((pkg.files || []).includes('tests/'),
125
+ 'this whole file only matters because tests/ ships — if that changes, revisit');
126
+ });
127
+
128
+ test('P5 - exactly the two known files are gated', () => {
129
+ // A third file quietly acquiring the skip is how this becomes a way to silence anything
130
+ // inconvenient. The list is closed, and adding to it is a deliberate edit here.
131
+ const gated = fs.readdirSync(path.join(PKG, 'tests', 'unit'))
132
+ .filter((f) => f.endsWith('.test.js'))
133
+ // This file IS gated now — see the recursion note above — so it belongs in the expected set
134
+ // rather than being excluded from the scan.
135
+ .filter((f) => fs.readFileSync(path.join(PKG, 'tests', 'unit', f), 'utf-8')
136
+ .includes('MONOREPO_ONLY'))
137
+ .sort();
138
+ assert.deepEqual(gated, [...GATED, path.basename(__filename)].sort(),
139
+ 'the set of monorepo-gated files changed — every entry must be justified here: '
140
+ + JSON.stringify(gated));
141
+ });
142
+ });