@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,274 @@
1
+ #!/usr/bin/env python3
2
+ """Quote-provenance tests, including the ADR-001 red-first A1 fixture."""
3
+
4
+ import sys
5
+
6
+ sys.dont_write_bytecode = True
7
+
8
+ import hashlib
9
+ import io
10
+ import json
11
+ import os
12
+ import tempfile
13
+ import unittest
14
+ from contextlib import redirect_stdout
15
+
16
+ import check_report_evidence as gate
17
+ import evidence_fetch as evidence_fetch
18
+ import quote_provenance as quote_provenance
19
+
20
+
21
+ def _record(body, content_type="text/plain; charset=utf-8"):
22
+ return evidence_fetch.FetchRecord(
23
+ url="https://example.test/source",
24
+ final_url="https://example.test/source",
25
+ status=200,
26
+ sha256_body=hashlib.sha256(body).hexdigest(),
27
+ bytes_len=len(body),
28
+ fetched_at="2026-09-02T00:00:00Z",
29
+ content_type=content_type,
30
+ witness=evidence_fetch._FETCH_WITNESS,
31
+ )
32
+
33
+
34
+ def _fact(quote, body_hash, acquisition="raw-fetch", excerpt_id="excerpt.json"):
35
+ return {
36
+ "claim": quote,
37
+ "source_url": "https://example.test/source",
38
+ "evidence_class": "FETCH_VERIFIED",
39
+ "schema_version": 4,
40
+ "quote": quote,
41
+ "acquisition": acquisition,
42
+ "sha256_body": body_hash,
43
+ "locator": "paragraph 1",
44
+ "excerpt_id": excerpt_id,
45
+ }
46
+
47
+
48
+ def _excerpt(text, body_hash, content_type="text/plain; charset=utf-8"):
49
+ return {
50
+ "sha256_body": body_hash,
51
+ "fetched_at": "2026-09-02T00:00:00Z",
52
+ "source_url": "https://example.test/source",
53
+ "excerpt": text,
54
+ "excerpt_offset": 0,
55
+ "radius_chars": 500,
56
+ "encoding": "utf-8",
57
+ "content_type": content_type,
58
+ "sha256_excerpt": hashlib.sha256(text.encode("utf-8")).hexdigest(),
59
+ }
60
+
61
+
62
+ class QuoteGateRedFirstTests(unittest.TestCase):
63
+ def test_report_quote_absent_from_source_body_is_named_violation(self):
64
+ body = b"The preserved source discusses iron status and recovery."
65
+ excerpt = body.decode("utf-8")
66
+ body_hash = hashlib.sha256(body).hexdigest()
67
+ excerpt_id = "a1.json"
68
+ quote = "The source explicitly recommends immediate supplementation"
69
+ fact = _fact(quote, body_hash, excerpt_id=excerpt_id)
70
+ report = (
71
+ 'The source states "The source explicitly recommends immediate '
72
+ 'supplementation" in its recommendation.'
73
+ )
74
+
75
+ with tempfile.TemporaryDirectory() as tmp:
76
+ with open(os.path.join(tmp, excerpt_id), "w", encoding="utf-8") as handle:
77
+ json.dump(
78
+ _excerpt(excerpt, body_hash),
79
+ handle,
80
+ )
81
+ findings, _ = gate.evaluate_quotes(report, [fact], tmp)
82
+
83
+ report_path = os.path.join(tmp, "report.md")
84
+ facts_path = os.path.join(tmp, "facts.json")
85
+ with open(report_path, "w", encoding="utf-8") as handle:
86
+ handle.write(report)
87
+ with open(facts_path, "w", encoding="utf-8") as handle:
88
+ json.dump([fact], handle)
89
+ output = io.StringIO()
90
+ with redirect_stdout(output):
91
+ exit_code = gate.main([
92
+ "--report", report_path,
93
+ "--facts", facts_path,
94
+ "--excerpts", tmp,
95
+ "--json",
96
+ ])
97
+
98
+ self.assertEqual([finding.kind for finding in findings], ["QUOTE_NOT_IN_SOURCE"])
99
+ self.assertIn(fact["quote"], findings[0].detail)
100
+ self.assertEqual(exit_code, 1)
101
+ payload = json.loads(output.getvalue())
102
+ self.assertIn("QUOTE_NOT_IN_SOURCE", [finding["kind"] for finding in payload["findings"]])
103
+ self.assertEqual(payload["counts"]["quote-not-in-excerpt"], 1)
104
+
105
+ def test_present_control_and_tool_summary_ceiling(self):
106
+ quote = "The source explicitly recommends gradual refeeding"
107
+ body = quote.encode("utf-8")
108
+ body_hash = hashlib.sha256(body).hexdigest()
109
+ excerpt = _excerpt(quote, body_hash)
110
+ with tempfile.TemporaryDirectory() as tmp:
111
+ for excerpt_id in ("present.json", "summary.json"):
112
+ with open(os.path.join(tmp, excerpt_id), "w", encoding="utf-8") as handle:
113
+ json.dump(excerpt, handle)
114
+ present_findings, present_counts = gate.evaluate_quotes(
115
+ f'The report says "{quote}".',
116
+ [_fact(quote, body_hash, excerpt_id="present.json")],
117
+ tmp,
118
+ )
119
+ summary_findings, _ = gate.evaluate_quotes(
120
+ f'The report says "{quote}".',
121
+ [_fact(quote, body_hash, acquisition="tool-summary", excerpt_id="summary.json")],
122
+ tmp,
123
+ )
124
+ self.assertEqual(present_findings, [])
125
+ self.assertEqual(present_counts["quote-verbatim-confirmed"], 1)
126
+ self.assertEqual([finding.kind for finding in summary_findings], ["QUOTE_METHOD_INELIGIBLE"])
127
+ self.assertIn("tool-summary", summary_findings[0].detail)
128
+
129
+
130
+ class QuoteRecordTests(unittest.TestCase):
131
+ def test_closed_acquisition_vocabulary_and_no_writable_verdict(self):
132
+ fields = quote_provenance.QuoteRecord.__dataclass_fields__
133
+ self.assertNotIn("verbatim_status", fields)
134
+ with self.assertRaisesRegex(ValueError, "closed set"):
135
+ quote_provenance.QuoteRecord("valid quote", "Raw-Download", "u", "a" * 64)
136
+ with self.assertRaisesRegex(ValueError, "closed set"):
137
+ quote_provenance.QuoteRecord("valid quote", "webfetch", "u", "a" * 64)
138
+ with self.assertRaisesRegex(ValueError, "non-blank"):
139
+ quote_provenance.QuoteRecord(" ", "raw-fetch", "u", "a" * 64)
140
+
141
+ def test_unknown_or_absent_method_is_honest_fifth_read_state(self):
142
+ for value in (None, "Raw-Fetch", "webfetch", ""):
143
+ with self.subTest(value=value):
144
+ self.assertEqual(quote_provenance.read_acquisition(value), "method-unknown")
145
+
146
+ def test_legacy_quote_is_denied_without_regrading_other_trust_fields(self):
147
+ quote = "A legacy quoted phrase long enough for the gate"
148
+ legacy = {
149
+ "claim": quote,
150
+ "source_url": "https://example.test/legacy",
151
+ "evidence_class": "FETCH_VERIFIED",
152
+ "trust_class": "SELF_ATTESTED",
153
+ "confidence": 0.4,
154
+ "schema_version": 3,
155
+ "quote": quote,
156
+ }
157
+ trust_before = {key: legacy[key] for key in ("evidence_class", "trust_class", "confidence")}
158
+ findings, counts = gate.evaluate_quotes(f'The report says "{quote}".', [legacy], "unused")
159
+ self.assertEqual([finding.kind for finding in findings], ["QUOTE_NO_EXCERPT"])
160
+ self.assertIn("unverified", findings[0].detail)
161
+ self.assertEqual(counts["quote-method-unknown"], 1)
162
+ self.assertEqual(
163
+ {key: legacy[key] for key in ("evidence_class", "trust_class", "confidence")},
164
+ trust_before,
165
+ )
166
+
167
+
168
+ class VerbatimPolicyTests(unittest.TestCase):
169
+ def setUp(self):
170
+ self.text = 'The source says “iron\u00a0status & recovery” — exactly.'
171
+ self.body_hash = hashlib.sha256(self.text.encode("utf-8")).hexdigest()
172
+ self.excerpt = _excerpt(self.text, self.body_hash, "text/html; charset=utf-8")
173
+
174
+ def fact(self, **overrides):
175
+ fact = _fact('iron status & recovery" - exactly', self.body_hash, excerpt_id="x.json")
176
+ fact.update(overrides)
177
+ return fact
178
+
179
+ def test_closed_verdict_set_and_normalized_exact_match(self):
180
+ self.assertEqual(
181
+ quote_provenance.VERBATIM_VERDICTS,
182
+ ("verbatim-confirmed", "not-in-excerpt", "no-excerpt", "method-ineligible",
183
+ "hash-mismatch", "method-unknown"),
184
+ )
185
+ verdict = quote_provenance.verify_verbatim(
186
+ 'iron status & recovery" - exactly', self.excerpt, self.fact()
187
+ )
188
+ self.assertEqual(verdict, "verbatim-confirmed")
189
+
190
+ def test_overlap_paraphrase_does_not_count_as_verbatim(self):
191
+ verdict = quote_provenance.verify_verbatim(
192
+ "iron status recovery was described exactly", self.excerpt, self.fact()
193
+ )
194
+ self.assertEqual(verdict, "not-in-excerpt")
195
+
196
+ def test_search_listing_and_manual_need_the_same_reconciliation(self):
197
+ span = 'iron status & recovery" - exactly'
198
+ for acquisition in ("search-listing", "manual"):
199
+ with self.subTest(acquisition=acquisition):
200
+ self.assertEqual(
201
+ quote_provenance.verify_verbatim(
202
+ span, self.excerpt, self.fact(acquisition=acquisition)
203
+ ),
204
+ "verbatim-confirmed",
205
+ )
206
+ self.assertEqual(
207
+ quote_provenance.verify_verbatim(span, None, self.fact(acquisition=acquisition)),
208
+ "no-excerpt",
209
+ )
210
+
211
+ def test_every_refusal_verdict_is_reachable(self):
212
+ cases = (
213
+ (None, self.fact(), "no-excerpt"),
214
+ (self.excerpt, self.fact(acquisition="tool-summary"), "method-ineligible"),
215
+ (dict(self.excerpt, sha256_excerpt="0" * 64), self.fact(), "hash-mismatch"),
216
+ (self.excerpt, self.fact(acquisition=None), "method-unknown"),
217
+ )
218
+ for excerpt, fact, expected in cases:
219
+ with self.subTest(expected=expected):
220
+ self.assertEqual(quote_provenance.verify_verbatim("anything at all", excerpt, fact), expected)
221
+
222
+ def test_quote_found_only_across_join_of_two_excerpts_is_rejected(self):
223
+ leaves = [
224
+ _excerpt("boundary phrase begins", self.body_hash),
225
+ _excerpt(" and ends here", self.body_hash),
226
+ ]
227
+ verdict = quote_provenance.verify_verbatim(
228
+ "phrase begins and ends", leaves, self.fact()
229
+ )
230
+ self.assertEqual(verdict, "not-in-excerpt", "a JOIN of excerpt leaves is never searched")
231
+
232
+
233
+ class CaptureTests(unittest.TestCase):
234
+ def test_capture_round_trip_is_bounded_and_self_ignoring(self):
235
+ quote = "bounded phrase copied exactly from source bytes"
236
+ body = (("prefix " * 500) + quote + (" suffix" * 500)).encode("utf-8")
237
+ record = _record(body)
238
+ captured = quote_provenance.capture_excerpt(body, quote, record)
239
+ self.assertLessEqual(len(captured["excerpt"]), quote_provenance.EXCERPT_MAX_CHARS)
240
+ self.assertNotEqual(captured["excerpt"].encode("utf-8"), body)
241
+ fact = _fact(quote, record.sha256_body, excerpt_id=captured["excerpt_id"])
242
+ with tempfile.TemporaryDirectory() as tmp:
243
+ path = quote_provenance.write_excerpt(tmp, captured)
244
+ loaded = quote_provenance.load_excerpt(tmp, captured["excerpt_id"])
245
+ self.assertTrue(os.path.exists(os.path.join(tmp, ".gitignore")))
246
+ with open(os.path.join(tmp, ".gitignore"), encoding="utf-8") as handle:
247
+ self.assertEqual(handle.read(), "*\n")
248
+ self.assertEqual(path, os.path.join(tmp, captured["excerpt_id"]))
249
+ self.assertEqual(quote_provenance.verify_verbatim(quote, loaded, fact), "verbatim-confirmed")
250
+
251
+ def test_forged_excerpt_and_wrong_source_body_are_refused(self):
252
+ quote = "captured phrase"
253
+ body = quote.encode("utf-8")
254
+ record = _record(body)
255
+ with self.assertRaisesRegex(quote_provenance.ExcerptCaptureRefused, "hash-mismatch"):
256
+ quote_provenance.capture_excerpt(body + b" forged", quote, record)
257
+ captured = quote_provenance.capture_excerpt(body, quote, record)
258
+ captured["excerpt"] = "author supplied captured phrase"
259
+ fact = _fact(quote, record.sha256_body, excerpt_id=captured["excerpt_id"])
260
+ self.assertEqual(quote_provenance.verify_verbatim(quote, captured, fact), "hash-mismatch")
261
+
262
+ def test_non_text_and_undecodable_bodies_refuse_by_name(self):
263
+ pdf = b"%PDF-1.7 quoted phrase"
264
+ with self.assertRaisesRegex(quote_provenance.ExcerptCaptureRefused, "non-text"):
265
+ quote_provenance.capture_excerpt(pdf, "quoted phrase", _record(pdf, "application/pdf"))
266
+ invalid = b"quoted phrase \xff"
267
+ with self.assertRaisesRegex(quote_provenance.ExcerptCaptureRefused, "undecodable"):
268
+ quote_provenance.capture_excerpt(
269
+ invalid, "quoted phrase", _record(invalid, "text/plain; charset=ascii")
270
+ )
271
+
272
+
273
+ if __name__ == "__main__":
274
+ unittest.main()
@@ -58,7 +58,8 @@ class SuiteCompletenessTests(unittest.TestCase):
58
58
  """The specific omission AM-14 caught, pinned by name so it cannot recur."""
59
59
  collected = _collected()
60
60
  for required in ("test_ed25519_verifier", "test_evidence_provenance", "test_goap_planner",
61
- "test_signature_v3", "test_population_match", "test_risk_absolute"):
61
+ "test_signature_v3", "test_population_match", "test_quote_provenance",
62
+ "test_risk_absolute"):
62
63
  self.assertIn(required, collected)
63
64
 
64
65
  def test_no_enumerated_run_command_is_reintroduced(self):
@@ -138,6 +138,10 @@ Read module: `modules/01-agent-review.md`
138
138
 
139
139
  **Goal:** Find all extractable knowledge in the project using parallel agents.
140
140
 
141
+ Each extractor delivers a FILE at its own absolute `TRACE_PATH` (terminal `Status:` line last),
142
+ never a reply — see `.claude/rules/swarm-file-evidence.md` and the assignment table in
143
+ `modules/01-agent-review.md`. A dead extractor and a working one are both silent.
144
+
141
145
  **Swarm Strategy — 5 parallel extraction agents:**
142
146
 
143
147
  | Agent | Scope | What It Looks For |
@@ -351,17 +351,10 @@ Context flow: [summary]
351
351
  - Use `templates/pipeline-template.md` for the orchestrator
352
352
  - Substitute `{{PLACEHOLDERS}}` with domain-specific content
353
353
 
354
- **Master Validation Checklist:**
355
- - [ ] Every skill has `SKILL.md` with name, description, triggers
356
- - [ ] Every skill has explicit input/output format
357
- - [ ] Every skill has anti-pattern warnings
358
- - [ ] Orchestrator references all skills by correct path
359
- - [ ] Quality gates have scoring systems and verdicts
360
- - [ ] Checkpoints have user commands documented
361
- - [ ] Context flow matches phase ordering
362
- - [ ] Git commits defined for each major phase
363
- - [ ] No circular dependencies in skill graph
364
- - [ ] All templates substituted (no `{{PLACEHOLDER}}` remaining)
354
+ **Master Validation Checklist:** every skill has `SKILL.md` (name/description/triggers), explicit
355
+ input/output format and anti-pattern warnings; orchestrator references correct paths; quality gates
356
+ carry scoring + verdicts; checkpoints document user commands; context flow matches phase order;
357
+ git commits per major phase; no circular dependencies; no `{{PLACEHOLDER}}` left unsubstituted.
365
358
 
366
359
  **Checkpoint:**
367
360
  ```
@@ -403,11 +396,18 @@ When designing pipelines, identify parallelism opportunities and use swarm agent
403
396
  **Swarm Design Pattern:**
404
397
  ```
405
398
  1. DECOMPOSE: Break task into independent subtasks
406
- 2. SPAWN: Launch parallel agents via Task tool
407
- 3. AGGREGATE: Collect results, merge, resolve conflicts
408
- 4. VALIDATE: Cross-check aggregate for consistency
399
+ 2. ASSIGN: One WORK_UNIT_ID + one absolute TRACE_PATH per subtask; record the launch instant
400
+ 3. SPAWN: Launch parallel agents via Task tool, each carrying its assignment
401
+ 4. RECEIPT: node .claude/hooks/check-swarm-receipts.cjs <manifest> (0 ok / 1 undelivered / 2 not run)
402
+ 5. AGGREGATE: Collect results FROM THE FILES, merge, resolve conflicts
403
+ 6. VALIDATE: Cross-check aggregate for consistency
409
404
  ```
410
405
 
406
+ A forged pipeline that skips steps 2 and 4 cannot tell a dead agent from a working one — both are
407
+ silent — so it will report a stage as running when nothing is. See
408
+ `.claude/rules/swarm-file-evidence.md`; the full seam text to paste into the forged pipeline lives
409
+ in `cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md` (Section 2).
410
+
411
411
  **Swarm Agent Template:**
412
412
  ```markdown
413
413
  | Agent | Scope | Criteria | Independence |
@@ -467,15 +467,10 @@ Skills written for claude.ai use `/mnt/skills/user/` paths. For Claude Code port
467
467
 
468
468
  ## Anti-Patterns
469
469
 
470
- **Monolithic Skills** — skills with 1000+ lines doing everything
471
- **Implicit Dependencies** skills that assume others exist without declaring
472
- **Skipping Quality Gates** generating toolkit from unvalidated documentation
473
- **Hardcoded Paths** using absolute paths instead of relative/mapped
474
- ❌ **Duplicating Logic** — copying skill content instead of view() references
475
- ❌ **No Checkpoints** — running entire pipeline without user feedback points
476
- ❌ **No Error Recovery** — pipeline that can't resume from a failed phase
477
- ❌ **Over-Engineering** — creating 20 skills when 5 would suffice
478
- ❌ **No Fallbacks** — skills that crash when an optional dependency is missing
470
+ ❌ Monolithic skills (1000+ lines) · implicit undeclared dependencies · skipping quality gates
471
+ (toolkit from unvalidated docs) · hardcoded absolute paths · duplicating logic instead of view() ·
472
+ no checkpoints · no error recovery / resume · over-engineering (20 skills where 5 suffice) ·
473
+ no fallbacks for optional dependencies
479
474
 
480
475
  ## Quality Standards
481
476
 
@@ -140,9 +140,14 @@ Swarm: 5 parallel validators via Task tool
140
140
  ├── validator-pseudocode → Story coverage, implementability
141
141
  └── validator-coherence → Cross-document consistency
142
142
 
143
+ Receipt: per .claude/rules/swarm-file-evidence.md — each validator writes its findings
144
+ to its own absolute TRACE_PATH,
145
+ terminal `Status: completed` / `Status: failed` last, then replies one line.
146
+ Aggregation reads the FILES and refuses to score a validator that delivered none —
147
+ a validator that died is silent exactly like one still working.
143
148
  Scoring: 0-100 per validator
144
- Aggregate: weighted average
145
- Blocking: score <50 = BLOCKED
149
+ Aggregate: weighted average over terminal-completed receipts only
150
+ Blocking: score <50 = BLOCKED; any missing receipt = BLOCKED
146
151
  Iterations: max 3
147
152
 
148
153
  Verdicts:
@@ -199,8 +199,26 @@ UNIFIED RESULT
199
199
  1. **Independence** — swarm agents must not depend on each other (or explicitly declare dependencies)
200
200
  2. **Clear scope** — each agent has a defined scope and criteria
201
201
  3. **Parallel execution** — use Task tool to launch agents concurrently
202
- 4. **Aggregation** — orchestrator merges results, resolves conflicts
202
+ 4. **Aggregation** — orchestrator merges results FROM FILES, resolves conflicts
203
203
  5. **Iterative** — if aggregate fails quality gate, re-run with fixes (max N iterations)
204
+ 6. **Positive file receipt** — every agent's RESULT is a file at a named path, never its reply
205
+
206
+ #### Positive file receipt (required)
207
+
208
+ Before dispatch, allocate one `RUN_ID` and, for every unit, a unique `WORK_UNIT_ID` plus an
209
+ absolute `TRACE_PATH` unique to `(RUN_ID, WORK_UNIT_ID)`; record the launch instant and pass both
210
+ fields to the worker. Each worker must write a substantive body ending in `Status: completed` or
211
+ `Status: failed` to `TRACE_PATH` before it returns its one-line pointer. Before merge, verify every
212
+ path is a regular non-symlink file, non-whitespace, post-launch, and terminal. Narrative output or
213
+ silence is never a receipt. Name missing, stale, partial, unreadable, duplicate, failed, dead-PID,
214
+ or probe-error units and refuse merge, aggregation, or completion until every required receipt is
215
+ valid and completed. Run `node .claude/hooks/check-swarm-receipts.cjs <manifest>` — exit 0 all
216
+ completed, exit 1 undelivered or failed, exit 2 the check did not run. See
217
+ `.claude/rules/swarm-file-evidence.md` for mechanics and the bounded non-atomic-write exception.
218
+
219
+ Why this belongs in the pattern and not in the orchestrator's good intentions: a worker that died
220
+ looks exactly like a worker that is still running, because both are silent. The absence of a
221
+ receipt is indistinguishable from unfinished work and therefore reads as progress.
204
222
 
205
223
  ### Swarm Design Template
206
224
  ```markdown
@@ -206,12 +206,23 @@ OUT: Verdict + score + gap register + iteration count
206
206
  ```
207
207
  IN: Task definition + agent table (scope, criteria, independence, output format)
208
208
  DO: 1. DECOMPOSE into independent subtasks
209
- 2. SPAWN parallel agents via Task tool
210
- 3. Each agent: scoped input -> execute -> structured output
211
- 4. AGGREGATE: collect, deduplicate, resolve conflicts
212
- 5. VALIDATE aggregate for consistency
213
- 6. IF inconsistent: run cross-validation agent (reads all outputs)
214
- OUT: Unified result + conflict resolution notes
209
+ 2. ASSIGN one WORK_UNIT_ID + one absolute TRACE_PATH per subtask; record launch instant
210
+ 3. SPAWN parallel agents via Task tool, each carrying its assignment
211
+ 4. Each agent: scoped input -> execute -> write substantive body + terminal
212
+ `Status: completed` / `Status: failed` to TRACE_PATH -> reply one line
213
+ 5. RECEIPT CHECK before merge: regular, non-symlink, non-whitespace, post-launch, terminal
214
+ (node .claude/hooks/check-swarm-receipts.cjs <manifest>; 0 ok / 1 undelivered / 2 not run)
215
+ 6. AGGREGATE from the files: collect, deduplicate, resolve conflicts
216
+ 7. VALIDATE aggregate for consistency
217
+ 8. IF inconsistent: run cross-validation agent (reads all outputs)
218
+ OUT: Unified result + conflict resolution notes + valid/required receipt ratio
219
+ ```
220
+
221
+ Narrative output and silence are never receipts, and an invalid receipt refuses merge, aggregation
222
+ and completion — a dead agent and a live one are both silent, so only the file separates them. Full
223
+ contract: `.claude/rules/swarm-file-evidence.md`.
224
+
225
+ ```
215
226
  ```
216
227
 
217
228
  **PU Unicorn Example:** `knowledge-extractor` Phase 1 runs 5 agents (extractor-patterns,
@@ -202,7 +202,6 @@ INPUT → PHASE 1 → PHASE 2 → ... → OUTPUT
202
202
  ```markdown
203
203
  ## Anti-Patterns
204
204
 
205
- ❌ [What NOT to do — reason]
206
205
  ❌ [What NOT to do — reason]
207
206
  ```
208
207
 
@@ -65,11 +65,14 @@ Always flag these terms and suggest specific replacements:
65
65
  **Score <50 = BLOCKED from development.** Provide rewrite suggestions.
66
66
 
67
67
  **Blocking floor — the weakest link decides, never the average.** Independently of the score, a
68
- requirement is BLOCKED if `Testable = 0` (no acceptance criteria) or `Completeness = 0` (rubric "No
69
- AC"). A story can total 72/100 with neither, and 72 reads as "minor fixes" in the table above. A non-zero `Testable` or `Completeness` REQUIRES quoting the acceptance criteria it scores — no quote
70
- means 0, because the agent that scores is the agent the floor binds. The
71
- floor is closed at those two: `Measurable` and `Traceability` are deliberately excluded — see
72
- `references/scoring-system.md` "Blocking floor" for the worked case and the reason.
68
+ requirement is BLOCKED if `Testable = 0` (no acceptance criteria), `Completeness = 0` (rubric "No
69
+ AC"), or `Traceability = 0` (no AC has a named scenario in the Criterion scenarios table). A story can
70
+ total 72/100 with no acceptance criteria, and 72 reads as "minor fixes" in the table above. A
71
+ non-zero `Testable` or `Completeness` REQUIRES quoting the acceptance criteria it scores; a non-zero
72
+ `Traceability` REQUIRES quoting the Criterion scenarios table or referencing its document and heading.
73
+ No artifact means 0, because the agent that scores is the agent the floor binds. The floor is closed
74
+ at those three; `Measurable` is deliberately excluded — see `references/scoring-system.md` →
75
+ "Blocking floor" for the worked cases and the reason.
73
76
 
74
77
  ## Output Format
75
78
 
@@ -77,6 +80,7 @@ floor is closed at those two: `Measurable` and `Traceability` are deliberately e
77
80
 
78
81
  ```markdown
79
82
  # Requirements Testability Analysis
83
+ Spec revision: sha256:<digest of 01_specification.md>
80
84
 
81
85
  ## Summary
82
86
  - Stories analyzed: X
@@ -90,6 +94,11 @@ floor is closed at those two: `Measurable` and `Traceability` are deliberately e
90
94
  | US-001 | ... | 92/100 | 6/6 ✓ | 5/5 ✓ | READY |
91
95
  | US-002 | ... | 45/100 | 3/6 ✗ | 2/5 ✗ | BLOCKED |
92
96
 
97
+ ## Criterion scenarios
98
+ | Criterion | Scenario |
99
+ |-----------|----------|
100
+ | AC-example-1 | Successful example flow |
101
+
93
102
  ## Detailed Analysis: US-002 (BLOCKED)
94
103
 
95
104
  ### INVEST Analysis
@@ -110,6 +119,10 @@ floor is closed at those two: `Measurable` and `Traceability` are deliberately e
110
119
  - Add AC: "Given X, when Y, then Z within 200ms"
111
120
  ```
112
121
 
122
+ Compute the revision line from the specification bytes with
123
+ `sha256sum docs/features/<f>/01_specification.md`. The `## Criterion scenarios` table is the artifact
124
+ the Traceability score keys on: it maps each AC id to a named scenario.
125
+
113
126
  ### Security Acceptance Criteria (scoring: +5 present / -10 missing, see Scoring Bonus below)
114
127
 
115
128
  When requirements involve authentication, data storage, external APIs, or multi-tenancy,
@@ -0,0 +1,67 @@
1
+ # Feature report contracts (`/feature`, Phases 2–4)
2
+
3
+ Byte-level formats that the packaged gates enforce. Read this when WRITING one of the three reports;
4
+ the gates are the machine half: `scripts/check-pipeline-gaps.sh` (`--report-revision`,
5
+ `--criterion-scenarios`, `--completion`) and `.claude/hooks/check-review-contract.cjs`. Exit codes
6
+ everywhere: `0` pass · `1` named gap(s), one line each · `2` could not establish (missing, unreadable,
7
+ symlinked or malformed input). Neither non-zero status is ever a warning.
8
+
9
+ ## Shared keys
10
+
11
+ - **AC ids** are `### AC-<slug>-<n>` headings in `docs/features/<f>/01_specification.md`
12
+ (`(FR|NFR|AC)-[A-Za-z0-9]+(-[A-Za-z0-9]+)*-[0-9]+`; fenced code blocks are ignored). FR/NFR stay under
13
+ the Phase 1 specification↔pseudocode gate; the three contracts below key on **AC-** ids only.
14
+ - **Spec revision line** — `Spec revision: sha256:<64 lowercase hex>`, the SHA-256 of the BYTES of
15
+ `01_specification.md` (`sha256sum docs/features/<f>/01_specification.md`), within the first 20 lines
16
+ of `validation-report.md` and of `review-report.md`. A report is bound to the revision it judged;
17
+ editing the specification afterwards makes the report stale by construction.
18
+
19
+ ## validation-report.md (Phase 2)
20
+
21
+ ```markdown
22
+ # Requirements Testability Analysis
23
+ Spec revision: sha256:<digest of 01_specification.md>
24
+
25
+ ## Criterion scenarios
26
+ | Criterion | Scenario |
27
+ |-----------|----------|
28
+ | AC-example-1 | Successful example flow |
29
+ ```
30
+
31
+ `## Criterion scenarios` maps every AC id to the named BDD scenario that covers it — the artifact the
32
+ `Traceability` blocking floor keys on (`references/scoring-system.md`). It is NOT the algorithm-level
33
+ `## Scenario Coverage` block that `sparc-prd-mini` writes into `02_pseudocode.md`. An AC id without a
34
+ row, an undeclared id, or an empty scenario cell is a named gap; a missing table cannot be established.
35
+
36
+ ## 05_completion.md (Phase 3)
37
+
38
+ ```markdown
39
+ ## Criterion coverage
40
+ | Criterion | Test file | Test title |
41
+ |-----------|-----------|------------|
42
+ | AC-example-1 | tests/example.test.js | rejects an invalid request |
43
+ ```
44
+
45
+ One row per AC id. `Test file` is relative to the project root and must be a regular file inside it
46
+ (absolute, `..`-escaping and symlinked paths are refused); `Test title` must occur verbatim in that
47
+ file. Both directions compare: an AC id without a row and a row whose id is not in the specification
48
+ are gaps. A table of names is not evidence — the gate opens the file.
49
+
50
+ ## review-report.md (Phase 4)
51
+
52
+ ```markdown
53
+ Reviewer family: claude|codex|human|unknown
54
+ Spec revision: sha256:<64 lowercase hex>
55
+
56
+ ## Spec conformance
57
+ | Criterion | Verdict | Evidence |
58
+ |-----------|---------|----------|
59
+ | AC-example-1 | met | tests/example.test.js — rejects an invalid request |
60
+ ```
61
+
62
+ Input contract: the review receives `01_specification.md` and `validation-report.md`; a review that
63
+ was not given the specification is incomplete by construction and must say so instead of grading code
64
+ quality alone. Output contract: both header lines within the first 20 lines (`Reviewer family:` is a
65
+ DISCLOSURE — the package refuses silence but cannot verify which family reviewed); every AC id exactly
66
+ once in `## Spec conformance`; `Verdict` is exactly `met`, `not met` or `unverifiable` (lowercase);
67
+ a `met` or `not met` row carries non-empty evidence; a `not met` row ideally carries a reproducer.
@@ -31,7 +31,7 @@ Total Score = INVEST Score (50%) + SMART Score (30%) + Quality Score (20%)
31
31
 
32
32
  | Criterion | Weight | Points | Calculation |
33
33
  |-----------|--------|--------|-------------|
34
- | Traceability | 10% | 0-10 | 10 if linked to tests, 5 if partial, 0 if none |
34
+ | Traceability | 10% | 0-10 | 10 if every AC id has a named scenario in the Criterion scenarios table; 5 if some do (the uncovered AC ids MUST be listed by name); 0 if none do, or the block is absent ⇒ the floor applies |
35
35
  | Completeness | 10% | 0-10 | See completeness rubric below |
36
36
 
37
37
  ### Completeness Rubric
@@ -56,12 +56,13 @@ Total Score = INVEST Score (50%) + SMART Score (30%) + Quality Score (20%)
56
56
  ### Blocking floor (overrides the total, both directions)
57
57
 
58
58
  **The weakest link decides, never the average.** A requirement is BLOCKED — whatever its total says —
59
- if either of these is zero:
59
+ if any of these is zero:
60
60
 
61
61
  | Criterion | Zero means | Why it vetoes |
62
62
  |-----------|-----------|---------------|
63
63
  | `Testable` (INVEST, 8) | no acceptance criteria exist | the gate exists to block untestable requirements |
64
64
  | `Completeness` (Quality, 10) | "No AC" on the rubric above | nothing states what "done" is |
65
+ | `Traceability` (Quality, 10) | no AC has a named scenario in the Criterion scenarios table | a story can otherwise pass validation with no scenario or test on any criterion |
65
66
 
66
67
  Without this floor the total alone lets an untestable requirement through. Worked case: a story with
67
68
  NO acceptance criteria and NO test links loses `Testable` 8, `Completeness` 10 and `Traceability` 10,
@@ -76,10 +77,18 @@ criteria AND is bound by the floor, so a score is not evidence of anything — r
76
77
  > the actual text, with its document and heading. `Testable = 4` means the AC exist and are vague:
77
78
  > quote them. **No quote ⇒ the score is 0 ⇒ the floor applies.**
78
79
 
79
- **The floor is CLOSED at these two.** `Measurable` and `Traceability` are deliberately NOT on it: a
80
- requirement with no number is often correct ("the user can export the report as PDF" is specific,
81
- testable and unmeasurable), so vetoing on `Measurable` would turn a false pass into a false block.
82
- Widening this list needs the same kind of worked case as the one above.
80
+ > A non-zero `Traceability` REQUIRES the Criterion scenarios table (AC id named scenario) quoted,
81
+ > or referenced by document and heading. **No table the score is 0 the floor applies.**
82
+
83
+ Measured worked case: story FR-009 had 27 criteria with no IDs, more than 10 of them uncovered by
84
+ any scenario, and passed validation. Its validation report 3.1 was written against document revision
85
+ 2; only a human noticed. With no named AC-id → scenario artifact, `Traceability = 0` and the floor
86
+ blocks that contour instead of letting its total hide the missing coverage.
87
+
88
+ **The floor is CLOSED at these three.** `Measurable` is deliberately NOT on it: a requirement with no
89
+ number is often correct ("the user can export the report as PDF" is specific, testable and
90
+ unmeasurable), so vetoing on `Measurable` would turn a false pass into a false block. Widening this
91
+ list needs the same kind of worked case as the ones above.
83
92
 
84
93
  ## Quality Gate Rules
85
94
 
@@ -144,7 +144,27 @@ const CJM_META = {
144
144
  // Lock result (✅)
145
145
  ```
146
146
 
147
- **Design адаптация под {INDUSTRY}:**
147
+ **Design: откуда берётся облик. Порядок приоритета, и он не симметричен.**
148
+
149
+ Прежде чем открывать таблицу ниже, ответьте на один вопрос: **воспроизводит ли проект НАЗВАННЫЙ
150
+ существующий продукт, и снят ли его облик?**
151
+
152
+ | Состояние источника | Откуда берётся палитра/типографика/тон |
153
+ |---|---|
154
+ | источник назван, облик **СНЯТ** | **из снятого облика источника** — таблица ниже НЕ применяется |
155
+ | источник назван, облик **НЕ ИЗМЕРЕН** (не удалось снять) | таблица ниже — как **ФОЛБЭК**, и он подписывается словом «фолбэк» вместе с причиной, по которой облик не сняли |
156
+ | **источника нет** (продукт не воспроизводится) | таблица ниже — как **ФОЛБЭК** по отрасли, и он тоже подписывается |
157
+
158
+ Почему это не две равноправные ветви. Отраслевая палитра — это догадка о том, как продукт мог бы
159
+ выглядеть. Когда есть живой источник, догадка не «одна из двух опций»: она хуже измерения ровно
160
+ настолько, насколько выдуманное отличается от наблюдённого. Прототип, который тихо подставил
161
+ Emerald, потому что проект про здоровье, читается точно так же, как прототип, у которого Emerald
162
+ измерен у источника — и именно эта неразличимость и есть дефект.
163
+
164
+ Подпись обязательна во всех состояниях, включая «источника нет»: неподписанная палитра и
165
+ подписанная выглядят одинаково для следующего читателя, а значат разное.
166
+
167
+ **Design адаптация под {INDUSTRY} — ФОЛБЭК, применяется только в двух нижних состояниях:**
148
168
 
149
169
  | Industry | Палитра | Иконки | Тон |
150
170
  |----------|---------|--------|-----|