@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
@@ -328,23 +328,38 @@ describe('e2e: v1.4 pre-shipped generic toolkit', () => {
328
328
  } finally { rmRf(dir); }
329
329
  });
330
330
 
331
- test('init installs all 5 generic rules', () => {
331
+ test('init installs every registered rule and the consumer detection ladder is readable', () => {
332
332
  const dir = tmpDir();
333
333
  try {
334
334
  runCli(['init'], dir);
335
- const expected = [
336
- 'replicate-pipeline',
337
- 'skill-interface-protocol',
338
- 'git-workflow',
339
- 'insights-capture',
340
- 'feature-lifecycle',
341
- ];
335
+ const { COMPONENTS } = require(path.join(PKG_DIR, 'src', 'utils.js'));
336
+ const expected = Object.keys(COMPONENTS.rules.items);
337
+ assert.ok(expected.includes('cost-of-detection-ladder'),
338
+ 'the explicit ladder slug prevents registry-derived omission from self-confirming');
342
339
  for (const rule of expected) {
343
340
  assert.ok(
344
341
  exists(dir, `.claude/rules/${rule}.md`),
345
- `${rule}.md should be installed by init (v1.4 pre-shipped)`
342
+ `${rule}.md should be installed by init`
346
343
  );
347
344
  }
345
+ const ladder = fs.readFileSync(
346
+ path.join(dir, '.claude/rules/cost-of-detection-ladder.md'), 'utf8');
347
+ assert.match(ladder, /strongest layer that can reliably express the property/i);
348
+ assert.match(ladder, /\| Reaction \| Owner \|/i);
349
+ } finally { rmRf(dir); }
350
+ });
351
+
352
+ test('doctor and verify reject a missing registered detection ladder', () => {
353
+ const dir = tmpDir();
354
+ try {
355
+ runCli(['init'], dir);
356
+ fs.unlinkSync(path.join(dir, '.claude/rules/cost-of-detection-ladder.md'));
357
+ const doctor = runCli(['doctor'], dir);
358
+ const verify = runCli(['verify'], dir);
359
+ assert.notEqual(doctor.exitCode, 0, 'doctor must reject a missing load-bearing rule');
360
+ assert.notEqual(verify.exitCode, 0, 'verify must reject a missing load-bearing rule');
361
+ assert.match(doctor.stdout + doctor.stderr, /cost-of-detection-ladder/);
362
+ assert.match(verify.stdout + verify.stderr, /cost-of-detection-ladder/);
348
363
  } finally { rmRf(dir); }
349
364
  });
350
365
 
@@ -490,6 +505,37 @@ describe('e2e: v1.4.1 cross-platform hooks', () => {
490
505
  describe('e2e: v1.4.2 settings.json merge on --force', () => {
491
506
  const USER_HOOK_COMMAND = 'echo "USER-CUSTOM-HOOK-MARKER-12345"';
492
507
 
508
+ test('P18 - init force adds one p-replicator prompt hook without replacing foreign hooks', () => {
509
+ const dir = tmpDir();
510
+ const foreign = 'node foreign-user-prompt-hook.cjs';
511
+ try {
512
+ runCli(['init'], dir);
513
+ const settingsPath = path.join(dir, '.claude/settings.json');
514
+ const manifestPath = path.join(dir, MANIFEST);
515
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
516
+ settings.hooks.UserPromptSubmit = [{
517
+ matcher: '*',
518
+ hooks: [{ type: 'command', command: foreign, timeout: 7 }],
519
+ }];
520
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
521
+
522
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
523
+ delete manifest.shippedDefaults['settings.json'].hooks.UserPromptSubmit;
524
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
525
+
526
+ assert.equal(runCli(['init', '--force'], dir).exitCode, 0);
527
+ assert.equal(runCli(['init', '--force'], dir).exitCode, 0);
528
+ const merged = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
529
+ const commands = merged.hooks.UserPromptSubmit
530
+ .flatMap((matcher) => matcher.hooks || [])
531
+ .map((hook) => hook.command);
532
+ assert.equal(commands.filter((command) => command === foreign).length, 1,
533
+ 'the consumer-owned prompt hook must survive both upgrades');
534
+ assert.equal(commands.filter((command) => command.includes('session-insights.cjs')).length, 1,
535
+ 'the package prompt hook must be added exactly once');
536
+ } finally { rmRf(dir); }
537
+ });
538
+
493
539
  test('init --force preserves user-added hooks in settings.json', () => {
494
540
  const dir = tmpDir();
495
541
  try {
@@ -0,0 +1,308 @@
1
+ 'use strict';
2
+
3
+ const { test, describe, before, after } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const { spawnSync } = require('node:child_process');
6
+ const crypto = require('node:crypto');
7
+ const fs = require('node:fs');
8
+ const os = require('node:os');
9
+ const path = require('node:path');
10
+
11
+ const PKG = path.resolve(__dirname, '..', '..');
12
+ const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm';
13
+ const MISSING_HINT = 'инсайтов пока нет; /myinsights создаст первую запись\n';
14
+ const LOCAL_PACKED = 'PACKED_LOCAL_ONLY';
15
+ const DZ_PACKED = 'PACKED_DZ_ONLY';
16
+ let packDir;
17
+ let tarball;
18
+ let tarballSha256;
19
+
20
+ function run(file, args, options = {}) {
21
+ return spawnSync(file, args, { encoding: 'utf8', ...options });
22
+ }
23
+
24
+ function assertOk(result, label) {
25
+ assert.equal(result.status, 0,
26
+ `${label} failed\nstdout:\n${result.stdout || ''}\nstderr:\n${result.stderr || ''}`);
27
+ }
28
+
29
+ function assertConsumerPath(candidate, consumer) {
30
+ const realCandidate = fs.realpathSync(candidate);
31
+ const realConsumer = fs.realpathSync(consumer);
32
+ const relative = path.relative(realConsumer, realCandidate);
33
+ assert.ok(relative && !relative.startsWith('..' + path.sep) && !path.isAbsolute(relative),
34
+ `source-only evidence rejected: ${realCandidate} is outside packed consumer ${realConsumer}`);
35
+ return realCandidate;
36
+ }
37
+
38
+ function installConsumer() {
39
+ const consumer = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-packed-consumer-')));
40
+ fs.writeFileSync(path.join(consumer, 'package.json'),
41
+ JSON.stringify({ name: 'packed-insights-consumer', private: true }) + '\n');
42
+ const install = run(NPM, [
43
+ 'install', '--ignore-scripts', '--omit=optional', '--no-audit', '--no-fund',
44
+ '--no-package-lock', tarball,
45
+ ], { cwd: consumer });
46
+ assertOk(install, 'fresh tarball install');
47
+
48
+ const packageRoot = path.join(consumer, 'node_modules', '@dzhechkov', 'p-replicator');
49
+ const cli = assertConsumerPath(path.join(packageRoot, 'bin', 'cli.js'), consumer);
50
+ const init = run(process.execPath, [cli, 'init'], { cwd: consumer });
51
+ assertOk(init, 'packed init');
52
+ return { consumer, packageRoot, cli };
53
+ }
54
+
55
+ function insight(overrides = {}) {
56
+ return {
57
+ date: '2026-08-30',
58
+ title: 'Packed writer creates the first carrier',
59
+ tags: ['packed', 'insights'],
60
+ problem: 'Source-only checks cannot prove what npm consumers receive.',
61
+ solution: 'Install and execute the exact npm pack tarball.',
62
+ references: ['tests/e2e/packed-insights-writer.test.js'],
63
+ ...overrides,
64
+ };
65
+ }
66
+
67
+ function runPackedWriter(consumer, input, options = {}) {
68
+ const writer = assertConsumerPath(
69
+ path.join(consumer, '.claude', 'hooks', 'write-insight.cjs'), consumer);
70
+ return run(process.execPath, [writer], {
71
+ cwd: path.join(consumer, '.claude'),
72
+ env: { ...process.env, PATH: options.path === undefined ? '' : options.path,
73
+ CLAUDE_PROJECT_DIR: consumer },
74
+ input: JSON.stringify(input),
75
+ });
76
+ }
77
+
78
+ function parseReceipt(result) {
79
+ assertOk(result, 'packed writer');
80
+ return JSON.parse(result.stdout);
81
+ }
82
+
83
+ function fakeDz(mode) {
84
+ const bin = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-fake-dz-')));
85
+ const script = path.join(bin, 'dz.js');
86
+ const outcome = mode === 'ok'
87
+ ? `process.stdout.write(${JSON.stringify(JSON.stringify([
88
+ { pattern: DZ_PACKED, domain: 'p-replicator-insights' },
89
+ ]))});`
90
+ : mode === 'empty'
91
+ ? "process.stdout.write('[]');"
92
+ : 'process.exitCode = 19;';
93
+ fs.writeFileSync(script, `'use strict';\n${outcome}\n`);
94
+ if (process.platform === 'win32') {
95
+ fs.writeFileSync(path.join(bin, 'dz.cmd'),
96
+ `@\"${process.execPath}\" \"%~dp0\\dz.js\" %*\r\n`);
97
+ } else {
98
+ const executable = path.join(bin, 'dz');
99
+ fs.writeFileSync(executable, `#!${process.execPath}\nrequire('./dz.js');\n`, { mode: 0o700 });
100
+ }
101
+ return bin;
102
+ }
103
+
104
+ function runPackedPrompt(consumer, pathValue) {
105
+ const hook = assertConsumerPath(
106
+ path.join(consumer, '.claude', 'hooks', 'session-insights.cjs'), consumer);
107
+ return run(process.execPath, [hook], {
108
+ cwd: path.join(consumer, '.claude'),
109
+ env: { ...process.env, PATH: pathValue, CLAUDE_PROJECT_DIR: consumer },
110
+ input: JSON.stringify({ hook_event_name: 'UserPromptSubmit', prompt: 'packed fallback' }),
111
+ });
112
+ }
113
+
114
+ function promptContext(result, label) {
115
+ assertOk(result, label);
116
+ const lines = result.stdout.trimEnd().split('\n');
117
+ assert.equal(lines.length, 1, `${label} emitted ${lines.length} envelopes`);
118
+ return JSON.parse(lines[0]).hookSpecificOutput.additionalContext;
119
+ }
120
+
121
+ before(() => {
122
+ packDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-pack-')));
123
+ const packed = run(NPM, ['pack', '--json', '--pack-destination', packDir], { cwd: PKG });
124
+ assertOk(packed, 'npm pack --json');
125
+ const receipt = JSON.parse(packed.stdout);
126
+ assert.equal(receipt.length, 1, 'npm pack must identify exactly one consumer artifact');
127
+ tarball = path.join(packDir, receipt[0].filename);
128
+ assert.ok(fs.existsSync(tarball), 'npm pack receipt names a missing tarball');
129
+ tarballSha256 = crypto.createHash('sha256').update(fs.readFileSync(tarball)).digest('hex');
130
+ process.stdout.write(`# packed artifact ${path.basename(tarball)} sha256=${tarballSha256}\n`);
131
+ });
132
+
133
+ after(() => {
134
+ if (packDir) fs.rmSync(packDir, { recursive: true, force: true });
135
+ });
136
+
137
+ describe('PR-022 exact packed artifact', () => {
138
+ test('P10-packed - absent dz emits local only without dz error text', () => {
139
+ const { consumer } = installConsumer();
140
+ try {
141
+ assert.equal(parseReceipt(runPackedWriter(consumer,
142
+ insight({ title: LOCAL_PACKED }))).status, 'created');
143
+ const context = promptContext(runPackedPrompt(consumer, ''), 'packed absent recall');
144
+ assert.match(context, new RegExp(LOCAL_PACKED));
145
+ assert.doesNotMatch(context, new RegExp(DZ_PACKED));
146
+ assert.doesNotMatch(context, /dz recall unavailable/i);
147
+ } finally { fs.rmSync(consumer, { recursive: true, force: true }); }
148
+ });
149
+
150
+ test('P11-packed - armed recall emits dz only', () => {
151
+ const { consumer } = installConsumer();
152
+ const bin = fakeDz('ok');
153
+ try {
154
+ assert.equal(parseReceipt(runPackedWriter(consumer,
155
+ insight({ title: LOCAL_PACKED }))).status, 'created');
156
+ const context = promptContext(runPackedPrompt(consumer, bin), 'packed armed recall');
157
+ assert.match(context, new RegExp(DZ_PACKED));
158
+ assert.doesNotMatch(context, new RegExp(LOCAL_PACKED));
159
+ } finally {
160
+ fs.rmSync(bin, { recursive: true, force: true });
161
+ fs.rmSync(consumer, { recursive: true, force: true });
162
+ }
163
+ });
164
+
165
+ test('P12-packed - failing recall names degradation and emits local only', () => {
166
+ const { consumer } = installConsumer();
167
+ const bin = fakeDz('failing');
168
+ try {
169
+ assert.equal(parseReceipt(runPackedWriter(consumer,
170
+ insight({ title: LOCAL_PACKED }))).status, 'created');
171
+ const context = promptContext(runPackedPrompt(consumer, bin), 'packed failing recall');
172
+ assert.match(context, /dz recall unavailable: exit 19; using local recent insights/i);
173
+ assert.match(context, new RegExp(LOCAL_PACKED));
174
+ assert.doesNotMatch(context, new RegExp(DZ_PACKED));
175
+ } finally {
176
+ fs.rmSync(bin, { recursive: true, force: true });
177
+ fs.rmSync(consumer, { recursive: true, force: true });
178
+ }
179
+ });
180
+
181
+ test('P13-packed - empty recall emits local only', () => {
182
+ const { consumer } = installConsumer();
183
+ const bin = fakeDz('empty');
184
+ try {
185
+ assert.equal(parseReceipt(runPackedWriter(consumer,
186
+ insight({ title: LOCAL_PACKED }))).status, 'created');
187
+ const context = promptContext(runPackedPrompt(consumer, bin), 'packed empty recall');
188
+ assert.match(context, new RegExp(LOCAL_PACKED));
189
+ assert.doesNotMatch(context, new RegExp(DZ_PACKED));
190
+ assert.doesNotMatch(context, /dz recall unavailable/i);
191
+ } finally {
192
+ fs.rmSync(bin, { recursive: true, force: true });
193
+ fs.rmSync(consumer, { recursive: true, force: true });
194
+ }
195
+ });
196
+
197
+ test('P1 — packed harvest writer creates, appends, and deduplicates from a missing carrier without dz', () => {
198
+ const { consumer, packageRoot } = installConsumer();
199
+ try {
200
+ assert.match(tarballSha256, /^[a-f0-9]{64}$/);
201
+ assert.equal(fs.existsSync(path.join(consumer, '.claude', 'insights')), false,
202
+ 'fresh init must leave the legitimate missing state intact');
203
+ const harvest = assertConsumerPath(
204
+ path.join(packageRoot, 'templates', '.claude', 'commands', 'harvest.md'), consumer);
205
+ assert.ok((fs.readFileSync(harvest, 'utf8').match(/insight/gi) || []).length > 0,
206
+ 'the packed command has no insight-writer binding');
207
+
208
+ assert.equal(parseReceipt(runPackedWriter(consumer, insight())).status, 'created');
209
+ const index = path.join(consumer, '.claude', 'insights', 'index.md');
210
+ const first = fs.readFileSync(index);
211
+ const second = insight({ title: 'Packed append keeps prior bytes', tags: ['append'] });
212
+ assert.equal(parseReceipt(runPackedWriter(consumer, second)).status, 'appended');
213
+ assert.ok(fs.readFileSync(index).subarray(0, first.length).equals(first));
214
+ const beforeReplay = fs.readFileSync(index);
215
+ assert.equal(parseReceipt(runPackedWriter(consumer,
216
+ { ...second, date: '2026-08-31' })).status, 'duplicate');
217
+ assert.deepEqual(fs.readFileSync(index), beforeReplay);
218
+ assert.equal(fs.existsSync(path.join(consumer, '.dz')), false,
219
+ 'the markdown lifecycle must not create dz state');
220
+ } finally { fs.rmSync(consumer, { recursive: true, force: true }); }
221
+ });
222
+
223
+ test('P17 - deleting .dz retains the packed Markdown record and local delivery', () => {
224
+ const { consumer } = installConsumer();
225
+ try {
226
+ const record = insight({ title: LOCAL_PACKED });
227
+ const written = parseReceipt(runPackedWriter(consumer, record, { path: process.env.PATH }));
228
+ assert.equal(written.status, 'created');
229
+ assert.equal(written.teach.state, 'ok');
230
+
231
+ const query = run('dz', ['recall', '--all', '--json', '--project', consumer], {
232
+ cwd: consumer,
233
+ env: process.env,
234
+ timeout: 5000,
235
+ });
236
+ assertOk(query, 'packed real dz query');
237
+ assert.ok(JSON.parse(query.stdout).some((row) =>
238
+ row.domain === 'p-replicator-insights' && row.pattern.includes(LOCAL_PACKED)),
239
+ 'the exact packed writer did not establish the temporary learned projection');
240
+
241
+ const index = path.join(consumer, '.claude', 'insights', 'index.md');
242
+ const before = fs.readFileSync(index);
243
+ const dzState = path.join(consumer, '.dz');
244
+ assert.ok(fs.existsSync(dzState), 'real dz did not establish the deletion fixture');
245
+ fs.rmSync(dzState, { recursive: true, force: true });
246
+ assert.deepEqual(fs.readFileSync(index), before,
247
+ 'removing only .dz must not change one Markdown byte');
248
+
249
+ const context = promptContext(runPackedPrompt(consumer, ''), 'packed fallback after .dz removal');
250
+ assert.match(context, new RegExp(LOCAL_PACKED));
251
+ assert.doesNotMatch(context, new RegExp(DZ_PACKED));
252
+ } finally { fs.rmSync(consumer, { recursive: true, force: true }); }
253
+ });
254
+
255
+ test('P1 - packed harvest writer completes the absent-create-append-delete lifecycle without dz', () => {
256
+ const { consumer, cli } = installConsumer();
257
+ try {
258
+ const index = path.join(consumer, '.claude', 'insights', 'index.md');
259
+ assert.equal(parseReceipt(runPackedWriter(consumer, insight())).status, 'created');
260
+ assert.equal(parseReceipt(runPackedWriter(consumer,
261
+ insight({ title: 'A distinct second record', solution: 'Append it.' }))).status, 'appended');
262
+
263
+ fs.rmSync(path.join(consumer, '.claude', 'insights'), { recursive: true, force: true });
264
+ const hook = assertConsumerPath(
265
+ path.join(consumer, '.claude', 'hooks', 'session-insights.cjs'), consumer);
266
+ const missing = run(process.execPath, [hook], {
267
+ cwd: path.join(consumer, '.claude'),
268
+ env: { ...process.env, PATH: '', CLAUDE_PROJECT_DIR: consumer },
269
+ });
270
+ assertOk(missing, 'packed missing-carrier hook');
271
+ assert.equal(missing.stdout, MISSING_HINT);
272
+ assert.equal(fs.existsSync(path.dirname(index)), false,
273
+ 'the SessionStart reader must leave the carrier absent');
274
+
275
+ const doctor = run(process.execPath, [cli, 'doctor'], { cwd: consumer });
276
+ assertOk(doctor, 'packed doctor after carrier removal');
277
+ assert.match(doctor.stdout, /insights carrier:\s*NOT STARTED/i);
278
+ assert.match(doctor.stdout, /\.claude\/insights\/index\.md/);
279
+
280
+ assert.equal(parseReceipt(runPackedWriter(consumer,
281
+ insight({ date: '2026-08-31' }))).status, 'created');
282
+ assert.ok(fs.existsSync(index), 'the next real write must recreate the removed carrier');
283
+ assert.equal(fs.existsSync(path.join(consumer, '.dz')), false);
284
+ } finally { fs.rmSync(consumer, { recursive: true, force: true }); }
285
+ });
286
+
287
+ test('P4 - packed doctor names a removed insights carrier without failing', () => {
288
+ const { consumer, cli } = installConsumer();
289
+ try {
290
+ assert.equal(parseReceipt(runPackedWriter(consumer, insight())).status, 'created');
291
+ fs.rmSync(path.join(consumer, '.claude', 'insights'), { recursive: true, force: true });
292
+ const doctor = run(process.execPath, [cli, 'doctor'], { cwd: consumer });
293
+ assertOk(doctor, 'packed doctor missing-state preservation');
294
+ assert.match(doctor.stdout, /insights carrier:\s*NOT STARTED/i);
295
+ assert.match(doctor.stdout, /no \.claude\/insights\/index\.md/i);
296
+ } finally { fs.rmSync(consumer, { recursive: true, force: true }); }
297
+ });
298
+
299
+ test('P5 - source-only evidence cannot satisfy packed acceptance', () => {
300
+ const consumer = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-provenance-')));
301
+ try {
302
+ const checkoutHook = path.join(PKG, 'templates', '.claude', 'hooks', 'session-insights.cjs');
303
+ assert.throws(() => assertConsumerPath(checkoutHook, consumer),
304
+ /source-only evidence rejected/,
305
+ 'the provenance safeguard must fire on a real checkout path');
306
+ } finally { fs.rmSync(consumer, { recursive: true, force: true }); }
307
+ });
308
+ });
@@ -0,0 +1,29 @@
1
+ # Specification — Order refund
2
+
3
+ <!-- FIXTURE: prep-traceability-fixture / feature slug order-refund. Not a live project artifact. -->
4
+
5
+ ## Requirements
6
+
7
+ ### FR-order-refund-1 — Validate refund eligibility
8
+
9
+ The service must accept a refund request only while its order is inside the refund window.
10
+
11
+ Acceptance scenarios:
12
+
13
+ - `SC-FR-order-refund-1-1`: Given an eligible paid order, when the buyer requests a refund, then validation succeeds.
14
+
15
+ ### FR-order-refund-2 — Notify the buyer
16
+
17
+ The service must notify the buyer after the refund request is accepted.
18
+
19
+ Acceptance scenarios:
20
+
21
+ - `SC-FR-order-refund-2-1`: Given an accepted refund request, when processing completes, then the buyer receives a confirmation.
22
+
23
+ ### FR-order-refund-3 — Record the audit event
24
+
25
+ The service must append an immutable audit event for every accepted refund request.
26
+
27
+ Acceptance scenarios:
28
+
29
+ - `SC-FR-order-refund-3-1`: Given an accepted refund request, when it is persisted, then an audit event records the order and request identifiers.
@@ -0,0 +1,57 @@
1
+ # Pseudocode — Order refund
2
+
3
+ <!-- FIXTURE: prep-traceability-fixture / feature slug order-refund. Not a live project artifact. -->
4
+
5
+ ## Core Algorithms
6
+
7
+ ### Algorithm: Validate refund eligibility
8
+
9
+ REQUIREMENT: `FR-order-refund-1`
10
+
11
+ REALISES: `SC-FR-order-refund-1-1`
12
+
13
+ INPUT: order, current time
14
+
15
+ OUTPUT: eligibility decision
16
+
17
+ STEPS:
18
+
19
+ 1. Reject an order that is not paid.
20
+ 2. Compare the current time with the refund-window deadline.
21
+ 3. Return the eligibility decision.
22
+
23
+ COMPLEXITY: O(1)
24
+
25
+ ### Algorithm: Record the refund audit event
26
+
27
+ REQUIREMENT: `FR-order-refund-3`
28
+
29
+ REALISES: `SC-FR-order-refund-3-1`
30
+
31
+ INPUT: order identifier, refund request identifier
32
+
33
+ OUTPUT: persisted audit event
34
+
35
+ STEPS:
36
+
37
+ 1. Build an immutable audit event from both identifiers.
38
+ 2. Append the event to the order audit stream.
39
+ 3. Return the persisted event.
40
+
41
+ COMPLEXITY: O(1)
42
+
43
+ ## Scenario Coverage
44
+
45
+ Scenarios in Specification.md: 3 · claimed by an algorithm: 2
46
+
47
+ Not claimed by any algorithm:
48
+
49
+ | Scenario | Reason |
50
+ |---|---|
51
+ | FR-order-refund-2 | out-of-mvp-scope |
52
+
53
+ Claimed by an algorithm but absent from Specification.md:
54
+
55
+ | Algorithm | Claimed ID |
56
+ |---|---|
57
+ | none | none |