@dzhechkov/p-replicator 1.12.0 → 1.13.2

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 (122) hide show
  1. package/.dz-manifest.json +281 -65
  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 +150 -9
  17. package/bin/cli.js +0 -0
  18. package/package.json +10 -11
  19. package/sbom.json +604 -64
  20. package/scripts/check-pipeline-gaps.sh +510 -21
  21. package/src/commands/init.js +1 -1
  22. package/src/rule-components.json +5 -1
  23. package/src/utils.js +33 -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 +72 -6
  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-review-contract.cjs +205 -0
  44. package/templates/.claude/hooks/check-source-version.cjs +312 -0
  45. package/templates/.claude/hooks/check-swarm-receipts.cjs +197 -0
  46. package/templates/.claude/hooks/check-webhook-contract.cjs +535 -0
  47. package/templates/.claude/hooks/statusline.cjs +2 -2
  48. package/templates/.claude/rules/embeddable-widget.md +73 -0
  49. package/templates/.claude/rules/feature-lifecycle.md +5 -6
  50. package/templates/.claude/rules/incoming-webhooks.md +99 -0
  51. package/templates/.claude/rules/long-running-job.md +73 -0
  52. package/templates/.claude/rules/model-call-cost.md +85 -0
  53. package/templates/.claude/rules/replicate-pipeline.md +122 -52
  54. package/templates/.claude/skills/brutal-honesty-review/SKILL.md +9 -0
  55. package/templates/.claude/skills/cc-toolkit-generator-enhanced/SKILL.md +4 -0
  56. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +46 -1
  57. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +7 -1
  58. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/06-package-deliver.md +20 -2
  59. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/claude-md-strategy.md +7 -0
  60. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/automation-commands.md +17 -0
  61. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle-ent.md +43 -5
  62. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +43 -7
  63. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/start-command.md +19 -1
  64. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md +151 -0
  65. package/templates/.claude/skills/goap-research-ed25519/SKILL.md +37 -22
  66. package/templates/.claude/skills/goap-research-ed25519/references/negative-results.md +94 -0
  67. package/templates/.claude/skills/goap-research-ed25519/scripts/check_report_evidence.py +368 -4
  68. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +122 -5
  69. package/templates/.claude/skills/goap-research-ed25519/scripts/evidence_fetch.py +33 -16
  70. package/templates/.claude/skills/goap-research-ed25519/scripts/quote_provenance.py +342 -0
  71. package/templates/.claude/skills/goap-research-ed25519/scripts/test_ed25519_verifier.py +60 -0
  72. package/templates/.claude/skills/goap-research-ed25519/scripts/test_evidence_provenance.py +139 -6
  73. package/templates/.claude/skills/goap-research-ed25519/scripts/test_quote_provenance.py +274 -0
  74. package/templates/.claude/skills/goap-research-ed25519/scripts/test_suite_completeness.py +2 -1
  75. package/templates/.claude/skills/knowledge-extractor/SKILL.md +4 -0
  76. package/templates/.claude/skills/pipeline-forge/SKILL.md +18 -23
  77. package/templates/.claude/skills/pipeline-forge/examples/replicate-analysis.md +7 -2
  78. package/templates/.claude/skills/pipeline-forge/references/patterns-catalog.md +19 -1
  79. package/templates/.claude/skills/pipeline-forge/references/self-extracted-patterns.md +17 -6
  80. package/templates/.claude/skills/pipeline-forge/references/skill-anatomy.md +0 -1
  81. package/templates/.claude/skills/requirements-validator/SKILL.md +18 -5
  82. package/templates/.claude/skills/requirements-validator/references/feature-report-contracts.md +67 -0
  83. package/templates/.claude/skills/requirements-validator/references/scoring-system.md +15 -6
  84. package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +21 -1
  85. package/templates/.claude/skills/sparc-prd-mini/SKILL.md +173 -725
  86. package/tests/e2e/feature-contour.test.js +176 -0
  87. package/tests/fixtures/feature-contour/docs/features/demo-gate/01_specification.md +15 -0
  88. package/tests/fixtures/feature-contour/docs/features/demo-gate/02_pseudocode.md +19 -0
  89. package/tests/fixtures/feature-contour/docs/features/demo-gate/03_architecture.md +3 -0
  90. package/tests/fixtures/feature-contour/docs/features/demo-gate/04_refinement.md +3 -0
  91. package/tests/fixtures/feature-contour/docs/features/demo-gate/05_completion.md +7 -0
  92. package/tests/fixtures/feature-contour/docs/features/demo-gate/review-report.md +11 -0
  93. package/tests/fixtures/feature-contour/docs/features/demo-gate/validation-report.md +10 -0
  94. package/tests/fixtures/feature-contour/tests/demo.test.js +16 -0
  95. package/tests/snapshot/baseline.json +64 -40
  96. package/tests/unit/capture-source-path.test.js +492 -0
  97. package/tests/unit/check-canon.test.js +403 -0
  98. package/tests/unit/check-embed-contract.test.js +422 -0
  99. package/tests/unit/check-external-deps.test.js +363 -0
  100. package/tests/unit/check-file-ownership.test.js +388 -0
  101. package/tests/unit/check-handoff-manifest.test.js +410 -0
  102. package/tests/unit/check-job-contract.test.js +514 -0
  103. package/tests/unit/check-look-origin.test.js +180 -0
  104. package/tests/unit/check-look-trace.test.js +420 -0
  105. package/tests/unit/check-metric-source.test.js +325 -0
  106. package/tests/unit/check-model-cost.test.js +425 -0
  107. package/tests/unit/check-ports.test.js +46 -2
  108. package/tests/unit/check-review-contract.test.js +181 -0
  109. package/tests/unit/check-source-version.test.js +344 -0
  110. package/tests/unit/check-swarm-receipts.test.js +231 -0
  111. package/tests/unit/check-webhook-contract.test.js +536 -0
  112. package/tests/unit/db-port-rule.test.js +8 -2
  113. package/tests/unit/detection-ladder-registry.test.js +2 -2
  114. package/tests/unit/generator-swarm-contract.test.js +287 -0
  115. package/tests/unit/guard-honest-input-meta.test.js +64 -0
  116. package/tests/unit/honest-failure-rules.test.js +106 -9
  117. package/tests/unit/look-phase-contract.test.js +231 -0
  118. package/tests/unit/negative-conclusion-gate.test.js +300 -0
  119. package/tests/unit/quote-provenance.test.js +122 -0
  120. package/tests/unit/sync-templates-guard.test.js +46 -3
  121. package/tests/unit/traceability-completion-gate.test.js +267 -0
  122. package/tests/unit/utils.test.js +40 -3
@@ -0,0 +1,231 @@
1
+ 'use strict';
2
+
3
+ // The deterministic half of the swarm contract. The rule text is prose a model reads — layer 2,
4
+ // violated in silence. This is layer 1, and only for whoever runs it.
5
+ //
6
+ // The load-bearing property is the THIRD exit code, for the same reason it is in check-ports and
7
+ // check-growth-trace: a checker that answers "clean" when it could not look converts an unknown
8
+ // into a reassurance. Here the unknown has a specific and very common cause — a worker whose PID is
9
+ // still alive and whose trace has not appeared yet. That is not delivery and it is not failure; it
10
+ // is "not yet", and reading it as either would be a lie in a different direction each time.
11
+ //
12
+ // These are BEHAVIOUR tests: the real utility, real files, real exit codes.
13
+
14
+ const { test, describe } = require('node:test');
15
+ const assert = require('node:assert/strict');
16
+ const { spawnSync } = require('node:child_process');
17
+ const fs = require('node:fs');
18
+ const os = require('node:os');
19
+ const path = require('node:path');
20
+
21
+ const PKG = path.resolve(__dirname, '..', '..');
22
+ const TPL = path.join(PKG, 'templates', '.claude');
23
+ const CHECK = path.join(TPL, 'hooks', 'check-swarm-receipts.cjs');
24
+
25
+ /**
26
+ * Build a throwaway run directory, write the traces described, and run the real checker.
27
+ *
28
+ * `traces` maps a unit id to a body, or to `null` for "this unit never wrote anything". The
29
+ * manifest's `launchMs` defaults to one second in the PAST, so a file written by this helper is
30
+ * legitimately post-launch — the freshness boundary is exercised explicitly by its own case.
31
+ */
32
+ function check({ units, traces = {}, launchMs, manifest: override, raw }) {
33
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-receipts-')));
34
+ try {
35
+ const resolved = (units || []).map((u) => ({
36
+ ...u,
37
+ tracePath: u.tracePath === undefined ? path.join(dir, u.workUnitId + '.md') : u.tracePath,
38
+ }));
39
+ for (const unit of resolved) {
40
+ const body = traces[unit.workUnitId];
41
+ if (body === undefined || body === null) continue;
42
+ fs.writeFileSync(unit.tracePath, body);
43
+ }
44
+ const file = path.join(dir, 'receipts.json');
45
+ if (raw !== undefined) fs.writeFileSync(file, raw);
46
+ else {
47
+ const m = override || { runId: 'r1', launchMs: launchMs ?? Date.now() - 1000, units: resolved };
48
+ fs.writeFileSync(file, JSON.stringify(m));
49
+ }
50
+ const r = spawnSync(process.execPath, [CHECK, file], { encoding: 'utf8' });
51
+ return { code: r.status, out: (r.stdout || '') + (r.stderr || ''), dir };
52
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
53
+ }
54
+
55
+ const DONE = 'Implemented the parser and its two tests.\nStatus: completed';
56
+ const FAILED = 'Could not build: the fixture is missing.\nStatus: failed';
57
+
58
+ describe('the swarm-receipt checker answers three questions, and never confuses two of them', () => {
59
+ test('P1 - every unit delivered a terminal completed receipt: exit 0', () => {
60
+ const r = check({
61
+ units: [{ workUnitId: 'api' }, { workUnitId: 'docs' }],
62
+ traces: { api: DONE, docs: DONE },
63
+ });
64
+ assert.equal(r.code, 0, 'two good receipts must pass: ' + r.out);
65
+ assert.match(r.out, /все 2 квитанций/, r.out);
66
+ assert.match(r.out, /ДОСТАВЛЕНА, а не что она верна/,
67
+ 'the receipt proves delivery, never correctness — the limit must be stated');
68
+ });
69
+
70
+ test('P2 - a missing trace from a worker with no liveness claim: exit 1, unit named', () => {
71
+ const r = check({
72
+ units: [{ workUnitId: 'api' }, { workUnitId: 'docs' }],
73
+ traces: { api: DONE },
74
+ });
75
+ assert.equal(r.code, 1, 'a missing receipt must refuse aggregation: ' + r.out);
76
+ assert.match(r.out, /docs \[undelivered\/missing\]/, r.out);
77
+ assert.match(r.out, /ЗАПРЕЩЕНЫ/, 'the refusal must be stated, not implied: ' + r.out);
78
+ });
79
+
80
+ test('P3 - a live PID with no trace is exit 2, a dead PID with no trace is exit 1', () => {
81
+ // The asymmetry is the whole rule: liveness may only EXTEND waiting; it can never deliver.
82
+ const live = check({ units: [{ workUnitId: 'api', pid: process.pid }] });
83
+ assert.equal(live.code, 2, 'a live worker is "not yet", never "clean": ' + live.out);
84
+ assert.match(live.out, /positive-liveness-only/, live.out);
85
+
86
+ // A pid that is certainly gone: spawn a child, wait for it, then reuse its pid.
87
+ const gone = spawnSync(process.execPath, ['-e', 'process.exit(0)']);
88
+ const dead = check({ units: [{ workUnitId: 'api', pid: gone.pid }] });
89
+ assert.equal(dead.code, 1, 'a dead worker with no trace is a delivered failure: ' + dead.out);
90
+ assert.match(dead.out, /dead-worker/, dead.out);
91
+ });
92
+
93
+ test('P4 - empty, whitespace-only, and marker-only bodies are all undelivered', () => {
94
+ for (const [label, body, reason] of [
95
+ ['empty', '', 'empty'],
96
+ ['whitespace', ' \n\t\n', 'empty'],
97
+ ['marker with no work above it', 'Status: completed', 'empty-payload'],
98
+ ]) {
99
+ const r = check({ units: [{ workUnitId: 'api' }], traces: { api: body } });
100
+ assert.equal(r.code, 1, label + ' must not pass: ' + r.out);
101
+ assert.match(r.out, new RegExp(reason), label + ': ' + r.out);
102
+ }
103
+ });
104
+
105
+ test('P5 - a body without the terminal line is PARTIAL, not success', () => {
106
+ const r = check({
107
+ units: [{ workUnitId: 'api' }],
108
+ traces: { api: 'I am half way through writing this file.' },
109
+ });
110
+ assert.equal(r.code, 1, 'a partial write must not read as delivery: ' + r.out);
111
+ assert.match(r.out, /non-terminal/, r.out);
112
+ });
113
+
114
+ test('P6 - a delivered Status: failed blocks the aggregate exactly like a missing one', () => {
115
+ const r = check({ units: [{ workUnitId: 'api' }], traces: { api: FAILED } });
116
+ assert.equal(r.code, 1, 'a delivered failure is still a refusal: ' + r.out);
117
+ assert.match(r.out, /delivered-failure/, r.out);
118
+ });
119
+
120
+ test('P7 - a file that predates the launch is STALE, however good it looks', () => {
121
+ // The single case a naive checker gets wrong: last run's trace, still on disk, fully terminal.
122
+ const r = check({
123
+ units: [{ workUnitId: 'api' }],
124
+ traces: { api: DONE },
125
+ launchMs: Date.now() + 60_000, // launch is in the future relative to the file
126
+ });
127
+ assert.equal(r.code, 1, 'a pre-launch file is not this run\'s evidence: ' + r.out);
128
+ assert.match(r.out, /stale/, r.out);
129
+ });
130
+
131
+ test('P8 - a symlink is not a regular file, even when its target is perfect', () => {
132
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-link-')));
133
+ try {
134
+ const real = path.join(dir, 'real.md');
135
+ const link = path.join(dir, 'api.md');
136
+ fs.writeFileSync(real, DONE);
137
+ fs.symlinkSync(real, link);
138
+ const file = path.join(dir, 'receipts.json');
139
+ fs.writeFileSync(file, JSON.stringify({
140
+ runId: 'r1', launchMs: Date.now() - 1000,
141
+ units: [{ workUnitId: 'api', tracePath: link }],
142
+ }));
143
+ const r = spawnSync(process.execPath, [CHECK, file], { encoding: 'utf8' });
144
+ assert.equal(r.status, 1, 'a symlinked trace must be refused: ' + r.stdout);
145
+ assert.match(r.stdout, /not-regular/, r.stdout);
146
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
147
+ });
148
+
149
+ test('P9 - a malformed ASSIGNMENT is exit 2, because the dispatch was wrong, not the delivery', () => {
150
+ const cases = [
151
+ [{ units: [{ workUnitId: 'api', tracePath: 'relative/path.md' }] }, /assignment/,
152
+ 'a relative TRACE_PATH cannot be verified'],
153
+ [{ units: [{ workUnitId: 'api' }, { workUnitId: 'api' }] }, /WORK_UNIT_ID повторяется/,
154
+ 'one id for two units would let one receipt clear both'],
155
+ [{ manifest: { launchMs: 1, units: [] } }, /ноль рабочих единиц/,
156
+ 'an empty swarm has nothing to prove'],
157
+ [{ manifest: { units: [{ workUnitId: 'a', tracePath: '/tmp/a.md' }] } }, /launchMs/,
158
+ 'without a launch instant, freshness is unknowable'],
159
+ [{ raw: 'not json at all' }, /не разбирается как JSON/, 'a broken manifest is an unknown'],
160
+ [{ raw: '[1,2,3]' }, /не является объектом/, 'an array is not a manifest'],
161
+ ];
162
+ for (const [input, pattern, why] of cases) {
163
+ const r = check(input);
164
+ assert.equal(r.code, 2, why + ' — got ' + r.code + ': ' + r.out);
165
+ assert.match(r.out, /проверка НЕ выполнена/, why + ': ' + r.out);
166
+ assert.match(r.out, pattern, why + ': ' + r.out);
167
+ }
168
+ });
169
+
170
+ test('P9b - two units sharing one TRACE_PATH is exit 2, not a lucky pass', () => {
171
+ // The second writer overwrites the first and BOTH report success — the silent lost update.
172
+ const r = check({
173
+ units: [{ workUnitId: 'a', tracePath: '/tmp/shared-trace.md' },
174
+ { workUnitId: 'b', tracePath: '/tmp/shared-trace.md' }],
175
+ });
176
+ assert.equal(r.code, 2, 'a shared path is a malformed dispatch: ' + r.out);
177
+ assert.match(r.out, /TRACE_PATH повторяется/, r.out);
178
+ });
179
+
180
+ test('P10 - a proven violation OUTRANKS an unresolved unit', () => {
181
+ // One unit is definitively undelivered while another is still live. Exit 1 is the actionable
182
+ // truth: aggregation is already forbidden, and saying "could not check" would be softer than
183
+ // the facts allow.
184
+ const r = check({
185
+ units: [{ workUnitId: 'dead' }, { workUnitId: 'busy', pid: process.pid }],
186
+ traces: {},
187
+ });
188
+ assert.equal(r.code, 1, 'a proven miss must not be downgraded to inconclusive: ' + r.out);
189
+ assert.match(r.out, /dead \[undelivered\/missing\]/, r.out);
190
+ assert.match(r.out, /busy \[waiting\/positive-liveness-only\]/,
191
+ 'the unresolved unit must still be NAMED, not swallowed: ' + r.out);
192
+ });
193
+
194
+ test('P11 - no manifest argument, and a manifest that does not exist, are both exit 2', () => {
195
+ const bare = spawnSync(process.execPath, [CHECK], { encoding: 'utf8' });
196
+ assert.equal(bare.status, 2, 'no argument is not a clean bill: ' + bare.stdout);
197
+ assert.match(bare.stdout, /не передан путь/, bare.stdout);
198
+
199
+ const absent = spawnSync(process.execPath, [CHECK, '/nonexistent/receipts.json'],
200
+ { encoding: 'utf8' });
201
+ assert.equal(absent.status, 2, 'an unreadable manifest is an unknown: ' + absent.stdout);
202
+ assert.match(absent.stdout, /не читается манифест/, absent.stdout);
203
+ });
204
+
205
+ test('P12 - it is a hooks component wired to NO event, and the three counters agree', () => {
206
+ const { COMPONENTS } = require(path.join(PKG, 'src', 'utils.js'));
207
+ assert.ok(COMPONENTS.hooks.items['check-swarm-receipts'],
208
+ 'it must be registered, or init/doctor/verify will not know it');
209
+ const settings = fs.readFileSync(path.join(TPL, 'settings.json'), 'utf-8');
210
+ assert.ok(!settings.includes('check-swarm-receipts'),
211
+ 'it must not be wired to an event: this package\'s hooks are non-blocking by contract, so a '
212
+ + 'hook could only print — it could never refuse anything');
213
+ const statusline = fs.readFileSync(path.join(TPL, 'hooks', 'statusline.cjs'), 'utf-8');
214
+ const m = statusline.match(/hooksExpected:\s*(\d+)/);
215
+ assert.ok(m, 'statusline must declare hooksExpected');
216
+ assert.equal(Number(m[1]), Object.keys(COMPONENTS.hooks.items).length,
217
+ 'the status line would report a phantom missing hook: ' + m[1]);
218
+ assert.equal(Number(m[1]), fs.readdirSync(path.join(TPL, 'hooks')).filter((f) => f.endsWith('.cjs')).length,
219
+ 'the shipped hook FILES must match the declared count — the counter drifted twice before');
220
+ });
221
+
222
+ test('P13 - MUTATION: turning the third exit code into a pass is caught', () => {
223
+ // The registered mutation in test/mutation-registry.json flips cannotCheck's exit(2) to exit(0).
224
+ // Assert here that at least one real input DEPENDS on that distinction, so the mutant has a
225
+ // killer: without this, the registry entry would name a property nothing proves.
226
+ const source = fs.readFileSync(CHECK, 'utf-8');
227
+ assert.match(source, /process\.exit\(2\);/, 'cannotCheck must exit 2');
228
+ const live = check({ units: [{ workUnitId: 'api', pid: process.pid }] });
229
+ assert.equal(live.code, 2, 'the live-worker case is the killer input for that mutant');
230
+ });
231
+ });