@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.
- package/.dz-manifest.json +281 -65
- package/CHANGELOG.md +148 -1
- package/LICENSE +21 -0
- package/MULTIPLATFORM_ROADMAP.md +1 -1
- package/README/eng/01_quickstart.md +2 -2
- package/README/eng/02_user_guide.md +1 -1
- package/README/eng/03_admin_guide.md +2 -2
- package/README/eng/05_architecture.md +1 -1
- package/README/eng/README.md +2 -1
- package/README/ru/01_quickstart.md +2 -2
- package/README/ru/02_user_guide.md +1 -1
- package/README/ru/03_admin_guide.md +2 -2
- package/README/ru/05_architecture.md +1 -1
- package/README/ru/README.md +2 -1
- package/README/ru/html/index.html +8 -8
- package/README.md +150 -9
- package/bin/cli.js +0 -0
- package/package.json +10 -11
- package/sbom.json +604 -64
- package/scripts/check-pipeline-gaps.sh +510 -21
- package/src/commands/init.js +1 -1
- package/src/rule-components.json +5 -1
- package/src/utils.js +33 -3
- package/templates/.claude/agents/product-discoverer.md +38 -0
- package/templates/.claude/agents/replicate-coordinator.md +11 -1
- package/templates/.claude/commands/feature.md +72 -6
- package/templates/.claude/commands/go.md +6 -8
- package/templates/.claude/commands/harvest.md +5 -7
- package/templates/.claude/commands/replicate.md +169 -44
- package/templates/.claude/commands/start.md +28 -7
- package/templates/.claude/hooks/capture-source-path.cjs +795 -0
- package/templates/.claude/hooks/check-canon.cjs +493 -0
- package/templates/.claude/hooks/check-embed-contract.cjs +374 -0
- package/templates/.claude/hooks/check-external-deps.cjs +288 -0
- package/templates/.claude/hooks/check-file-ownership.cjs +424 -0
- package/templates/.claude/hooks/check-handoff-manifest.cjs +367 -0
- package/templates/.claude/hooks/check-job-contract.cjs +501 -0
- package/templates/.claude/hooks/check-look-origin.cjs +240 -0
- package/templates/.claude/hooks/check-look-trace.cjs +385 -0
- package/templates/.claude/hooks/check-metric-source.cjs +296 -0
- package/templates/.claude/hooks/check-model-cost.cjs +470 -0
- package/templates/.claude/hooks/check-ports.cjs +27 -6
- package/templates/.claude/hooks/check-review-contract.cjs +205 -0
- package/templates/.claude/hooks/check-source-version.cjs +312 -0
- package/templates/.claude/hooks/check-swarm-receipts.cjs +197 -0
- package/templates/.claude/hooks/check-webhook-contract.cjs +535 -0
- package/templates/.claude/hooks/statusline.cjs +2 -2
- package/templates/.claude/rules/embeddable-widget.md +73 -0
- package/templates/.claude/rules/feature-lifecycle.md +5 -6
- package/templates/.claude/rules/incoming-webhooks.md +99 -0
- package/templates/.claude/rules/long-running-job.md +73 -0
- package/templates/.claude/rules/model-call-cost.md +85 -0
- package/templates/.claude/rules/replicate-pipeline.md +122 -52
- package/templates/.claude/skills/brutal-honesty-review/SKILL.md +9 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/SKILL.md +4 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +46 -1
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +7 -1
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/06-package-deliver.md +20 -2
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/claude-md-strategy.md +7 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/automation-commands.md +17 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle-ent.md +43 -5
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +43 -7
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/start-command.md +19 -1
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md +151 -0
- package/templates/.claude/skills/goap-research-ed25519/SKILL.md +37 -22
- package/templates/.claude/skills/goap-research-ed25519/references/negative-results.md +94 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/check_report_evidence.py +368 -4
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +122 -5
- package/templates/.claude/skills/goap-research-ed25519/scripts/evidence_fetch.py +33 -16
- package/templates/.claude/skills/goap-research-ed25519/scripts/quote_provenance.py +342 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_ed25519_verifier.py +60 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_evidence_provenance.py +139 -6
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_quote_provenance.py +274 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_suite_completeness.py +2 -1
- package/templates/.claude/skills/knowledge-extractor/SKILL.md +4 -0
- package/templates/.claude/skills/pipeline-forge/SKILL.md +18 -23
- package/templates/.claude/skills/pipeline-forge/examples/replicate-analysis.md +7 -2
- package/templates/.claude/skills/pipeline-forge/references/patterns-catalog.md +19 -1
- package/templates/.claude/skills/pipeline-forge/references/self-extracted-patterns.md +17 -6
- package/templates/.claude/skills/pipeline-forge/references/skill-anatomy.md +0 -1
- package/templates/.claude/skills/requirements-validator/SKILL.md +18 -5
- package/templates/.claude/skills/requirements-validator/references/feature-report-contracts.md +67 -0
- package/templates/.claude/skills/requirements-validator/references/scoring-system.md +15 -6
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +21 -1
- package/templates/.claude/skills/sparc-prd-mini/SKILL.md +173 -725
- package/tests/e2e/feature-contour.test.js +176 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/01_specification.md +15 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/02_pseudocode.md +19 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/03_architecture.md +3 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/04_refinement.md +3 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/05_completion.md +7 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/review-report.md +11 -0
- package/tests/fixtures/feature-contour/docs/features/demo-gate/validation-report.md +10 -0
- package/tests/fixtures/feature-contour/tests/demo.test.js +16 -0
- package/tests/snapshot/baseline.json +64 -40
- package/tests/unit/capture-source-path.test.js +492 -0
- package/tests/unit/check-canon.test.js +403 -0
- package/tests/unit/check-embed-contract.test.js +422 -0
- package/tests/unit/check-external-deps.test.js +363 -0
- package/tests/unit/check-file-ownership.test.js +388 -0
- package/tests/unit/check-handoff-manifest.test.js +410 -0
- package/tests/unit/check-job-contract.test.js +514 -0
- package/tests/unit/check-look-origin.test.js +180 -0
- package/tests/unit/check-look-trace.test.js +420 -0
- package/tests/unit/check-metric-source.test.js +325 -0
- package/tests/unit/check-model-cost.test.js +425 -0
- package/tests/unit/check-ports.test.js +46 -2
- package/tests/unit/check-review-contract.test.js +181 -0
- package/tests/unit/check-source-version.test.js +344 -0
- package/tests/unit/check-swarm-receipts.test.js +231 -0
- package/tests/unit/check-webhook-contract.test.js +536 -0
- package/tests/unit/db-port-rule.test.js +8 -2
- package/tests/unit/detection-ladder-registry.test.js +2 -2
- package/tests/unit/generator-swarm-contract.test.js +287 -0
- package/tests/unit/guard-honest-input-meta.test.js +64 -0
- package/tests/unit/honest-failure-rules.test.js +106 -9
- package/tests/unit/look-phase-contract.test.js +231 -0
- package/tests/unit/negative-conclusion-gate.test.js +300 -0
- package/tests/unit/quote-provenance.test.js +122 -0
- package/tests/unit/sync-templates-guard.test.js +46 -3
- package/tests/unit/traceability-completion-gate.test.js +267 -0
- package/tests/unit/utils.test.js +40 -3
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* check-swarm-receipts.cjs — did the parallel work units actually DELIVER, or is the coordinator
|
|
6
|
+
* about to aggregate silence?
|
|
7
|
+
*
|
|
8
|
+
* THE FAILURE THIS EXISTS FOR. A worker that died looks exactly like a worker that is still
|
|
9
|
+
* running: both are silent. Silence therefore reads as "in progress", and a coordinator can
|
|
10
|
+
* report in good faith that the review is running when no review exists. The cure is to make the
|
|
11
|
+
* RESULT of every unit a FILE at a named path: no file, no work — and that is a fact a machine can
|
|
12
|
+
* establish, not a feeling a reader has to trust. This utility is that machine.
|
|
13
|
+
*
|
|
14
|
+
* NOT an event hook. Like `check-ports.cjs`, `check-growth-trace.cjs` and `check-look-trace.cjs`,
|
|
15
|
+
* it lives here because this directory already carries plain Node utilities; nothing registers it
|
|
16
|
+
* in settings.json. That is deliberate and load-bearing: this package's hooks are NON-BLOCKING by
|
|
17
|
+
* contract (pinned by tests/unit/hooks-project-anchored.test.js, which requires exit 0), so a hook
|
|
18
|
+
* could never refuse anything — it could only print. Invoke it:
|
|
19
|
+
*
|
|
20
|
+
* node .claude/hooks/check-swarm-receipts.cjs <path-to-receipts-manifest.json>
|
|
21
|
+
*
|
|
22
|
+
* Exit codes — three, and the third is the point:
|
|
23
|
+
* 0 every required receipt is a fresh, substantive, terminal file ending `Status: completed`
|
|
24
|
+
* 1 at least one unit is UNDELIVERED or delivered a `Status: failed` — refuse aggregation
|
|
25
|
+
* 2 THE CHECK DID NOT RUN — no manifest, malformed assignment, or an inconclusive probe
|
|
26
|
+
*
|
|
27
|
+
* A checker that answers "clean" when it could not look converts an unknown into a reassurance,
|
|
28
|
+
* which is the same substitution the whole contract exists to refuse. So an unreadable trace is
|
|
29
|
+
* exit 2, never 0; and a missing trace whose worker PID is still ALIVE is exit 2 as well — a live
|
|
30
|
+
* worker may extend waiting, but liveness has never been delivery.
|
|
31
|
+
*
|
|
32
|
+
* MANIFEST SHAPE (the coordinator writes it BEFORE dispatch, which is what makes freshness
|
|
33
|
+
* checkable at all — `launchMs` has to be recorded before the workers can write):
|
|
34
|
+
*
|
|
35
|
+
* {
|
|
36
|
+
* "runId": "<run-unique id>",
|
|
37
|
+
* "launchMs": 1756728000000, // Date.now() captured immediately before dispatch
|
|
38
|
+
* "units": [
|
|
39
|
+
* { "workUnitId": "api", "tracePath": "/abs/path/api.md", "pid": 12345 },
|
|
40
|
+
* { "workUnitId": "docs", "tracePath": "/abs/path/docs.md" }
|
|
41
|
+
* ]
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* `pid` is optional and can only ever move an ABSENT trace between "undelivered" and "still
|
|
45
|
+
* waiting". It can never turn a missing file into a delivery.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const fs = require('node:fs');
|
|
49
|
+
const path = require('node:path');
|
|
50
|
+
|
|
51
|
+
const TERMINAL = /^Status: (completed|failed)$/;
|
|
52
|
+
|
|
53
|
+
function say(s) { process.stdout.write(s + '\n'); }
|
|
54
|
+
|
|
55
|
+
/** Exit 2 with a reason. Never merged with "clean": not-run and not-violated are different facts. */
|
|
56
|
+
function cannotCheck(reason, hint) {
|
|
57
|
+
say('⚠️ проверка НЕ выполнена: ' + reason);
|
|
58
|
+
if (hint) say(' ' + hint);
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Is the worker behind this unit still alive?
|
|
64
|
+
*
|
|
65
|
+
* Three answers, because two would be a lie. `signal 0` tells us the process exists (`live`) or is
|
|
66
|
+
* gone (`dead`); anything else — EPERM on a process we do not own, a malformed pid — is `unknown`,
|
|
67
|
+
* and an unknown liveness must never be read as either.
|
|
68
|
+
*/
|
|
69
|
+
function liveness(pid) {
|
|
70
|
+
if (pid === undefined || pid === null) return 'unknown';
|
|
71
|
+
if (!Number.isInteger(pid) || pid <= 0) return 'unknown';
|
|
72
|
+
try { process.kill(pid, 0); return 'live'; } catch (e) {
|
|
73
|
+
return e && e.code === 'ESRCH' ? 'dead' : 'unknown';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The whole verdict for ONE unit, as a state plus a reason.
|
|
79
|
+
*
|
|
80
|
+
* The order of the checks is the contract, not a convenience. Assignment first: a relative or
|
|
81
|
+
* duplicated `TRACE_PATH` means the DISPATCH was wrong, and a wrong dispatch cannot produce a
|
|
82
|
+
* meaningful verdict about delivery. Then existence, then regular-file, then freshness, then
|
|
83
|
+
* substance, then the terminal marker LAST — because the marker is written last, so a file that
|
|
84
|
+
* has a body but no marker is a PARTIAL write, not a failure and not a success.
|
|
85
|
+
*/
|
|
86
|
+
function inspectReceipt(unit, launchMs) {
|
|
87
|
+
const { workUnitId, tracePath } = unit;
|
|
88
|
+
if (!workUnitId || typeof tracePath !== 'string' || !path.isAbsolute(tracePath)) {
|
|
89
|
+
return { state: 'inconclusive', reason: 'assignment' };
|
|
90
|
+
}
|
|
91
|
+
const alive = liveness(unit.pid);
|
|
92
|
+
let stat;
|
|
93
|
+
try { stat = fs.lstatSync(tracePath); } catch (error) {
|
|
94
|
+
if (error.code !== 'ENOENT') return { state: 'inconclusive', reason: 'unreadable' };
|
|
95
|
+
// A live worker may only EXTEND waiting. It can never stand in for the file.
|
|
96
|
+
if (alive === 'live') return { state: 'waiting', reason: 'positive-liveness-only' };
|
|
97
|
+
return { state: 'undelivered', reason: alive === 'dead' ? 'dead-worker' : 'missing' };
|
|
98
|
+
}
|
|
99
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return { state: 'undelivered', reason: 'not-regular' };
|
|
100
|
+
if (stat.mtimeMs <= launchMs) return { state: 'undelivered', reason: 'stale' };
|
|
101
|
+
let body;
|
|
102
|
+
try { body = fs.readFileSync(tracePath, 'utf-8'); } catch { return { state: 'inconclusive', reason: 'unreadable' }; }
|
|
103
|
+
// Whitespace is exactly as empty as nothing. A size check alone would pass a lone newline.
|
|
104
|
+
if (!body.trim()) return { state: 'undelivered', reason: 'empty' };
|
|
105
|
+
const lines = body.trimEnd().split(/\r?\n/);
|
|
106
|
+
const terminal = lines.at(-1);
|
|
107
|
+
if (!TERMINAL.test(terminal)) return { state: 'undelivered', reason: 'non-terminal' };
|
|
108
|
+
// A marker with nothing above it is a receipt for no work.
|
|
109
|
+
if (!lines.slice(0, -1).join('\n').trim()) return { state: 'undelivered', reason: 'empty-payload' };
|
|
110
|
+
return terminal === 'Status: completed'
|
|
111
|
+
? { state: 'completed', reason: 'terminal-receipt' }
|
|
112
|
+
: { state: 'failed', reason: 'delivered-failure' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readManifest(file) {
|
|
116
|
+
let raw;
|
|
117
|
+
try { raw = fs.readFileSync(file, 'utf-8'); } catch {
|
|
118
|
+
cannotCheck('не читается манифест квитанций: ' + file,
|
|
119
|
+
'манифест пишет КООРДИНАТОР до диспатча — его отсутствие значит, что рой запускали без учёта, '
|
|
120
|
+
+ 'а не что все отчитались');
|
|
121
|
+
}
|
|
122
|
+
let manifest;
|
|
123
|
+
try { manifest = JSON.parse(raw); } catch (e) {
|
|
124
|
+
cannotCheck('манифест не разбирается как JSON: ' + ((e && e.message) || e));
|
|
125
|
+
}
|
|
126
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
127
|
+
cannotCheck('манифест не является объектом');
|
|
128
|
+
}
|
|
129
|
+
if (!Number.isFinite(manifest.launchMs)) {
|
|
130
|
+
cannotCheck('в манифесте нет числового launchMs',
|
|
131
|
+
'без момента запуска нельзя отличить свежий след от файла, лежавшего здесь до роя');
|
|
132
|
+
}
|
|
133
|
+
const units = Array.isArray(manifest.units) ? manifest.units : null;
|
|
134
|
+
if (!units) cannotCheck('в манифесте нет массива units');
|
|
135
|
+
if (!units.length) {
|
|
136
|
+
cannotCheck('в манифесте ноль рабочих единиц',
|
|
137
|
+
'пустой рой нечего проверять — сказать «всё чисто» значило бы отчитаться о проверке, '
|
|
138
|
+
+ 'которой не было');
|
|
139
|
+
}
|
|
140
|
+
const ids = units.map((u) => u && u.workUnitId);
|
|
141
|
+
const paths = units.map((u) => u && u.tracePath);
|
|
142
|
+
const dupeIds = [...new Set(ids.filter((id, i) => ids.indexOf(id) !== i))];
|
|
143
|
+
if (dupeIds.length) {
|
|
144
|
+
cannotCheck('WORK_UNIT_ID повторяется: ' + dupeIds.join(', '),
|
|
145
|
+
'один идентификатор на две единицы — одна квитанция зачлась бы за обе');
|
|
146
|
+
}
|
|
147
|
+
const dupePaths = [...new Set(paths.filter((p, i) => paths.indexOf(p) !== i))];
|
|
148
|
+
if (dupePaths.length) {
|
|
149
|
+
cannotCheck('TRACE_PATH повторяется: ' + dupePaths.join(', '),
|
|
150
|
+
'два работника на один путь — второй перезапишет первого, и потеря будет молчаливой');
|
|
151
|
+
}
|
|
152
|
+
return manifest;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function main() {
|
|
156
|
+
const file = process.argv[2];
|
|
157
|
+
if (!file) {
|
|
158
|
+
cannotCheck('не передан путь к манифесту квитанций',
|
|
159
|
+
'использование: node .claude/hooks/check-swarm-receipts.cjs <manifest.json>');
|
|
160
|
+
}
|
|
161
|
+
const manifest = readManifest(file);
|
|
162
|
+
const results = manifest.units.map((unit) => ({ unit, verdict: inspectReceipt(unit, manifest.launchMs) }));
|
|
163
|
+
|
|
164
|
+
const bad = results.filter((r) => r.verdict.state === 'undelivered' || r.verdict.state === 'failed');
|
|
165
|
+
const unknown = results.filter((r) => r.verdict.state === 'inconclusive' || r.verdict.state === 'waiting');
|
|
166
|
+
const ok = results.filter((r) => r.verdict.state === 'completed');
|
|
167
|
+
|
|
168
|
+
const line = (r) => ' • ' + (r.unit.workUnitId || '<без WORK_UNIT_ID>') + ' [' + r.verdict.state
|
|
169
|
+
+ '/' + r.verdict.reason + '] ' + (r.unit.tracePath || '<без TRACE_PATH>');
|
|
170
|
+
|
|
171
|
+
if (bad.length) {
|
|
172
|
+
say('❌ квитанции не собраны: ' + ok.length + ' из ' + results.length + ' завершены');
|
|
173
|
+
for (const r of bad) say(line(r));
|
|
174
|
+
for (const r of unknown) say(line(r));
|
|
175
|
+
say(' Сведение, синтез и завершение ЗАПРЕЩЕНЫ, пока каждая требуемая квитанция не '
|
|
176
|
+
+ 'terminal-completed. Доклад агента — не квитанция; тишина — не прогресс.');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
if (unknown.length) {
|
|
180
|
+
say('⚠️ проверка НЕ выполнена: ' + unknown.length + ' из ' + results.length
|
|
181
|
+
+ ' единиц не дали определённого ответа');
|
|
182
|
+
for (const r of unknown) say(line(r));
|
|
183
|
+
say(' Живой PID продлевает ожидание, но не заменяет файл; нечитаемый след — неизвестность, '
|
|
184
|
+
+ 'а не успех. Дождитесь следа или диагностируйте единицу и перезапустите проверку.');
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
say('✅ все ' + results.length + ' квитанций свежие, содержательные и завершены Status: completed');
|
|
188
|
+
say(' Ограничение: это доказывает, что работа ДОСТАВЛЕНА, а не что она верна.');
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
main();
|
|
194
|
+
} catch (err) {
|
|
195
|
+
// Even an unexpected failure must not read as "clean".
|
|
196
|
+
cannotCheck('внутренняя ошибка проверки: ' + String((err && err.message) || err));
|
|
197
|
+
}
|