@dzhechkov/p-replicator 1.12.0 → 1.13.1

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 (106) hide show
  1. package/.dz-manifest.json +225 -61
  2. package/CHANGELOG.md +148 -1
  3. package/LICENSE +21 -0
  4. package/MULTIPLATFORM_ROADMAP.md +1 -1
  5. package/README/eng/01_quickstart.md +2 -2
  6. package/README/eng/02_user_guide.md +1 -1
  7. package/README/eng/03_admin_guide.md +2 -2
  8. package/README/eng/05_architecture.md +1 -1
  9. package/README/eng/README.md +2 -1
  10. package/README/ru/01_quickstart.md +2 -2
  11. package/README/ru/02_user_guide.md +1 -1
  12. package/README/ru/03_admin_guide.md +2 -2
  13. package/README/ru/05_architecture.md +1 -1
  14. package/README/ru/README.md +2 -1
  15. package/README/ru/html/index.html +8 -8
  16. package/README.md +132 -9
  17. package/bin/cli.js +0 -0
  18. package/package.json +10 -11
  19. package/sbom.json +470 -60
  20. package/scripts/check-pipeline-gaps.sh +0 -0
  21. package/src/commands/init.js +1 -1
  22. package/src/rule-components.json +5 -1
  23. package/src/utils.js +32 -3
  24. package/templates/.claude/agents/product-discoverer.md +38 -0
  25. package/templates/.claude/agents/replicate-coordinator.md +11 -1
  26. package/templates/.claude/commands/feature.md +29 -5
  27. package/templates/.claude/commands/go.md +6 -8
  28. package/templates/.claude/commands/harvest.md +5 -7
  29. package/templates/.claude/commands/replicate.md +169 -44
  30. package/templates/.claude/commands/start.md +28 -7
  31. package/templates/.claude/hooks/capture-source-path.cjs +795 -0
  32. package/templates/.claude/hooks/check-canon.cjs +493 -0
  33. package/templates/.claude/hooks/check-embed-contract.cjs +374 -0
  34. package/templates/.claude/hooks/check-external-deps.cjs +288 -0
  35. package/templates/.claude/hooks/check-file-ownership.cjs +424 -0
  36. package/templates/.claude/hooks/check-handoff-manifest.cjs +367 -0
  37. package/templates/.claude/hooks/check-job-contract.cjs +501 -0
  38. package/templates/.claude/hooks/check-look-origin.cjs +240 -0
  39. package/templates/.claude/hooks/check-look-trace.cjs +385 -0
  40. package/templates/.claude/hooks/check-metric-source.cjs +296 -0
  41. package/templates/.claude/hooks/check-model-cost.cjs +470 -0
  42. package/templates/.claude/hooks/check-ports.cjs +27 -6
  43. package/templates/.claude/hooks/check-source-version.cjs +312 -0
  44. package/templates/.claude/hooks/check-swarm-receipts.cjs +197 -0
  45. package/templates/.claude/hooks/check-webhook-contract.cjs +535 -0
  46. package/templates/.claude/hooks/statusline.cjs +2 -2
  47. package/templates/.claude/rules/embeddable-widget.md +73 -0
  48. package/templates/.claude/rules/feature-lifecycle.md +5 -6
  49. package/templates/.claude/rules/incoming-webhooks.md +99 -0
  50. package/templates/.claude/rules/long-running-job.md +73 -0
  51. package/templates/.claude/rules/model-call-cost.md +85 -0
  52. package/templates/.claude/rules/replicate-pipeline.md +121 -52
  53. package/templates/.claude/skills/brutal-honesty-review/SKILL.md +9 -0
  54. package/templates/.claude/skills/cc-toolkit-generator-enhanced/SKILL.md +4 -0
  55. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +46 -1
  56. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +7 -1
  57. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/06-package-deliver.md +20 -2
  58. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/claude-md-strategy.md +7 -0
  59. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/automation-commands.md +17 -0
  60. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle-ent.md +43 -5
  61. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +43 -7
  62. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/start-command.md +19 -1
  63. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md +151 -0
  64. package/templates/.claude/skills/goap-research-ed25519/SKILL.md +37 -22
  65. package/templates/.claude/skills/goap-research-ed25519/references/negative-results.md +94 -0
  66. package/templates/.claude/skills/goap-research-ed25519/scripts/check_report_evidence.py +368 -4
  67. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +122 -5
  68. package/templates/.claude/skills/goap-research-ed25519/scripts/evidence_fetch.py +33 -16
  69. package/templates/.claude/skills/goap-research-ed25519/scripts/quote_provenance.py +342 -0
  70. package/templates/.claude/skills/goap-research-ed25519/scripts/test_ed25519_verifier.py +60 -0
  71. package/templates/.claude/skills/goap-research-ed25519/scripts/test_evidence_provenance.py +139 -6
  72. package/templates/.claude/skills/goap-research-ed25519/scripts/test_quote_provenance.py +274 -0
  73. package/templates/.claude/skills/goap-research-ed25519/scripts/test_suite_completeness.py +2 -1
  74. package/templates/.claude/skills/knowledge-extractor/SKILL.md +4 -0
  75. package/templates/.claude/skills/pipeline-forge/SKILL.md +18 -23
  76. package/templates/.claude/skills/pipeline-forge/examples/replicate-analysis.md +7 -2
  77. package/templates/.claude/skills/pipeline-forge/references/patterns-catalog.md +19 -1
  78. package/templates/.claude/skills/pipeline-forge/references/self-extracted-patterns.md +17 -6
  79. package/templates/.claude/skills/pipeline-forge/references/skill-anatomy.md +0 -1
  80. package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +21 -1
  81. package/templates/.claude/skills/sparc-prd-mini/SKILL.md +173 -725
  82. package/tests/snapshot/baseline.json +60 -38
  83. package/tests/unit/capture-source-path.test.js +492 -0
  84. package/tests/unit/check-canon.test.js +403 -0
  85. package/tests/unit/check-embed-contract.test.js +422 -0
  86. package/tests/unit/check-external-deps.test.js +363 -0
  87. package/tests/unit/check-file-ownership.test.js +388 -0
  88. package/tests/unit/check-handoff-manifest.test.js +410 -0
  89. package/tests/unit/check-job-contract.test.js +514 -0
  90. package/tests/unit/check-look-origin.test.js +180 -0
  91. package/tests/unit/check-look-trace.test.js +420 -0
  92. package/tests/unit/check-metric-source.test.js +325 -0
  93. package/tests/unit/check-model-cost.test.js +425 -0
  94. package/tests/unit/check-ports.test.js +46 -2
  95. package/tests/unit/check-source-version.test.js +344 -0
  96. package/tests/unit/check-swarm-receipts.test.js +231 -0
  97. package/tests/unit/check-webhook-contract.test.js +536 -0
  98. package/tests/unit/db-port-rule.test.js +8 -2
  99. package/tests/unit/detection-ladder-registry.test.js +2 -2
  100. package/tests/unit/generator-swarm-contract.test.js +287 -0
  101. package/tests/unit/guard-honest-input-meta.test.js +64 -0
  102. package/tests/unit/honest-failure-rules.test.js +91 -9
  103. package/tests/unit/look-phase-contract.test.js +231 -0
  104. package/tests/unit/negative-conclusion-gate.test.js +300 -0
  105. package/tests/unit/quote-provenance.test.js +122 -0
  106. package/tests/unit/utils.test.js +40 -3
@@ -0,0 +1,287 @@
1
+ 'use strict';
2
+
3
+ // THE DEFECT THIS SUITE CLOSES.
4
+ //
5
+ // A worker that died looks exactly like a worker that is still running: both are silent. Silence
6
+ // therefore reads as "in progress", and a coordinator can report in good faith that a review is
7
+ // running when no review exists — the absence of a receipt is indistinguishable from unfinished
8
+ // work, so it reads as progress. The cure is to declare the RESULT of every parallel unit to be a
9
+ // FILE at a named path: no file, no work, and a machine can say so.
10
+ //
11
+ // MEASURED 2026-09-01, before this change:
12
+ // templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md
13
+ // — 22 matches for parallel-agent dispatch, 0 matches for a file-result requirement.
14
+ // Reproducer: `grep -cE 'parallel|swarm|subagent|Task\(|Task tool' <file>` -> 22
15
+ // `grep -c 'TRACE_PATH' <file>` -> 0
16
+ //
17
+ // The package had ALREADY cured itself: templates/.claude/rules/swarm-file-evidence.md, cited by
18
+ // nine of its own files. What it had not done was put the cure in the GENERATORS — the skills whose
19
+ // output is written into somebody else's project. So every toolkit this package generates inherited
20
+ // a defect its author had already fixed at home. tests/unit/honest-failure-rules.test.js P6 named
21
+ // that gap honestly as deferred; this suite is what makes the deferral over.
22
+ //
23
+ // Three sides, deliberately:
24
+ // 1. IDENTITY — the rule the generator emits is byte-identical to the pre-shipped one.
25
+ // 2. CONTRACT — every generator artifact that dispatches parallel agents carries the contract,
26
+ // proved by the same twelve-predicate checker the first-party seams use.
27
+ // 3. DISCOVERY — no generator file may dispatch parallel agents and be absent from the inventory.
28
+ // Without this, the next uncured generator file ships in silence.
29
+
30
+ const { test, describe } = require('node:test');
31
+ const assert = require('node:assert/strict');
32
+ const fs = require('node:fs');
33
+ const path = require('node:path');
34
+
35
+ const PKG = path.resolve(__dirname, '..', '..');
36
+ const TPL = path.join(PKG, 'templates', '.claude');
37
+ const SKILLS = path.join(TPL, 'skills');
38
+
39
+ /** The skills whose OUTPUT is instructions written into a project this package did not create. */
40
+ const GENERATOR_SKILLS = ['cc-toolkit-generator-enhanced', 'pipeline-forge'];
41
+
42
+ /** The emitted-rule template and the pre-shipped rule it must reproduce verbatim. */
43
+ const RULE = path.join(TPL, 'rules', 'swarm-file-evidence.md');
44
+ const RULE_TEMPLATE = path.join(SKILLS, 'cc-toolkit-generator-enhanced',
45
+ 'references', 'templates', 'swarm-file-evidence.md');
46
+
47
+ /**
48
+ * A generator file that DISPATCHES parallel agents. Deliberately broader than the seam list: this
49
+ * is the discovery net, and a net that only matches what we already fixed would catch nothing.
50
+ */
51
+ const DISPATCH = /Task tool|Task\(|[Ss]pawn (?:parallel|concurrent|one|specialized|multiple)|parallel (?:agents?|Task|validators?|tasks)|parallel Tasks/;
52
+
53
+ /**
54
+ * The inventory, split by how much of the contract each file must carry.
55
+ *
56
+ * `full` — the file IS a dispatch instruction (or the template of one), so it must stand alone:
57
+ * somebody reads only this file and runs a swarm from it.
58
+ * `pointer` — the file SUMMARISES or ORCHESTRATES; it must name the rule and the two silences, so a
59
+ * reader knows a contract exists and where it lives, without paying for it twice.
60
+ *
61
+ * The distinction is not cosmetic. Requiring the full block in an index would spend the
62
+ * always-loaded budget on repetition; accepting a pointer in a command template would ship a
63
+ * dispatch instruction whose contract lives somewhere the executing agent never opens.
64
+ */
65
+ const GENERATOR_SEAMS = [
66
+ ['cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md', 'full'],
67
+ ['cc-toolkit-generator-enhanced/references/templates/feature-lifecycle-ent.md', 'full'],
68
+ ['cc-toolkit-generator-enhanced/references/templates/start-command.md', 'full'],
69
+ ['cc-toolkit-generator-enhanced/references/templates/automation-commands.md', 'full'],
70
+ ['cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md', 'full'],
71
+ ['pipeline-forge/references/patterns-catalog.md', 'full'],
72
+ ['cc-toolkit-generator-enhanced/SKILL.md', 'pointer'],
73
+ ['cc-toolkit-generator-enhanced/references/claude-md-strategy.md', 'pointer'],
74
+ ['cc-toolkit-generator-enhanced/modules/03-generate-p0.md', 'pointer'],
75
+ ['cc-toolkit-generator-enhanced/modules/04-generate-p1.md', 'pointer'],
76
+ ['cc-toolkit-generator-enhanced/modules/06-package-deliver.md', 'pointer'],
77
+ ['pipeline-forge/SKILL.md', 'pointer'],
78
+ ['pipeline-forge/references/self-extracted-patterns.md', 'pointer'],
79
+ ['pipeline-forge/examples/replicate-analysis.md', 'pointer'],
80
+ ];
81
+
82
+ const read = (rel) => fs.readFileSync(path.join(SKILLS, rel), 'utf8');
83
+
84
+ /**
85
+ * The twelve predicates of the file-receipt contract, in the same shape
86
+ * tests/unit/honest-failure-rules.test.js applies to the eight first-party seams. Kept as a local
87
+ * copy on purpose: importing a `.test.js` would execute its suite, and a shared helper module for
88
+ * two callers is a worse trade than a duplicated function that both sides can mutate against.
89
+ */
90
+ function swarmContractProblems(text) {
91
+ const problems = [];
92
+ // MEASURED 2026-09-01: the twelve keyword predicates below are all satisfied by a block retitled
93
+ // "Coordination note" whose opening imperative was softened to "Where convenient". A registered
94
+ // mutation doing exactly that SURVIVED the first version of this suite — so the vocabulary of the
95
+ // contract was tested and its FORCE was not. These two predicates are that gap closed: the
96
+ // heading must say the block is required, and the worker obligation must read `must`, never a
97
+ // suggestion. An optional receipt is not a receipt.
98
+ if (!/Positive file receipt \(required\)/.test(text)) {
99
+ problems.push('the receipt block is not titled as required');
100
+ }
101
+ if (!/must write[\s\S]{0,160}TRACE_PATH/i.test(text)) {
102
+ problems.push('the worker obligation is advisory, not imperative');
103
+ }
104
+ if (!/WORK_UNIT_ID/.test(text)) problems.push('dispatch does not name WORK_UNIT_ID');
105
+ if (!/TRACE_PATH/.test(text)) problems.push('dispatch does not name TRACE_PATH');
106
+ if (!/absolute/i.test(text)) problems.push('TRACE_PATH is not resolved as absolute');
107
+ if (!/(?:unique|different|distinct)[\s\S]{0,100}(?:WORK_UNIT_ID|TRACE_PATH)|(?:WORK_UNIT_ID|TRACE_PATH)[\s\S]{0,100}(?:unique|different|distinct)/i.test(text)) {
108
+ problems.push('trace path is not unique per work unit');
109
+ }
110
+ const write = text.search(/write[\s\S]{0,100}TRACE_PATH/i);
111
+ const reportAfterWrite = write < 0 ? -1 : text.slice(write)
112
+ .search(/one-line[\s\S]{0,40}(?:pointer|report)|(?:pointer|report)[\s\S]{0,40}one-line/i);
113
+ if (write < 0 || reportAfterWrite < 0) problems.push('worker does not write before report');
114
+ const required = [
115
+ [/regular/i, 'regular-file check missing'],
116
+ [/non-symlink|not a symlink/i, 'symlink refusal missing'],
117
+ [/non-(?:empty|whitespace)|substantive/i, 'substantive-body check missing'],
118
+ [/post-launch|after[\s\S]{0,40}launch|pre-dispatch/i, 'freshness boundary missing'],
119
+ [/Status: completed/, 'completed terminal marker missing'],
120
+ [/Status: failed/, 'failed terminal marker missing'],
121
+ [/(?:narrative|chat)[\s\S]{0,80}(?:not|never)[\s\S]{0,40}(?:receipt|result)|silence[\s\S]{0,80}(?:not|never)/i,
122
+ 'narrative or silence can still act as a receipt'],
123
+ [/(?:refuse|block|must not)[\s\S]{0,100}(?:merge|synthesis|aggregation|completion)/i,
124
+ 'invalid receipt does not block aggregation'],
125
+ ];
126
+ for (const [pattern, message] of required) if (!pattern.test(text)) problems.push(message);
127
+ return problems;
128
+ }
129
+
130
+ /**
131
+ * What a POINTER owes its reader: where the contract lives, that a file is the result, and that
132
+ * silence proves nothing. Weaker than the full block by design — and still falsifiable, which is
133
+ * the only property that matters.
134
+ */
135
+ function pointerProblems(text) {
136
+ const problems = [];
137
+ if (!/swarm-file-evidence\.md/.test(text)) problems.push('does not name the rule file');
138
+ if (!/TRACE_PATH/.test(text)) problems.push('does not name TRACE_PATH');
139
+ if (!/silent|silence/i.test(text)) problems.push('does not say that silence proves nothing');
140
+ return problems;
141
+ }
142
+
143
+ /** Every file under the generator skills, as skill-relative POSIX paths. */
144
+ function walk(dir, base, out = []) {
145
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
146
+ const full = path.join(dir, entry.name);
147
+ if (entry.isDirectory()) walk(full, base, out);
148
+ else out.push(path.relative(base, full).split(path.sep).join('/'));
149
+ }
150
+ return out;
151
+ }
152
+
153
+ function generatorFiles() {
154
+ const out = [];
155
+ for (const skill of GENERATOR_SKILLS) out.push(...walk(path.join(SKILLS, skill), SKILLS));
156
+ return out;
157
+ }
158
+
159
+ /**
160
+ * The discovery verdict: which dispatching generator files are NOT in the inventory.
161
+ *
162
+ * This is the half that survives the next change. A contract test proves the files we already
163
+ * fixed stayed fixed; only this one fires when somebody adds a sixteenth generator file that
164
+ * launches agents and requires nothing.
165
+ */
166
+ function uncoveredDispatchers(files, inventory) {
167
+ const declared = new Set(inventory.map(([file]) => file));
168
+ return files.filter((rel) => {
169
+ if (!/\.(md|cjs|js)$/.test(rel)) return false;
170
+ return DISPATCH.test(fs.readFileSync(path.join(SKILLS, rel), 'utf8')) && !declared.has(rel);
171
+ });
172
+ }
173
+
174
+ describe('the swarm file contract reached the GENERATORS, not only the package itself', () => {
175
+ test('G1 - the emitted rule is byte-identical to the pre-shipped rule', () => {
176
+ const canonical = fs.readFileSync(RULE, 'utf8').trimEnd();
177
+ const template = fs.readFileSync(RULE_TEMPLATE, 'utf8');
178
+ const fenced = /```markdown\n([\s\S]*?)\n```/.exec(template);
179
+ assert.ok(fenced, 'the template must carry the rule body in a markdown fence');
180
+ assert.equal(fenced[1], canonical,
181
+ 'the generated rule drifted from the pre-shipped one — two copies of a contract that '
182
+ + 'disagree are worse than one, because each side believes it is authoritative');
183
+
184
+ // Mutation: a single weakened word in the copy must be observable.
185
+ const mutant = template.replace('silence is\nneither progress nor completion',
186
+ 'silence may be read as progress');
187
+ assert.notEqual(mutant, template, 'mutation fixture did not apply — the anchor text moved');
188
+ const mutated = /```markdown\n([\s\S]*?)\n```/.exec(mutant)[1];
189
+ assert.notEqual(mutated, canonical, 'a weakened copy must not compare equal');
190
+ });
191
+
192
+ test('G2 - every full-contract generator seam carries all twelve predicates', () => {
193
+ for (const [file, profile] of GENERATOR_SEAMS) {
194
+ if (profile !== 'full') continue;
195
+ assert.deepEqual(swarmContractProblems(read(file)), [], file);
196
+ }
197
+ });
198
+
199
+ test('G2m - MUTATION: removing the receipt block from a seam turns it red', () => {
200
+ const file = 'cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md';
201
+ const source = read(file);
202
+ assert.deepEqual(swarmContractProblems(source), [], 'precondition: the real file is green');
203
+
204
+ // Strip exactly what the change added: every "Positive file receipt (required)" block.
205
+ const stripped = source.replace(/#### Positive file receipt \(required\)[\s\S]*?non-atomic-write exception\.\n/g, '');
206
+ assert.notEqual(stripped, source, 'mutation fixture did not apply — the block heading moved');
207
+ const problems = swarmContractProblems(stripped);
208
+ assert.ok(problems.length >= 6,
209
+ 'a seam without the block must fail on most predicates, got: ' + problems.join(', '));
210
+ // The two terminal markers are the receipt's whole point: without them a "trace" is a note.
211
+ assert.ok(problems.includes('completed terminal marker missing'), problems.join(', '));
212
+ assert.ok(problems.includes('failed terminal marker missing'), problems.join(', '));
213
+ assert.ok(problems.includes('invalid receipt does not block aggregation'), problems.join(', '));
214
+
215
+ // The FORCE of the block, separately from its vocabulary. Downgrading the heading alone leaves
216
+ // every keyword in place — that is how the registered start-command mutation first survived.
217
+ const softened = source.replaceAll('#### Positive file receipt (required)', '#### Coordination note');
218
+ assert.notEqual(softened, source, 'mutation fixture did not apply — the heading moved');
219
+ assert.deepEqual(swarmContractProblems(softened),
220
+ ['the receipt block is not titled as required'],
221
+ 'a retitled block must fire exactly the mandate predicate and nothing else');
222
+
223
+ // And each predicate must fire INDEPENDENTLY, or one weakened word could hide behind another.
224
+ const weakened = source.replaceAll('Narrative output or\nsilence is never a receipt.', '');
225
+ assert.ok(swarmContractProblems(weakened)
226
+ .includes('narrative or silence can still act as a receipt'),
227
+ 'deleting only the silence clause must fire only that predicate');
228
+ });
229
+
230
+ test('G3 - every pointer names the rule, the trace path, and the silence', () => {
231
+ for (const [file, profile] of GENERATOR_SEAMS) {
232
+ if (profile !== 'pointer') continue;
233
+ assert.deepEqual(pointerProblems(read(file)), [], file);
234
+ }
235
+ assert.deepEqual(pointerProblems('Spawn parallel agents via Task tool.'),
236
+ ['does not name the rule file', 'does not name TRACE_PATH',
237
+ 'does not say that silence proves nothing'],
238
+ 'a bare dispatch instruction must fail all three');
239
+ });
240
+
241
+ test('G4 - DISCOVERY: no generator file dispatches agents outside the inventory', () => {
242
+ const files = generatorFiles();
243
+ assert.ok(files.length > 20, 'the walk found suspiciously few generator files: ' + files.length);
244
+ assert.deepEqual(uncoveredDispatchers(files, GENERATOR_SEAMS), [],
245
+ 'these generator files launch parallel agents and are not covered by the contract');
246
+
247
+ // Mutation: shrink the inventory and the net must catch what it no longer declares.
248
+ const short = GENERATOR_SEAMS.filter(([f]) => f !== 'pipeline-forge/SKILL.md');
249
+ assert.deepEqual(uncoveredDispatchers(files, short), ['pipeline-forge/SKILL.md'],
250
+ 'dropping a declared seam must reappear as an uncovered dispatcher');
251
+ });
252
+
253
+ test('G5 - the deterministic half is registered and wired to no event', () => {
254
+ const { COMPONENTS } = require(path.join(PKG, 'src', 'utils.js'));
255
+ assert.ok(COMPONENTS.hooks.items['check-swarm-receipts'],
256
+ 'unregistered, init/doctor/verify would never require the checker');
257
+ const settings = fs.readFileSync(path.join(TPL, 'settings.json'), 'utf-8');
258
+ assert.ok(!settings.includes('check-swarm-receipts'),
259
+ 'it must not be wired to an event: this package\'s hooks are non-blocking by contract, so a '
260
+ + 'hook could only print — it could never refuse anything');
261
+ const statusline = fs.readFileSync(path.join(TPL, 'hooks', 'statusline.cjs'), 'utf-8');
262
+ const m = statusline.match(/hooksExpected:\s*(\d+)/);
263
+ assert.ok(m, 'statusline must declare hooksExpected');
264
+ assert.equal(Number(m[1]), Object.keys(COMPONENTS.hooks.items).length,
265
+ 'the status line would report a phantom missing hook: ' + m[1]);
266
+ const rule = fs.readFileSync(path.join(TPL, 'rules', 'replicate-pipeline.md'), 'utf-8');
267
+ assert.match(rule, /check-swarm-receipts\.cjs/,
268
+ 'the pre-shipped inventory must list it, or the third counter drifts from the other two');
269
+ });
270
+
271
+ test('G6 - the generator EMITS the rule and the checker, not just prose about them', () => {
272
+ const p0 = read('cc-toolkit-generator-enhanced/modules/03-generate-p0.md');
273
+ assert.match(p0, /Item 9b: swarm-file-evidence\.md rule/,
274
+ 'the P0 item list must carry the rule as a generated artifact');
275
+ assert.match(p0, /\.claude\/rules\/swarm-file-evidence\.md/, 'the output path must be named');
276
+ assert.match(p0, /check-swarm-receipts\.cjs/,
277
+ 'the deterministic half must travel with the text half — text alone is layer 2');
278
+ assert.match(p0, /Do NOT overwrite/,
279
+ 'a project initialised by p-replicator already has the rule; regenerating it would fight the '
280
+ + 'do-not-overwrite gate');
281
+
282
+ const deliver = read('cc-toolkit-generator-enhanced/modules/06-package-deliver.md');
283
+ assert.match(deliver, /CHECK 7b/, 'delivery must gate on the contract having reached the toolkit');
284
+ assert.match(deliver, /FAIL-CLOSED/,
285
+ 'a delivery check that passes on absence is the same substitution this whole feature refuses');
286
+ });
287
+ });
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const PKG = path.resolve(__dirname, '..', '..');
10
+ const HOOKS = path.join(PKG, 'templates', '.claude', 'hooks');
11
+ const TESTS = path.join(PKG, 'tests', 'unit');
12
+ const HONEST_INPUT_TITLE = /clean|чист|honest|exits? 0/i;
13
+
14
+ function testTitles(source) {
15
+ return [...source.matchAll(/\b(?:test|it)\s*\(\s*(['"`])([^\r\n]*?)\1/g)]
16
+ .map((match) => match[2]);
17
+ }
18
+
19
+ function auditHonestInputs(hooksDir, testsDir) {
20
+ const hooks = fs.readdirSync(hooksDir)
21
+ .filter((name) => /^check-.*\.cjs$/.test(name))
22
+ .sort();
23
+ const issues = [];
24
+ for (const hook of hooks) {
25
+ const testFile = hook.replace(/\.cjs$/, '.test.js');
26
+ const testPath = path.join(testsDir, testFile);
27
+ if (!fs.existsSync(testPath)) {
28
+ issues.push({ hook, testFile, reason: 'missing-test' });
29
+ continue;
30
+ }
31
+ const titles = testTitles(fs.readFileSync(testPath, 'utf8'));
32
+ if (!titles.some((title) => HONEST_INPUT_TITLE.test(title))) {
33
+ issues.push({ hook, testFile, reason: 'missing-honest-input-title' });
34
+ }
35
+ }
36
+ return { hooks, issues };
37
+ }
38
+
39
+ test('catalog requires an honest-input title for every shipped check-*.cjs', () => {
40
+ const result = auditHonestInputs(HOOKS, TESTS);
41
+ assert.ok(result.hooks.length > 0, 'an empty hook catalog cannot prove the invariant');
42
+ assert.deepEqual(result.issues, [], JSON.stringify(result.issues, null, 2));
43
+ });
44
+
45
+ test('honest-input catalog probe reports a fake check with no same-named test', () => {
46
+ const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-honest-meta-')));
47
+ const hooksCopy = path.join(root, 'hooks');
48
+ const testsCopy = path.join(root, 'unit');
49
+ try {
50
+ fs.cpSync(HOOKS, hooksCopy, { recursive: true });
51
+ fs.cpSync(TESTS, testsCopy, { recursive: true });
52
+ fs.writeFileSync(path.join(hooksCopy, 'check-zzz.cjs'), "'use strict';\n");
53
+
54
+ const result = auditHonestInputs(hooksCopy, testsCopy);
55
+ assert.ok(result.hooks.includes('check-zzz.cjs'),
56
+ 'the derived enumeration must see a newly added catalog entry');
57
+ assert.deepEqual(result.issues.filter((issue) => issue.hook === 'check-zzz.cjs'), [{
58
+ hook: 'check-zzz.cjs', testFile: 'check-zzz.test.js', reason: 'missing-test',
59
+ }]);
60
+ } finally {
61
+ fs.rmSync(root, { recursive: true, force: true });
62
+ }
63
+ });
64
+
@@ -12,8 +12,43 @@ const PKG = path.resolve(__dirname, '..', '..');
12
12
  const ROOT = path.join(PKG, 'templates', '.claude');
13
13
  const CLI = path.join(PKG, 'bin', 'cli.js');
14
14
  const RULES = ['honest-configuration', 'swarm-file-evidence'];
15
- const FRESH_BEFORE = { files: 28, bytes: 317195, estimatedTokens: 80896.1 };
15
+ /**
16
+ * The PREIMAGE of the current change: the always-loaded corpus (rules + commands + every SKILL.md)
17
+ * as it stood BEFORE this change, measured by measureCorpus() below.
18
+ *
19
+ * It must be RE-PINNED with each deliberate corpus growth, and here is why: the guard measures a
20
+ * DELTA against a "before". Left pinned at an older change's preimage, "before" silently stops
21
+ * meaning "before this change" and the trigger degenerates into a permanent ceiling — the package
22
+ * could never add another rule or command again, however small, because the previous change had
23
+ * already spent most of the budget. That is not the property the guard names ("context delta uses
24
+ * fresh measurement"), and it would be a ceiling nobody chose.
25
+ *
26
+ * `ESTIMATED_TRIGGER` is deliberately NOT touched when the preimage moves. Re-pinning the preimage
27
+ * restores the measurement; changing the threshold would relax it, and those are different acts.
28
+ *
29
+ * Re-pinned 2026-09-01 by the LEAD agent after merging THREE parallel branches — `incoming-webhooks`
30
+ * (2658f5a7), `long-running-job` (22885752) and `model-call-cost` (1f4ea077). Each branch measured
31
+ * its own delta against the SAME preimage and each fitted the trigger ALONE; none of them could see
32
+ * the other two. The preimage is therefore pinned to the state BEFORE ALL THREE, not to the last of
33
+ * them: pinning to the last would let the gate weigh one contribution and stay silent about two,
34
+ * which is the exact form of dishonesty this gate exists to prevent. The owner instructed that
35
+ * `ESTIMATED_TRIGGER` NOT be raised under any result — a red gate telling the truth is worth more
36
+ * than a green one bought by moving the line.
37
+ *
38
+ * The previous preimage was the `embeddable-widget` pin (backlog 23ed5f5c), the last state before
39
+ * those three merged rules. Feature `corpus-budget-compression` earned a fresh preimage on
40
+ * 2026-09-01/02: P5 passed against that OLD pin on the settled tree with live
41
+ * {files:34, bytes:356321, estimatedTokens:95940.35}, a measured delta of 3988.50 below the
42
+ * unchanged 4037.75 trigger.
43
+ * Reproducer: measureCorpus() over templates/.claude/{rules,commands}/*.md plus every SKILL.md.
44
+ * `ESTIMATED_TRIGGER` was not touched.
45
+ */
46
+ const FRESH_BEFORE = { files: 34, bytes: 356321, estimatedTokens: 95940.35 };
16
47
  const ESTIMATED_TRIGGER = 4037.75;
48
+ // Measured 2026-09-01 before corpus-budget-compression. Modules and references are outside
49
+ // measureCorpus(); a 0-byte allowance makes any relocation growth visible instead of budget-free.
50
+ const UNMEASURED_SKILL_SURFACES_BEFORE = { files: 59, bytes: 638309 };
51
+ const UNMEASURED_SKILL_SURFACES_GROWTH_ALLOWANCE = 0;
17
52
  const SEAMS = [
18
53
  ['commands/feature.md', 'both'],
19
54
  ['rules/feature-lifecycle.md', 'both'],
@@ -161,6 +196,29 @@ function walkSkills(dir, out) {
161
196
  }
162
197
  }
163
198
 
199
+ function walkAllFiles(dir, out) {
200
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
201
+ const file = path.join(dir, entry.name);
202
+ if (entry.isDirectory()) walkAllFiles(file, out);
203
+ else out.push(file);
204
+ }
205
+ }
206
+
207
+ function measureUnmeasuredSkillSurfaces(root) {
208
+ const files = [];
209
+ const skills = path.join(root, 'skills');
210
+ for (const skill of fs.readdirSync(skills, { withFileTypes: true }).filter((entry) => entry.isDirectory())) {
211
+ for (const surface of ['modules', 'references']) {
212
+ const dir = path.join(skills, skill.name, surface);
213
+ if (fs.existsSync(dir)) walkAllFiles(dir, files);
214
+ }
215
+ }
216
+ return files.reduce((total, file) => ({
217
+ files: total.files + 1,
218
+ bytes: total.bytes + fs.readFileSync(file).length,
219
+ }), { files: 0, bytes: 0 });
220
+ }
221
+
164
222
  function measureCorpus(root) {
165
223
  const files = [];
166
224
  for (const dir of ['rules', 'commands']) {
@@ -248,11 +306,11 @@ test('P0 - ADR boundary is one feature with exactly two non-duplicated rules', (
248
306
  }
249
307
  });
250
308
 
251
- test('P1 - registry and rule filesystem agree on the nine-rule contract', () => {
309
+ test('P1 - registry and rule filesystem agree on the thirteen-rule contract', () => {
252
310
  const registry = JSON.parse(read('src/rule-components.json'));
253
311
  const files = fs.readdirSync(path.join(ROOT, 'rules'))
254
312
  .filter((name) => name.endsWith('.md')).map((name) => name.slice(0, -3)).sort();
255
- assert.equal(Object.keys(registry).length, 9, 'the canonical registry must carry nine rules');
313
+ assert.equal(Object.keys(registry).length, 13, 'the canonical registry must carry thirteen rules');
256
314
  assert.deepEqual(Object.keys(registry).sort(), files, 'registry and canonical rule files diverged');
257
315
  for (const slug of RULES) assert.ok(registry[slug], `registry missing ${slug}`);
258
316
  });
@@ -379,18 +437,32 @@ test('P5 - context delta uses fresh measurement and honest load-surface labels',
379
437
  'unreadable or mislabeled measurement must not become a zero delta');
380
438
  });
381
439
 
382
- test('P6 - the owned inventory is eight first-party seams and does not imply generator coverage', () => {
440
+ test('P6 - the owned inventory is eight first-party seams, and the generators have their own', () => {
383
441
  const files = SEAMS.map(([file]) => file);
384
442
  assert.deepEqual(seamInventoryProblems(files), []);
385
443
  assert.equal(seamInventoryProblems(files.slice(0, -1)).length, 1,
386
444
  'omitting an owned seam must be observable');
387
445
  assert.equal(seamInventoryProblems([...files, 'skills/generator/example.md']).length, 1,
388
- 'a deferred generator must not be relabeled as implemented');
446
+ 'this list is FIRST-PARTY only; a generator seam belongs to the generator inventory');
447
+
448
+ // Until 2026-09-01 this test asserted that generator coverage was DEFERRED, and said so honestly.
449
+ // It is no longer deferred: the contract now travels with the toolkits this package generates,
450
+ // and its inventory, its twelve-predicate check, its discovery net and its mutation proof live in
451
+ // tests/unit/generator-swarm-contract.test.js. Leaving the old wording here would have turned an
452
+ // honest deferral into a false one — the same substitution the whole feature refuses.
453
+ const generatorSuite = path.join(PKG, 'tests', 'unit', 'generator-swarm-contract.test.js');
454
+ assert.ok(fs.existsSync(generatorSuite),
455
+ 'the generator half must be proved somewhere, or this file is silently the only claim');
456
+ const generatorSource = fs.readFileSync(generatorSuite, 'utf8');
457
+ assert.match(generatorSource, /const GENERATOR_SEAMS = \[/,
458
+ 'the generator inventory must be enumerated, not implied');
459
+ assert.match(generatorSource, /uncoveredDispatchers/,
460
+ 'an inventory without a discovery net cannot notice the NEXT uncured generator file');
389
461
  });
390
462
 
391
- test('P7 - current package inventories derive the nine-rule count', () => {
392
- assert.deepEqual(countClaimProblems('Pre-shipped: 9 rules. Rules ●9+2. **9 rules**', 9), []);
393
- assert.equal(countClaimProblems('Pre-shipped: 7 rules.', 9).length, 1,
463
+ test('P7 - current package inventories derive the thirteen-rule count', () => {
464
+ assert.deepEqual(countClaimProblems('Pre-shipped: 12 rules. Rules ●11+2. **12 rules**', 11), []);
465
+ assert.equal(countClaimProblems('Pre-shipped: 7 rules.', 11).length, 1,
394
466
  'the deliberately stale count fixture must fire');
395
467
  const docs = [
396
468
  'README.md',
@@ -407,7 +479,7 @@ test('P7 - current package inventories derive the nine-rule count', () => {
407
479
  'templates/.claude/commands/replicate.md',
408
480
  'templates/.claude/rules/replicate-pipeline.md',
409
481
  ];
410
- const wrong = docs.flatMap((file) => countClaimProblems(read(file), 9).map((problem) => `${file}: ${problem}`));
482
+ const wrong = docs.flatMap((file) => countClaimProblems(read(file), 11).map((problem) => `${file}: ${problem}`));
411
483
  assert.deepEqual(wrong, []);
412
484
  });
413
485
 
@@ -490,3 +562,13 @@ test('P10 - fresh init pack registry manifest and SBOM carry both rules', () =>
490
562
  `SBOM missing or stale for ${relative}`);
491
563
  }
492
564
  });
565
+
566
+ test('P11 - unmeasured skill surfaces did not absorb the compressed corpus', () => {
567
+ const live = measureUnmeasuredSkillSurfaces(ROOT);
568
+ assert.equal(live.files, UNMEASURED_SKILL_SURFACES_BEFORE.files,
569
+ 'unmeasured skill module/reference inventory changed; re-measure and justify the new surface');
570
+ assert.ok(live.bytes <= UNMEASURED_SKILL_SURFACES_BEFORE.bytes
571
+ + UNMEASURED_SKILL_SURFACES_GROWTH_ALLOWANCE,
572
+ `unmeasured skill modules/references grew from ${UNMEASURED_SKILL_SURFACES_BEFORE.bytes}`
573
+ + ` to ${live.bytes} bytes; always-loaded text may have been relocated outside measureCorpus()`);
574
+ });