@dzhechkov/p-replicator 1.10.4 → 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 (136) hide show
  1. package/.dz-manifest.json +312 -76
  2. package/CHANGELOG.md +232 -0
  3. package/MULTIPLATFORM_ROADMAP.md +1 -1
  4. package/README/eng/01_quickstart.md +3 -3
  5. package/README/eng/02_user_guide.md +1 -1
  6. package/README/eng/03_admin_guide.md +2 -2
  7. package/README/eng/04_api_reference.md +11 -5
  8. package/README/eng/05_architecture.md +1 -1
  9. package/README/eng/README.md +2 -1
  10. package/README/ru/01_quickstart.md +3 -3
  11. package/README/ru/02_user_guide.md +1 -1
  12. package/README/ru/03_admin_guide.md +2 -2
  13. package/README/ru/04_api_reference.md +11 -5
  14. package/README/ru/05_architecture.md +1 -1
  15. package/README/ru/README.md +2 -1
  16. package/README/ru/html/index.html +9 -9
  17. package/README.md +278 -39
  18. package/package.json +5 -4
  19. package/sbom.json +665 -75
  20. package/scripts/check-pipeline-gaps.sh +413 -0
  21. package/src/commands/doctor.js +94 -4
  22. package/src/commands/init.js +1 -1
  23. package/src/rule-components.json +15 -0
  24. package/src/utils.js +35 -11
  25. package/templates/.claude/agents/harvest-coordinator.md +10 -1
  26. package/templates/.claude/agents/product-discoverer.md +38 -0
  27. package/templates/.claude/agents/replicate-coordinator.md +11 -1
  28. package/templates/.claude/commands/feature.md +81 -9
  29. package/templates/.claude/commands/go.md +9 -0
  30. package/templates/.claude/commands/harvest.md +39 -3
  31. package/templates/.claude/commands/myinsights.md +21 -26
  32. package/templates/.claude/commands/replicate.md +171 -37
  33. package/templates/.claude/commands/start.md +29 -0
  34. package/templates/.claude/hooks/capture-source-path.cjs +795 -0
  35. package/templates/.claude/hooks/check-canon.cjs +493 -0
  36. package/templates/.claude/hooks/check-embed-contract.cjs +374 -0
  37. package/templates/.claude/hooks/check-external-deps.cjs +288 -0
  38. package/templates/.claude/hooks/check-file-ownership.cjs +424 -0
  39. package/templates/.claude/hooks/check-handoff-manifest.cjs +367 -0
  40. package/templates/.claude/hooks/check-job-contract.cjs +501 -0
  41. package/templates/.claude/hooks/check-look-origin.cjs +240 -0
  42. package/templates/.claude/hooks/check-look-trace.cjs +385 -0
  43. package/templates/.claude/hooks/check-metric-source.cjs +296 -0
  44. package/templates/.claude/hooks/check-model-cost.cjs +470 -0
  45. package/templates/.claude/hooks/check-ports.cjs +434 -24
  46. package/templates/.claude/hooks/check-source-version.cjs +312 -0
  47. package/templates/.claude/hooks/check-swarm-receipts.cjs +197 -0
  48. package/templates/.claude/hooks/check-webhook-contract.cjs +535 -0
  49. package/templates/.claude/hooks/session-insights.cjs +158 -25
  50. package/templates/.claude/hooks/statusline.cjs +2 -2
  51. package/templates/.claude/hooks/write-insight.cjs +253 -0
  52. package/templates/.claude/rules/cost-of-detection-ladder.md +96 -0
  53. package/templates/.claude/rules/docker-ports.md +41 -19
  54. package/templates/.claude/rules/embeddable-widget.md +73 -0
  55. package/templates/.claude/rules/feature-lifecycle.md +13 -3
  56. package/templates/.claude/rules/honest-configuration.md +54 -0
  57. package/templates/.claude/rules/incoming-webhooks.md +99 -0
  58. package/templates/.claude/rules/insights-capture.md +10 -5
  59. package/templates/.claude/rules/long-running-job.md +73 -0
  60. package/templates/.claude/rules/model-call-cost.md +85 -0
  61. package/templates/.claude/rules/replicate-pipeline.md +123 -52
  62. package/templates/.claude/rules/skill-interface-protocol.md +1 -0
  63. package/templates/.claude/rules/swarm-file-evidence.md +46 -0
  64. package/templates/.claude/settings.json +13 -1
  65. package/templates/.claude/skills/brutal-honesty-review/SKILL.md +9 -0
  66. package/templates/.claude/skills/cc-toolkit-generator-enhanced/SKILL.md +4 -0
  67. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +46 -1
  68. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +7 -1
  69. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/06-package-deliver.md +20 -2
  70. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/claude-md-strategy.md +7 -0
  71. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/automation-commands.md +17 -0
  72. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle-ent.md +43 -5
  73. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +43 -7
  74. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/start-command.md +19 -1
  75. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md +151 -0
  76. package/templates/.claude/skills/goap-research-ed25519/SKILL.md +37 -22
  77. package/templates/.claude/skills/goap-research-ed25519/references/negative-results.md +94 -0
  78. package/templates/.claude/skills/goap-research-ed25519/scripts/check_report_evidence.py +368 -4
  79. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +122 -5
  80. package/templates/.claude/skills/goap-research-ed25519/scripts/evidence_fetch.py +33 -16
  81. package/templates/.claude/skills/goap-research-ed25519/scripts/quote_provenance.py +342 -0
  82. package/templates/.claude/skills/goap-research-ed25519/scripts/test_ed25519_verifier.py +60 -0
  83. package/templates/.claude/skills/goap-research-ed25519/scripts/test_evidence_provenance.py +139 -6
  84. package/templates/.claude/skills/goap-research-ed25519/scripts/test_quote_provenance.py +274 -0
  85. package/templates/.claude/skills/goap-research-ed25519/scripts/test_suite_completeness.py +2 -1
  86. package/templates/.claude/skills/knowledge-extractor/SKILL.md +4 -0
  87. package/templates/.claude/skills/knowledge-extractor/modules/01-agent-review.md +16 -5
  88. package/templates/.claude/skills/pipeline-forge/SKILL.md +18 -23
  89. package/templates/.claude/skills/pipeline-forge/examples/replicate-analysis.md +7 -2
  90. package/templates/.claude/skills/pipeline-forge/references/patterns-catalog.md +19 -1
  91. package/templates/.claude/skills/pipeline-forge/references/self-extracted-patterns.md +17 -6
  92. package/templates/.claude/skills/pipeline-forge/references/skill-anatomy.md +0 -1
  93. package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +21 -1
  94. package/templates/.claude/skills/sparc-prd-mini/SKILL.md +234 -716
  95. package/tests/e2e/lifecycle.test.js +55 -9
  96. package/tests/e2e/packed-insights-writer.test.js +308 -0
  97. package/tests/fixtures/prep-traceability-fixture/docs/features/order-refund/01_specification.md +29 -0
  98. package/tests/fixtures/prep-traceability-fixture/docs/features/order-refund/02_pseudocode.md +57 -0
  99. package/tests/snapshot/baseline.json +72 -46
  100. package/tests/snapshot/templates.test.js +47 -0
  101. package/tests/unit/absence-is-not-emptiness.test.js +15 -1
  102. package/tests/unit/capture-source-path.test.js +492 -0
  103. package/tests/unit/check-canon.test.js +403 -0
  104. package/tests/unit/check-embed-contract.test.js +422 -0
  105. package/tests/unit/check-external-deps.test.js +363 -0
  106. package/tests/unit/check-file-ownership.test.js +388 -0
  107. package/tests/unit/check-handoff-manifest.test.js +410 -0
  108. package/tests/unit/check-job-contract.test.js +514 -0
  109. package/tests/unit/check-look-origin.test.js +180 -0
  110. package/tests/unit/check-look-trace.test.js +420 -0
  111. package/tests/unit/check-metric-source.test.js +325 -0
  112. package/tests/unit/check-model-cost.test.js +425 -0
  113. package/tests/unit/check-pipeline-gaps.test.js +94 -0
  114. package/tests/unit/check-ports.test.js +773 -2
  115. package/tests/unit/check-source-version.test.js +344 -0
  116. package/tests/unit/check-swarm-receipts.test.js +231 -0
  117. package/tests/unit/check-webhook-contract.test.js +536 -0
  118. package/tests/unit/db-port-rule.test.js +43 -6
  119. package/tests/unit/detection-ladder-contract.test.js +302 -0
  120. package/tests/unit/detection-ladder-registry.test.js +52 -0
  121. package/tests/unit/doctor-insight-flow.test.js +315 -0
  122. package/tests/unit/external-dependency-check.test.js +19 -19
  123. package/tests/unit/generator-swarm-contract.test.js +287 -0
  124. package/tests/unit/guard-honest-input-meta.test.js +64 -0
  125. package/tests/unit/honest-failure-rules.test.js +574 -0
  126. package/tests/unit/hooks-project-anchored.test.js +67 -3
  127. package/tests/unit/insights-docs-tell-the-truth.test.js +52 -31
  128. package/tests/unit/insights-dz-delegation.test.js +197 -0
  129. package/tests/unit/insights-writer.test.js +285 -0
  130. package/tests/unit/look-phase-contract.test.js +231 -0
  131. package/tests/unit/negative-conclusion-gate.test.js +300 -0
  132. package/tests/unit/quote-provenance.test.js +122 -0
  133. package/tests/unit/shipped-suite-context.test.js +3 -1
  134. package/tests/unit/traceability-machine-ids.test.js +413 -0
  135. package/tests/unit/traceability-negative-fixture.test.js +322 -0
  136. package/tests/unit/utils.test.js +40 -2
@@ -7,9 +7,8 @@
7
7
  // contact-with-reality defect in this pipeline; everything else it checks, it can check by reading
8
8
  // its own output.
9
9
  //
10
- // TWO honesty constraints bind this suite, both from the field report:
11
- // 1. check-pipeline-gaps.sh, which the report cites, DOES NOT EXIST. P7 keeps it uncited.
12
- // 2. No external fact may be baked in. What an API can do DRIFTS: a fixture asserting one would
10
+ // One honesty constraint binds this suite from the field report:
11
+ // No external fact may be baked in. What an API can do DRIFTS: a fixture asserting one would
13
12
  // fail when the world changes and pass when our own rule breaks. P6 asserts the ABSENCE of
14
13
  // vendor names, and no assertion here mentions a capability of any real service.
15
14
 
@@ -188,22 +187,23 @@ describe('the pipeline asks whether the outside world can do what a requirement
188
187
  'doc-validator.md says ' + stated[1] + ' but tables ' + inAgent.size);
189
188
  });
190
189
 
191
- test('P7 — nothing cites check-pipeline-gaps.sh, which does not exist', () => {
192
- // The report cites it. MEASURED — a repo-wide find returns nothing. Pointing a reader at a
193
- // script that is not there is a worse failure than the gap it was cited to close.
194
- for (const f of [SPARC, REPLICATE]) {
195
- assert.ok(!read(f).includes('check-pipeline-gaps'),
196
- f + ' references a script that does not exist in this repository');
197
- }
198
- // The premise, asserted rather than assumed: if someone ever ADDS the script, this goes red and
199
- // whoever added it has to revisit the rule instead of leaving a stale prohibition behind.
190
+ test('P7 — check-pipeline-gaps is package-owned and deliberately wired', () => {
200
191
  const pkgRoot = path.join(__dirname, '..', '..');
201
- const candidates = ['check-pipeline-gaps.sh', path.join('scripts', 'check-pipeline-gaps.sh'),
202
- path.join('bin', 'check-pipeline-gaps.sh')];
203
- for (const c of candidates) {
204
- assert.ok(!fs.existsSync(path.join(pkgRoot, c)),
205
- 'the script now exists at ' + c + ' — this rule was written on the premise that it does '
206
- + 'not, so revisit it rather than deleting this assertion');
207
- }
192
+ const canonical = path.join(pkgRoot, 'scripts', 'check-pipeline-gaps.sh');
193
+ const projection = path.join(TPL, 'hooks', 'check-pipeline-gaps.sh');
194
+ const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
195
+ assert.ok(fs.statSync(canonical).isFile(), 'the package canon must exist');
196
+ assert.equal(fs.existsSync(projection), false,
197
+ 'the Node-only hooks surface must not duplicate the packaged shell utility');
198
+ assert.ok(pkg.files.includes('scripts/check-pipeline-gaps.sh'),
199
+ 'the exact checker path must be in the npm allowlist');
200
+ assert.match(read('commands/feature.md'),
201
+ /require\.resolve\('@dzhechkov\/p-replicator\/scripts\/check-pipeline-gaps\.sh'\)/,
202
+ '/feature must resolve the checker from the installed package');
203
+ assert.doesNotMatch(read('commands/feature.md'), /\.claude\/hooks\/check-pipeline-gaps\.sh/);
204
+ assert.match(read(SPARC), /FR\/NFR\/AC machine-key wire format/,
205
+ 'the authoring skill must define the wire format consumed by the checker');
206
+ assert.ok(!read('settings.json').includes('check-pipeline-gaps'),
207
+ 'the deliberate blocking gate must not be registered as a non-blocking event hook');
208
208
  });
209
209
  });
@@ -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
+