@dzhechkov/p-replicator 1.13.1 → 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 +65 -13
- package/README.md +19 -1
- package/package.json +3 -3
- package/sbom.json +142 -12
- package/scripts/check-pipeline-gaps.sh +510 -21
- package/src/utils.js +1 -0
- package/templates/.claude/commands/feature.md +43 -1
- package/templates/.claude/hooks/check-review-contract.cjs +205 -0
- package/templates/.claude/hooks/statusline.cjs +1 -1
- package/templates/.claude/rules/replicate-pipeline.md +4 -3
- 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/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 +9 -7
- package/tests/unit/check-review-contract.test.js +181 -0
- package/tests/unit/honest-failure-rules.test.js +18 -3
- package/tests/unit/sync-templates-guard.test.js +46 -3
- package/tests/unit/traceability-completion-gate.test.js +267 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Specification — Demo gate
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
### FR-demo-gate-1 — Validate a submitted value
|
|
6
|
+
|
|
7
|
+
The demo validator must distinguish accepted values from rejected values.
|
|
8
|
+
|
|
9
|
+
### AC-demo-gate-1 — Accept a valid value
|
|
10
|
+
|
|
11
|
+
Given a valid value, when the validator runs, then it reports acceptance.
|
|
12
|
+
|
|
13
|
+
### AC-demo-gate-2 — Reject an invalid value
|
|
14
|
+
|
|
15
|
+
Given an invalid value, when the validator runs, then it reports rejection.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Pseudocode — Demo gate
|
|
2
|
+
|
|
3
|
+
## Validation algorithm
|
|
4
|
+
|
|
5
|
+
### Algorithm: Validate submitted value
|
|
6
|
+
|
|
7
|
+
REQUIREMENT: `FR-demo-gate-1`
|
|
8
|
+
REQUIREMENT: `AC-demo-gate-1`
|
|
9
|
+
REQUIREMENT: `AC-demo-gate-2`
|
|
10
|
+
|
|
11
|
+
INPUT: submitted value
|
|
12
|
+
|
|
13
|
+
OUTPUT: acceptance decision
|
|
14
|
+
|
|
15
|
+
STEPS:
|
|
16
|
+
|
|
17
|
+
1. Compare the submitted value with the accepted fixture value.
|
|
18
|
+
2. Return acceptance for a match.
|
|
19
|
+
3. Return rejection otherwise.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Review report — Demo gate
|
|
2
|
+
Reviewer family: claude
|
|
3
|
+
Spec revision: sha256:264708a769ad0d07d715420d82278461c5f1b831dd365eaaf6be051db18d47e8
|
|
4
|
+
|
|
5
|
+
The fixture implementation and its executable tests conform to both declared criteria.
|
|
6
|
+
|
|
7
|
+
## Spec conformance
|
|
8
|
+
| Criterion | Verdict | Evidence |
|
|
9
|
+
|-----------|---------|----------|
|
|
10
|
+
| AC-demo-gate-1 | met | tests/demo.test.js — accepts a valid value |
|
|
11
|
+
| AC-demo-gate-2 | met | tests/demo.test.js — rejects an invalid value |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Validation report — Demo gate
|
|
2
|
+
Spec revision: sha256:264708a769ad0d07d715420d82278461c5f1b831dd365eaaf6be051db18d47e8
|
|
3
|
+
|
|
4
|
+
The specification is ready for implementation: both acceptance criteria have concrete scenarios.
|
|
5
|
+
|
|
6
|
+
## Criterion scenarios
|
|
7
|
+
| Criterion | Scenario |
|
|
8
|
+
|-----------|----------|
|
|
9
|
+
| AC-demo-gate-1 | Valid input is accepted |
|
|
10
|
+
| AC-demo-gate-2 | Invalid input is rejected |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
function accepts(value) {
|
|
7
|
+
return value === 'valid';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
test('accepts a valid value', () => {
|
|
11
|
+
assert.equal(accepts('valid'), true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('rejects an invalid value', () => {
|
|
15
|
+
assert.equal(accepts('invalid'), false);
|
|
16
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"totalFiles":
|
|
2
|
+
"generatedAt": "2026-09-02T07:49:08.074Z",
|
|
3
|
+
"totalFiles": 166,
|
|
4
4
|
"files": {
|
|
5
5
|
".claude/agents/doc-validator.md": "e57950758cd4764b2cb54b3f1905567e9dbc1ed20ac6ecd30998614894689f4f",
|
|
6
6
|
".claude/agents/harvest-coordinator.md": "71e524e8379df46bbf457dbc1d4480634e05c99b7d3a4f59a356b8d71a0a4166",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
".claude/agents/replicate-coordinator.md": "bbfe18a7642954f357db08e849349fee02681e2f0ffa036539c45d98dbaba960",
|
|
9
9
|
".claude/commands/deploy.md": "27f5424ee217163c55e5e6b9322a1f0858159407bd24ebd6d6588d6c22a5f890",
|
|
10
10
|
".claude/commands/docs.md": "be03592cb3f1d61b60dc96d3e26d71281ad4eb2d4626012c080ce609a32c96e4",
|
|
11
|
-
".claude/commands/feature.md": "
|
|
11
|
+
".claude/commands/feature.md": "6908fa6c8ba770f829378a021d5f7e9a9bc79a15bdd67b278d8c6a765968b291",
|
|
12
12
|
".claude/commands/go.md": "e7337012dccaab09e0cf27a696ebff14bea4626fb5167538af7da2f5cc7b597a",
|
|
13
13
|
".claude/commands/harvest.md": "ed6355c102af0f2c62bf6714fce63157e0cb1c1ee85bc32c96f137b8b0c0e1ba",
|
|
14
14
|
".claude/commands/myinsights.md": "f31c5e133b5027197359e4179cbba1ab535f990634a69ea7c5c73bd2bc329d68",
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
".claude/hooks/check-metric-source.cjs": "315deda6354f91737165786bdb22b799517bd80530e5b4584593c4f4df84c56e",
|
|
35
35
|
".claude/hooks/check-model-cost.cjs": "425bda547661e0b31f313d6bb371db4d303cc83a43189fb4dc981e1fb43fac06",
|
|
36
36
|
".claude/hooks/check-ports.cjs": "2b458cf78367ec7752174f08aa40109b3b6217e405f99a46e18e75e4b69bc87e",
|
|
37
|
+
".claude/hooks/check-review-contract.cjs": "44e32ea31a4a71ceb6eccb0956006519de270eab787a79ab4884978bc04385ab",
|
|
37
38
|
".claude/hooks/check-source-version.cjs": "b9dc2eee7403119481ea2c49a94581613f2c134685448736c2734948a638a010",
|
|
38
39
|
".claude/hooks/check-swarm-receipts.cjs": "9fa83d78b085d8eea28958b4722999ba44a7bd47c98fe5a96127fc5b5230b40c",
|
|
39
40
|
".claude/hooks/check-webhook-contract.cjs": "dfa27bd24cee8868fd60be5d24749c206f303fe4afcc1e72024d526324562a39",
|
|
40
41
|
".claude/hooks/session-insights.cjs": "31fbe90d81b01dfb56557ed5d68dbde2211a14d5469c5d3761f45fb63c07c3a7",
|
|
41
42
|
".claude/hooks/state-update.cjs": "47146a76b768ac2273abe8f2f017856a0e28b45afaf90048148da417dd19604e",
|
|
42
|
-
".claude/hooks/statusline.cjs": "
|
|
43
|
+
".claude/hooks/statusline.cjs": "50c08decec3e80ed325f50d7b062fbe0add47e8594002fcc165248492242ab56",
|
|
43
44
|
".claude/hooks/write-insight.cjs": "743f2d85b037cf522dfaf4775947b0cd331e52f2f304e88360c160e0e8e9a589",
|
|
44
45
|
".claude/rules/cost-of-detection-ladder.md": "f15309ee8f1494478ff7678380dbe8fc8eecd154be36d57c9090cd17126a07da",
|
|
45
46
|
".claude/rules/docker-ports.md": "59eb430f29dd762e8b76ad6d1177fe8b5b250de007856c743387cb88ee157942",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
".claude/rules/insights-capture.md": "d529e6be79178636ca362d83366b1dd7a843371e8ba4f2b09d65c009fab52dc4",
|
|
52
53
|
".claude/rules/long-running-job.md": "e6bd0efff89338998d34440a94dc9288cf7bcf790b27bcaa51b83d43f57942ed",
|
|
53
54
|
".claude/rules/model-call-cost.md": "26c3b20ac3ed7f9cf6deb5e94302ade87f9e3e51fffca7ac2377ff31bc144f34",
|
|
54
|
-
".claude/rules/replicate-pipeline.md": "
|
|
55
|
+
".claude/rules/replicate-pipeline.md": "13bf073273820addeb87167402495b2f683cefac70a9f6cb07b6e054b1851e0f",
|
|
55
56
|
".claude/rules/skill-interface-protocol.md": "c3f624d0a8a4c7e0f5744e529eb4486cc0589fdb732b9799bccc75091a6d63ca",
|
|
56
57
|
".claude/rules/swarm-file-evidence.md": "c71a216dbc3fd633c30949823ceac354f8439b96279a1a8dd606afb1b3ef5c45",
|
|
57
58
|
".claude/settings.json": "18db09827dce410667b93e2b1a41ef8e46d4431114733e4304a5fc2192b2fc66",
|
|
@@ -145,10 +146,11 @@
|
|
|
145
146
|
".claude/skills/problem-solver-enhanced/SKILL.md": "031378aa2e10c98928e35de07d454135e73550cd0c49f0cd886d53e4208d6b9f",
|
|
146
147
|
".claude/skills/requirements-validator/assets/templates/bdd-feature-template.feature": "5b1e47f10447ce574d0eb3917b69e2613b6e3f9c48b30ff5f4cfb57758c99a1d",
|
|
147
148
|
".claude/skills/requirements-validator/references/bdd-patterns.md": "04611a8c52c81f3a6d4fc2f9661ebc56159a125b6fb44a4d18b432e2b82de6e3",
|
|
149
|
+
".claude/skills/requirements-validator/references/feature-report-contracts.md": "df263cbce17fb5d9dec6514561c66f6283b3f19e2fc85056f0531bac615be0f1",
|
|
148
150
|
".claude/skills/requirements-validator/references/invest-criteria.md": "ac6976adf776b9b60c784e1a0f6a0d96dd3332b16f4c12b72d3e59f9325bb626",
|
|
149
|
-
".claude/skills/requirements-validator/references/scoring-system.md": "
|
|
151
|
+
".claude/skills/requirements-validator/references/scoring-system.md": "72104b74f07e0610f67f00b246b5225a530203bff225bacf7bf384bc6fd21cdf",
|
|
150
152
|
".claude/skills/requirements-validator/references/smart-criteria.md": "619a33fdb6bc336872fdf9f1fc91924f8ccd3c2ebef023912124756692b45aef",
|
|
151
|
-
".claude/skills/requirements-validator/SKILL.md": "
|
|
153
|
+
".claude/skills/requirements-validator/SKILL.md": "b4b394adf08feee552f39c83331398f119e0d4abf21e683fe4706da438b4a426",
|
|
152
154
|
".claude/skills/reverse-engineering-unicorn/examples/noom-cjm-example.md": "01d33586e26bf093a57a7a1082bf3bdba3af801bb8348a685c0418f76987a0b0",
|
|
153
155
|
".claude/skills/reverse-engineering-unicorn/examples/noom-module1-example.md": "7009cf2e2624a1254913ad2e1ff554e7ff675358b0b99bc0632f2e12132663a3",
|
|
154
156
|
".claude/skills/reverse-engineering-unicorn/modules/01-intelligence.md": "0770bd63a6a1eeb34e873cb101e2ba0cf88d507e9bc03242046eaa837b9f587c",
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = 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 CHECK = path.join(PKG, 'templates', '.claude', 'hooks', 'check-review-contract.cjs');
|
|
13
|
+
const SLUG = 'traceability-floor';
|
|
14
|
+
const SPEC = [
|
|
15
|
+
'# Specification',
|
|
16
|
+
'',
|
|
17
|
+
'### AC-trace-1 First criterion',
|
|
18
|
+
'',
|
|
19
|
+
'The first contour is observable.',
|
|
20
|
+
'',
|
|
21
|
+
'```markdown',
|
|
22
|
+
'### AC-ignored-9 Example only',
|
|
23
|
+
'```',
|
|
24
|
+
'',
|
|
25
|
+
'### AC-trace-2 Second criterion',
|
|
26
|
+
'',
|
|
27
|
+
'The second contour is observable.',
|
|
28
|
+
'',
|
|
29
|
+
].join('\n');
|
|
30
|
+
|
|
31
|
+
function digest(body) {
|
|
32
|
+
return crypto.createHash('sha256').update(body).digest('hex');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function report(options) {
|
|
36
|
+
const o = options || {};
|
|
37
|
+
const rows = o.rows || [
|
|
38
|
+
'| AC-trace-1 | met | tests/trace.test.js — proves the first contour |',
|
|
39
|
+
'| AC-trace-2 | unverifiable | |',
|
|
40
|
+
];
|
|
41
|
+
return [
|
|
42
|
+
'# Review report',
|
|
43
|
+
...(o.family === null ? [] : ['Reviewer family: ' + (o.family || 'claude')]),
|
|
44
|
+
'Spec revision: sha256:' + (o.revision || digest(SPEC)),
|
|
45
|
+
'',
|
|
46
|
+
...(o.section === false ? [] : [
|
|
47
|
+
'## Spec conformance',
|
|
48
|
+
...(o.table === false ? [] : [
|
|
49
|
+
'| Criterion | Verdict | Evidence |',
|
|
50
|
+
'|-----------|---------|----------|',
|
|
51
|
+
...rows,
|
|
52
|
+
]),
|
|
53
|
+
]),
|
|
54
|
+
'',
|
|
55
|
+
].join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function check(options) {
|
|
59
|
+
const o = options || {};
|
|
60
|
+
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-review-')));
|
|
61
|
+
const feature = path.join(root, 'docs', 'features', SLUG);
|
|
62
|
+
fs.mkdirSync(feature, { recursive: true });
|
|
63
|
+
try {
|
|
64
|
+
const specPath = path.join(feature, '01_specification.md');
|
|
65
|
+
if (o.symlinkSpec) {
|
|
66
|
+
const target = path.join(root, 'spec-target.md');
|
|
67
|
+
fs.writeFileSync(target, SPEC);
|
|
68
|
+
fs.symlinkSync(target, specPath);
|
|
69
|
+
} else {
|
|
70
|
+
fs.writeFileSync(specPath, SPEC);
|
|
71
|
+
}
|
|
72
|
+
if (!o.missingReport) {
|
|
73
|
+
const reportPath = path.join(feature, 'review-report.md');
|
|
74
|
+
fs.writeFileSync(reportPath, o.body === undefined ? report() : o.body);
|
|
75
|
+
if (o.unreadableReport) fs.chmodSync(reportPath, 0o000);
|
|
76
|
+
}
|
|
77
|
+
const result = spawnSync(process.execPath, [CHECK, root, SLUG], { encoding: 'utf8' });
|
|
78
|
+
return { code: result.status, out: (result.stdout || '') + (result.stderr || '') };
|
|
79
|
+
} finally {
|
|
80
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function namedGap(options, pattern) {
|
|
85
|
+
const result = check(options);
|
|
86
|
+
assert.equal(result.code, 1, result.out);
|
|
87
|
+
assert.match(result.out, pattern, result.out);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
test('honest input exits 0 and prints the measured counts', () => {
|
|
91
|
+
const result = check();
|
|
92
|
+
assert.equal(result.code, 0, result.out);
|
|
93
|
+
assert.match(result.out,
|
|
94
|
+
/PASS review-contract feature=traceability-floor AC-ids=2 rows=2/,
|
|
95
|
+
result.out);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('missing reviewer family is a named gap', () => {
|
|
99
|
+
namedGap({ body: report({ family: null }) }, /GAP Reviewer family line missing/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('reviewer family outside the vocabulary is a named gap', () => {
|
|
103
|
+
namedGap({ body: report({ family: 'gpt' }) }, /GAP Reviewer family invalid value=gpt/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('revision mismatch names both 12-character prefixes', () => {
|
|
107
|
+
const wrong = '0'.repeat(64);
|
|
108
|
+
const actual = digest(SPEC).slice(0, 12);
|
|
109
|
+
namedGap(
|
|
110
|
+
{ body: report({ revision: wrong }) },
|
|
111
|
+
new RegExp('GAP Spec revision mismatch report=000000000000 specification=' + actual),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('missing Spec conformance section is a named gap', () => {
|
|
116
|
+
namedGap({ body: report({ section: false }) }, /GAP Spec conformance section missing/);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('missing Spec conformance table is a named gap', () => {
|
|
120
|
+
namedGap({ body: report({ table: false }) }, /GAP Spec conformance table missing or malformed/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('an AC id with no row is named', () => {
|
|
124
|
+
namedGap({ body: report({ rows: [
|
|
125
|
+
'| AC-trace-1 | met | tests/trace.test.js — proves the first contour |',
|
|
126
|
+
] }) }, /GAP AC-trace-2 has no Spec conformance row/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('a row id absent from the specification is named', () => {
|
|
130
|
+
namedGap({ body: report({ rows: [
|
|
131
|
+
'| AC-trace-1 | met | tests/trace.test.js — proves the first contour |',
|
|
132
|
+
'| AC-trace-2 | unverifiable | |',
|
|
133
|
+
'| AC-invented-3 | met | tests/trace.test.js — invented contour |',
|
|
134
|
+
] }) }, /GAP AC-invented-3 row id is not in the specification/);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('a duplicate row is named', () => {
|
|
138
|
+
namedGap({ body: report({ rows: [
|
|
139
|
+
'| AC-trace-1 | met | tests/trace.test.js — first proof |',
|
|
140
|
+
'| AC-trace-1 | met | tests/trace.test.js — duplicate proof |',
|
|
141
|
+
'| AC-trace-2 | unverifiable | |',
|
|
142
|
+
] }) }, /GAP AC-trace-1 duplicate Spec conformance row/);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('a verdict outside the vocabulary is named', () => {
|
|
146
|
+
namedGap({ body: report({ rows: [
|
|
147
|
+
'| AC-trace-1 | passed | tests/trace.test.js — first proof |',
|
|
148
|
+
'| AC-trace-2 | unverifiable | |',
|
|
149
|
+
] }) }, /GAP AC-trace-1 verdict invalid value=passed/);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('empty evidence on met and not met rows is named', () => {
|
|
153
|
+
for (const verdict of ['met', 'not met']) {
|
|
154
|
+
namedGap({ body: report({ rows: [
|
|
155
|
+
'| AC-trace-1 | ' + verdict + ' | |',
|
|
156
|
+
'| AC-trace-2 | unverifiable | |',
|
|
157
|
+
] }) }, new RegExp('GAP AC-trace-1 evidence empty for verdict=' + verdict));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('unreadable or missing report → exit 2', () => {
|
|
162
|
+
for (const options of [{ missingReport: true }, { unreadableReport: true }]) {
|
|
163
|
+
const result = check(options);
|
|
164
|
+
assert.equal(result.code, 2, result.out);
|
|
165
|
+
assert.match(result.out, /NOT-ESTABLISHED review contract: review report/, result.out);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('symlinked spec → exit 2', () => {
|
|
170
|
+
const result = check({ symlinkSpec: true });
|
|
171
|
+
assert.equal(result.code, 2, result.out);
|
|
172
|
+
assert.match(result.out, /NOT-ESTABLISHED review contract: specification is a symlink/, result.out);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('the family line is disclosure only: every vocabulary value passes, gpt fails', () => {
|
|
176
|
+
for (const family of ['claude', 'codex', 'human', 'unknown']) {
|
|
177
|
+
const result = check({ body: report({ family }) });
|
|
178
|
+
assert.equal(result.code, 0, family + ': ' + result.out);
|
|
179
|
+
}
|
|
180
|
+
namedGap({ body: report({ family: 'gpt' }) }, /GAP Reviewer family invalid value=gpt/);
|
|
181
|
+
});
|
|
@@ -41,13 +41,28 @@ const RULES = ['honest-configuration', 'swarm-file-evidence'];
|
|
|
41
41
|
* {files:34, bytes:356321, estimatedTokens:95940.35}, a measured delta of 3988.50 below the
|
|
42
42
|
* unchanged 4037.75 trigger.
|
|
43
43
|
* Reproducer: measureCorpus() over templates/.claude/{rules,commands}/*.md plus every SKILL.md.
|
|
44
|
-
* `ESTIMATED_TRIGGER` was not touched.
|
|
44
|
+
* `ESTIMATED_TRIGGER` was not touched. *
|
|
45
|
+
* Re-pinned 2026-09-02 for feature `traceability-floor-and-contract` (three /feature gates + the
|
|
46
|
+
* Traceability floor). Preimage = the tree at its parent commit d126adec^ (HEAD~1 of the feature),
|
|
47
|
+
* measured {files:34, bytes:372385, estimatedTokens:99956.35}. Against the PREVIOUS pin that state
|
|
48
|
+
* already sat at a delta of 4016.00 — the four night features between the two pins (check-ports
|
|
49
|
+
* receipts, design-analysis-hypothesis, quote-provenance, heal-bhr-drift) each fitted the trigger
|
|
50
|
+
* and none re-pinned, so 21.75 tokens of headroom remained and any new gate line would have tripped
|
|
51
|
+
* P5: the "ceiling nobody chose" this comment warns about, observed live. The feature's OWN delta
|
|
52
|
+
* against its true preimage is 715.75 tokens (feature.md gate mandates + validator report contract +
|
|
53
|
+
* the hook's inventory line), after its byte-level formats were moved to
|
|
54
|
+
* requirements-validator/references/feature-report-contracts.md. `ESTIMATED_TRIGGER` unchanged.
|
|
45
55
|
*/
|
|
46
|
-
const FRESH_BEFORE = { files: 34, bytes:
|
|
56
|
+
const FRESH_BEFORE = { files: 34, bytes: 372385, estimatedTokens: 99956.35 };
|
|
47
57
|
const ESTIMATED_TRIGGER = 4037.75;
|
|
48
58
|
// Measured 2026-09-01 before corpus-budget-compression. Modules and references are outside
|
|
49
59
|
// measureCorpus(); a 0-byte allowance makes any relocation growth visible instead of budget-free.
|
|
50
|
-
|
|
60
|
+
// Re-measured 2026-09-02 (features/traceability-floor-and-contract): +1 file, +4265 bytes —
|
|
61
|
+
// requirements-validator/references/feature-report-contracts.md (the byte-level formats of the
|
|
62
|
+
// three /feature report gates; invocation-time material, the gates are the machine half) and the
|
|
63
|
+
// Traceability floor row + FR-009 worked case in references/scoring-system.md. Not a relocation:
|
|
64
|
+
// the always-loaded corpus measured by P5 GREW in the same change (feature.md gates), it did not shrink.
|
|
65
|
+
const UNMEASURED_SKILL_SURFACES_BEFORE = { files: 60, bytes: 642574 };
|
|
51
66
|
const UNMEASURED_SKILL_SURFACES_GROWTH_ALLOWANCE = 0;
|
|
52
67
|
const SEAMS = [
|
|
53
68
|
['commands/feature.md', 'both'],
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// scripts/sync-templates.js copies <found root>/.claude/{skills,commands,agents,rules,hooks} over
|
|
4
4
|
// templates/.claude/ — the directory that becomes the npm tarball. It finds its source by walking up
|
|
5
|
-
// to five parents
|
|
6
|
-
// MONOREPO, whose .claude/skills holds a whole
|
|
5
|
+
// to five parents, stopping at the package repository boundary, and taking the FIRST one containing
|
|
6
|
+
// .claude/skills, which inside a monorepo is the MONOREPO, whose .claude/skills holds a whole
|
|
7
|
+
// different toolkit.
|
|
7
8
|
//
|
|
8
9
|
// Nothing had fired only because package.json's prepublishOnly points at the publish gate, not here.
|
|
9
10
|
// "Safe because dead" is not a safety property: a dead script can be revived by anyone who does not
|
|
@@ -61,7 +62,11 @@ const DECLARATION = 'p-replicator-sync-source: v1';
|
|
|
61
62
|
* with its own templates/.claude tree. `marker` decides whether the root claims to be the source. */
|
|
62
63
|
function fixture(opts) {
|
|
63
64
|
const o = opts || {};
|
|
64
|
-
const
|
|
65
|
+
const parent = o.parent || os.tmpdir();
|
|
66
|
+
const root = fs.realpathSync(fs.mkdtempSync(path.join(parent, 'p-rep-sync-')));
|
|
67
|
+
const boundary = o.boundary === undefined ? 'dir' : o.boundary;
|
|
68
|
+
if (boundary === 'dir') fs.mkdirSync(path.join(root, '.git'));
|
|
69
|
+
if (boundary === 'file') fs.writeFileSync(path.join(root, '.git'), 'gitdir: /nonexistent\n');
|
|
65
70
|
if (o.rootHasClaude !== false) {
|
|
66
71
|
fs.mkdirSync(path.join(root, '.claude', 'skills', 'intruder'), { recursive: true });
|
|
67
72
|
fs.writeFileSync(path.join(root, '.claude', 'skills', 'intruder', 'SKILL.md'),
|
|
@@ -208,6 +213,44 @@ describe.skip = describe.skip || (() => {});
|
|
|
208
213
|
} finally { cleanup(f.root); }
|
|
209
214
|
});
|
|
210
215
|
|
|
216
|
+
test('P10 — a .claude/skills ABOVE the repository boundary is not a candidate root', () => {
|
|
217
|
+
const grandparent = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-intruder-')));
|
|
218
|
+
fs.mkdirSync(path.join(grandparent, '.claude', 'skills', 'intruder'), { recursive: true });
|
|
219
|
+
fs.writeFileSync(path.join(grandparent, '.claude', 'skills', 'intruder', 'SKILL.md'),
|
|
220
|
+
'# outside the fixture repository\n');
|
|
221
|
+
try {
|
|
222
|
+
const bounded = fixture({ parent: grandparent, rootHasClaude: false, boundary: 'dir' });
|
|
223
|
+
try {
|
|
224
|
+
const r = runScript(bounded.pkg);
|
|
225
|
+
assert.equal(r.code, 0, 'the repository boundary must stop the walk: ' + r.out);
|
|
226
|
+
assert.match(r.out, /Not in repo context — skipping sync/);
|
|
227
|
+
} finally { cleanup(bounded.root); }
|
|
228
|
+
|
|
229
|
+
const unbounded = fixture({ parent: grandparent, rootHasClaude: false, boundary: false });
|
|
230
|
+
try {
|
|
231
|
+
const r = runScript(unbounded.pkg);
|
|
232
|
+
assert.equal(r.code, 1, 'without a boundary the outside candidate must still be found');
|
|
233
|
+
assert.match(r.out, /REFUSING to sync/,
|
|
234
|
+
'the contrast proves the boundary, rather than the host /tmp state, caused the skip');
|
|
235
|
+
} finally { cleanup(unbounded.root); }
|
|
236
|
+
} finally { cleanup(grandparent); }
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('P11 — a .git FILE (worktree) is a boundary too', () => {
|
|
240
|
+
const grandparent = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'p-rep-intruder-')));
|
|
241
|
+
fs.mkdirSync(path.join(grandparent, '.claude', 'skills', 'intruder'), { recursive: true });
|
|
242
|
+
fs.writeFileSync(path.join(grandparent, '.claude', 'skills', 'intruder', 'SKILL.md'),
|
|
243
|
+
'# outside the fixture worktree\n');
|
|
244
|
+
try {
|
|
245
|
+
const f = fixture({ parent: grandparent, rootHasClaude: false, boundary: 'file' });
|
|
246
|
+
try {
|
|
247
|
+
const r = runScript(f.pkg);
|
|
248
|
+
assert.equal(r.code, 0, 'a worktree .git file must stop the walk: ' + r.out);
|
|
249
|
+
assert.match(r.out, /Not in repo context — skipping sync/);
|
|
250
|
+
} finally { cleanup(f.root); }
|
|
251
|
+
} finally { cleanup(grandparent); }
|
|
252
|
+
});
|
|
253
|
+
|
|
211
254
|
test('P5 — no helper tells a reader to run it', () => {
|
|
212
255
|
// The helper printed "Run prepublishOnly first: node scripts/sync-templates.js" when templates/
|
|
213
256
|
// was missing: a live invitation to the hazardous command, in the one situation where a reader
|