@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,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", "
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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.
|
|
407
|
-
3.
|
|
408
|
-
4.
|
|
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
|
-
❌
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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.
|
|
210
|
-
3.
|
|
211
|
-
4.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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,
|
|
@@ -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)
|
|
69
|
-
AC")
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
`
|
|
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,
|
package/templates/.claude/skills/requirements-validator/references/feature-report-contracts.md
ADDED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|----------|---------|--------|-----|
|