peaks-loop 4.0.48 → 4.0.50
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/CHANGELOG.md +44 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/dist/cli/commands/audit-commands.js +1 -0
- package/dist/cli/commands/baseline-commands.js +163 -25
- package/dist/cli/commands/compact-command.js +1 -3
- package/dist/cli/commands/core/skill-command.js +53 -4
- package/dist/cli/commands/core/standards-command.d.ts +24 -0
- package/dist/cli/commands/core/standards-command.js +74 -0
- package/dist/cli/commands/feedback-commands.d.ts +11 -7
- package/dist/cli/commands/feedback-commands.js +49 -17
- package/dist/cli/commands/final-review-commands.js +12 -0
- package/dist/cli/commands/hooks-commands.js +55 -38
- package/dist/cli/commands/loop-eval-commands.js +22 -6
- package/dist/cli/commands/share-commands.js +37 -11
- package/dist/cli/commands/slice-integrate-commands.js +17 -0
- package/dist/cli/commands/web-commands.js +8 -1
- package/dist/cli/commands/workflow-lifecycle-commands.d.ts +6 -0
- package/dist/cli/commands/workflow-lifecycle-commands.js +64 -3
- package/dist/services/adapter/adapter.d.ts +30 -0
- package/dist/services/adapter/auto-adapter.d.ts +13 -0
- package/dist/services/adapter/claude-adapter.js +12 -0
- package/dist/services/adapter/codex-adapter.d.ts +12 -0
- package/dist/services/adapter/codex-adapter.js +12 -0
- package/dist/services/adapter/copilot-adapter.d.ts +12 -0
- package/dist/services/adapter/copilot-adapter.js +12 -0
- package/dist/services/artifacts/artifact-prerequisites.js +10 -0
- package/dist/services/artifacts/request-artifact-service.js +59 -38
- package/dist/services/audit/backing-detector.d.ts +25 -7
- package/dist/services/audit/backing-detector.js +33 -17
- package/dist/services/audit/enforcer-liveness.d.ts +12 -0
- package/dist/services/audit/enforcer-liveness.js +100 -0
- package/dist/services/audit/enforcers/active-skill-resolver.js +14 -1
- package/dist/services/audit/enforcers/lint-catalog-governance.d.ts +23 -11
- package/dist/services/audit/enforcers/lint-catalog-governance.js +10 -14
- package/dist/services/audit/enforcers/lint-rd-handoff-coverage.d.ts +5 -15
- package/dist/services/audit/enforcers/lint-rd-handoff-coverage.js +94 -25
- package/dist/services/audit/enforcers/lint-style.d.ts +9 -1
- package/dist/services/audit/enforcers/lint-style.js +38 -2
- package/dist/services/audit/prose-ratio-calculator.d.ts +28 -17
- package/dist/services/audit/prose-ratio-calculator.js +25 -18
- package/dist/services/audit/red-line-catalog-p2-a.js +1 -1
- package/dist/services/audit/red-lines-service.js +51 -7
- package/dist/services/capability-audit-service/independent-checker.d.ts +15 -0
- package/dist/services/capability-audit-service/independent-checker.js +140 -0
- package/dist/services/capability-audit-service/index.d.ts +3 -1
- package/dist/services/capability-audit-service/index.js +1 -0
- package/dist/services/capability-audit-service/runner.d.ts +17 -13
- package/dist/services/capability-audit-service/runner.js +76 -15
- package/dist/services/capability-audit-service/types.d.ts +48 -0
- package/dist/services/capability-guard-runner/contracts/J01.js +21 -22
- package/dist/services/capability-guard-runner/contracts/J02.d.ts +1 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +114 -28
- package/dist/services/capability-guard-runner/contracts/J03.d.ts +13 -0
- package/dist/services/capability-guard-runner/contracts/J03.js +72 -21
- package/dist/services/capability-guard-runner/contracts/J04.d.ts +6 -0
- package/dist/services/capability-guard-runner/contracts/J04.js +65 -32
- package/dist/services/capability-guard-runner/contracts/J05.js +118 -16
- package/dist/services/capability-guard-runner/contracts/J06.d.ts +14 -0
- package/dist/services/capability-guard-runner/contracts/J06.js +57 -39
- package/dist/services/capability-guard-runner/contracts/J07.d.ts +9 -0
- package/dist/services/capability-guard-runner/contracts/J07.js +76 -47
- package/dist/services/capability-guard-runner/contracts/J08.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J08.js +66 -39
- package/dist/services/capability-guard-runner/contracts/J09.d.ts +13 -0
- package/dist/services/capability-guard-runner/contracts/J09.js +95 -39
- package/dist/services/capability-guard-runner/contracts/J10.d.ts +12 -0
- package/dist/services/capability-guard-runner/contracts/J10.js +69 -35
- package/dist/services/capability-guard-runner/contracts/J11.d.ts +8 -0
- package/dist/services/capability-guard-runner/contracts/J11.js +73 -33
- package/dist/services/capability-guard-runner/contracts/J12.d.ts +12 -0
- package/dist/services/capability-guard-runner/contracts/J12.js +66 -30
- package/dist/services/capability-guard-runner/contracts/J13.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J13.js +62 -40
- package/dist/services/capability-guard-runner/contracts/J14.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J14.js +60 -31
- package/dist/services/capability-guard-runner/contracts/J15.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J15.js +70 -35
- package/dist/services/capability-guard-runner/contracts/_shared.d.ts +24 -0
- package/dist/services/capability-guard-runner/contracts/_shared.js +67 -0
- package/dist/services/capability-guard-runner/registry.d.ts +5 -0
- package/dist/services/capability-guard-runner/registry.js +140 -0
- package/dist/services/capability-guard-runner/runner.d.ts +26 -0
- package/dist/services/capability-guard-runner/runner.js +63 -6
- package/dist/services/code/auto-compact-lifecycle.d.ts +75 -0
- package/dist/services/code/auto-compact-lifecycle.js +65 -16
- package/dist/services/code/auto-compact-modes.d.ts +13 -2
- package/dist/services/code/auto-compact-modes.js +20 -4
- package/dist/services/code/auto-compact-orchestrator.js +119 -19
- package/dist/services/code/compact-event-settle.d.ts +20 -8
- package/dist/services/code/compact-event-settle.js +21 -0
- package/dist/services/code/post-compact-detector.js +20 -11
- package/dist/services/code/step-08-gate.js +21 -6
- package/dist/services/compact-statusline/compact-statusline-service.js +56 -22
- package/dist/services/config/config-safety.js +11 -9
- package/dist/services/context/auto-compact-types.d.ts +20 -2
- package/dist/services/feedback/feedback-promotion-service.d.ts +137 -14
- package/dist/services/feedback/feedback-promotion-service.js +341 -20
- package/dist/services/feedback/promotion-artifact-evidence.d.ts +69 -0
- package/dist/services/feedback/promotion-artifact-evidence.js +332 -0
- package/dist/services/final-review/pre-post-diff.js +10 -2
- package/dist/services/job/job-progress-store.js +18 -3
- package/dist/services/observability/jsonl-store.d.ts +19 -0
- package/dist/services/observability/jsonl-store.js +27 -2
- package/dist/services/observability/observability-service.d.ts +11 -4
- package/dist/services/observability/observability-service.js +16 -3
- package/dist/services/prd/handoff-service.js +43 -0
- package/dist/services/qa/qa-business-review-state.js +19 -5
- package/dist/services/sc/sc-service.d.ts +8 -0
- package/dist/services/sc/sc-service.js +8 -1
- package/dist/services/scan/api-diff-types.js +20 -2
- package/dist/services/security/safe-settings-path.js +19 -1
- package/dist/services/session/getSessionDir.d.ts +33 -0
- package/dist/services/session/getSessionDir.js +60 -0
- package/dist/services/skill/skill-search-service.d.ts +3 -3
- package/dist/services/slice/slice-review-state.js +19 -4
- package/dist/services/standards/loop-engineering-lint.d.ts +1 -1
- package/dist/services/standards/loop-engineering-lint.js +6 -0
- package/dist/services/web/daemon-registry.js +27 -2
- package/dist/services/workflow/pipeline-verify-gate-support.js +10 -11
- package/dist/services/workflow/pipeline-verify-service.d.ts +1 -1
- package/dist/services/workflow/pipeline-verify-service.js +23 -10
- package/dist/services/workflow/pipeline-verify-types.d.ts +5 -3
- package/dist/services/workspace/claude-settings-template.d.ts +53 -37
- package/dist/services/workspace/claude-settings-template.js +105 -83
- package/dist/services/workspace/generated-artifacts-stamp.d.ts +119 -0
- package/dist/services/workspace/generated-artifacts-stamp.js +167 -0
- package/dist/services/workspace/workspace-claude-settings-materializer.d.ts +8 -0
- package/dist/services/workspace/workspace-claude-settings-materializer.js +38 -3
- package/dist/services/workspace/workspace-service.js +11 -1
- package/dist/shared/fs-utils.d.ts +26 -0
- package/dist/shared/fs-utils.js +35 -0
- package/dist/shared/runtime-root.d.ts +73 -0
- package/dist/shared/runtime-root.js +77 -0
- package/package.json +9 -7
- package/scripts/copy-templates.mjs +0 -12
- package/scripts/install-skills.mjs +154 -53
- package/skills/bee/peaks-qa/SKILL.md +0 -1
- package/skills/bee/peaks-rd/SKILL.md +0 -1
- package/skills/peaks-code/SKILL.md +12 -10
- package/skills/peaks-code/references/periodic-checkpoint.md +2 -2
- package/skills/peaks-code/references/runbook.md +3 -0
- package/skills/peaks-code/references/session-overload-signal-index.md +4 -2
- package/skills/peaks-code/references/startup-sequence.md +2 -2
- package/skills/peaks-code/references/step-0-8-gate.md +1 -1
- package/skills/peaks-code/references/sub-agent-dispatch.md +19 -19
- package/dist/cli/commands/context-builder-commands.d.ts +0 -11
- package/dist/cli/commands/context-builder-commands.js +0 -85
- package/dist/services/hooks/write-gate.js +0 -111
- package/skills/bee/peaks-prd/references/command-migration.md +0 -3
- package/skills/bee/peaks-qa/references/command-migration.md +0 -3
- package/skills/bee/peaks-rd/references/command-migration.md +0 -3
- package/skills/bee/peaks-sc/references/command-migration.md +0 -3
- package/skills/bee/peaks-txt/references/command-migration.md +0 -3
- package/skills/bee/peaks-ui/references/command-migration.md +0 -3
- package/skills/peaks-code/references/command-migration.md +0 -3
|
@@ -1,24 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
5
|
+
const DETECTOR = ['scripts', 'lint', 'silent-warning-detector.mjs'];
|
|
6
|
+
/**
|
|
7
|
+
* Violation ceilings measured on the frozen tree when this contract was
|
|
8
|
+
* written (2026-09-15, 767 files scanned). They are a RATCHET, not a target:
|
|
9
|
+
* the invariant is "no silent-catch or fake-green pattern is REINTRODUCED", so
|
|
10
|
+
* the check is that the count does not grow. Fixing violations lowers the
|
|
11
|
+
* ceiling in the same slice that fixes them.
|
|
12
|
+
*/
|
|
13
|
+
const CEILING = {
|
|
14
|
+
'catch-return-null': 41,
|
|
15
|
+
'empty-catch': 59
|
|
16
|
+
};
|
|
17
|
+
function parseCounts(stdout) {
|
|
18
|
+
const line = /\[silent-warning-detector\] summary: (.+)$/m.exec(stdout);
|
|
19
|
+
if (line === null)
|
|
20
|
+
return null;
|
|
21
|
+
const counts = {};
|
|
22
|
+
for (const pair of line[1].split(',')) {
|
|
23
|
+
const m = /^\s*([a-z-]+)=(\d+)\s*$/.exec(pair);
|
|
24
|
+
if (m !== null)
|
|
25
|
+
counts[m[1]] = Number(m[2]);
|
|
26
|
+
}
|
|
27
|
+
return counts;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Behavioural probe of the "no silent-catch / fake-green reintroduced"
|
|
31
|
+
* invariant.
|
|
32
|
+
*
|
|
33
|
+
* The previous version read `final-review-types.ts` and asserted it contained
|
|
34
|
+
* four dimension strings — a file whose own name is `final-review`, so the
|
|
35
|
+
* check restated its filename.
|
|
36
|
+
*
|
|
37
|
+
* Here the repository's own AST guard (`scripts/lint/silent-warning-detector.mjs`)
|
|
38
|
+
* is executed and its per-rule violation counts are compared against the frozen
|
|
39
|
+
* ceilings. A newly swallowed `catch` in `src/**` raises a count and reddens the
|
|
40
|
+
* journey.
|
|
41
|
+
*/
|
|
10
42
|
export async function runJ03Contract(ctx) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
const row = requireBaselineRow(ctx);
|
|
44
|
+
const missing = missingSourceFiles(ctx, row);
|
|
45
|
+
const detectorPath = join(ctx.projectRoot, ...DETECTOR);
|
|
46
|
+
const detectorPresent = existsSync(detectorPath);
|
|
47
|
+
const run = detectorPresent
|
|
48
|
+
? spawnSync('node', [detectorPath], { cwd: ctx.projectRoot, encoding: 'utf8', windowsHide: true })
|
|
49
|
+
: null;
|
|
50
|
+
const stdout = run?.stdout ?? '';
|
|
51
|
+
const counts = parseCounts(stdout);
|
|
52
|
+
const scanned = /scanned (\d+) files/.exec(stdout);
|
|
53
|
+
const grew = [];
|
|
54
|
+
const missingRules = [];
|
|
55
|
+
if (counts !== null) {
|
|
56
|
+
for (const rule of Object.keys(CEILING)) {
|
|
57
|
+
if (counts[rule] === undefined)
|
|
58
|
+
missingRules.push(rule);
|
|
59
|
+
else if (counts[rule] > CEILING[rule])
|
|
60
|
+
grew.push(`${rule}: ${String(counts[rule])} > ${String(CEILING[rule])}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const result = combineProbes([
|
|
64
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
65
|
+
probe(detectorPresent, `the repository AST guard is present (${detectorPath})`),
|
|
66
|
+
probe(scanned !== null && Number(scanned[1]) > 0, `the AST guard scanned files (${scanned?.[1] ?? 'none'})`),
|
|
67
|
+
probe(counts !== null, `the AST guard reported per-rule counts (${counts === null ? 'unparseable' : JSON.stringify(counts)})`),
|
|
68
|
+
probe(missingRules.length === 0, `every ratcheted rule was reported (missing: ${missingRules.join(',') || 'none'})`),
|
|
69
|
+
probe(grew.length === 0, `no silent-catch rule count grew past its ceiling (${grew.join('; ') || 'none'})`)
|
|
70
|
+
]);
|
|
71
|
+
const artifact = row.sourceFiles[1] ?? 'src/services/final-review/final-review-service.ts';
|
|
72
|
+
if (result.ok)
|
|
73
|
+
return pass(ctx, artifact);
|
|
74
|
+
return fail(ctx, artifact, `silent-catch violation counts stay at or below ${JSON.stringify(CEILING)}`, result.detail, 'J03 invariant broken: a silent-catch or fake-green pattern was reintroduced under src/**');
|
|
24
75
|
}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe: drive `auditGoal()` with a complete and an incomplete
|
|
4
|
+
* payload and assert it accepts exactly one of them. Counting `dimension`
|
|
5
|
+
* occurrences in the source would have passed even if the six-element guard
|
|
6
|
+
* were replaced by a comment.
|
|
7
|
+
*/
|
|
2
8
|
export declare function runJ04Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,42 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { auditGoal, IncompleteAuditError } from '../../audit/audit-goal-service.js';
|
|
2
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
3
|
+
/** The six dimensions the frozen baseline names, in the baseline's own order. */
|
|
4
|
+
const SIX_DIMENSIONS = [
|
|
5
|
+
'correctness', 'completeness', 'scope', 'risks', 'alternatives', 'constraints'
|
|
6
|
+
];
|
|
7
|
+
function runnerReturning(output) {
|
|
8
|
+
return { call: async () => ({ output, tokens: { input: 1, output: 1 } }) };
|
|
9
|
+
}
|
|
10
|
+
function envelope(dimensions, severity = 'info') {
|
|
11
|
+
return JSON.stringify({
|
|
12
|
+
summary: 's',
|
|
13
|
+
audit: dimensions.map((dimension) => ({ dimension, finding: 'f', severity })),
|
|
14
|
+
proposedGoal: 'g',
|
|
15
|
+
successCriteria: ['c'],
|
|
16
|
+
roughEffort: 'small',
|
|
17
|
+
confidence: 'high',
|
|
18
|
+
rationale: 'r'
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Behavioural probe: drive `auditGoal()` with a complete and an incomplete
|
|
23
|
+
* payload and assert it accepts exactly one of them. Counting `dimension`
|
|
24
|
+
* occurrences in the source would have passed even if the six-element guard
|
|
25
|
+
* were replaced by a comment.
|
|
26
|
+
*/
|
|
5
27
|
export async function runJ04Contract(ctx) {
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
let
|
|
28
|
+
const row = requireBaselineRow(ctx);
|
|
29
|
+
const missing = missingSourceFiles(ctx, row);
|
|
30
|
+
let completeAccepted = false;
|
|
31
|
+
let completeCount = -1;
|
|
10
32
|
try {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
windowsHide: true
|
|
15
|
-
}).toString('utf8');
|
|
16
|
-
helpOk = stdout.length > 0 && !stdout.includes('unknown command');
|
|
33
|
+
const out = await auditGoal({ need: 'n' }, runnerReturning(envelope(SIX_DIMENSIONS)));
|
|
34
|
+
completeAccepted = true;
|
|
35
|
+
completeCount = out.audit.length;
|
|
17
36
|
}
|
|
18
37
|
catch {
|
|
19
|
-
|
|
38
|
+
completeAccepted = false;
|
|
20
39
|
}
|
|
21
|
-
|
|
22
|
-
let
|
|
40
|
+
let fiveRejected = false;
|
|
41
|
+
let rejectName = 'none';
|
|
23
42
|
try {
|
|
24
|
-
|
|
25
|
-
cwd: ctx.projectRoot,
|
|
26
|
-
env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` },
|
|
27
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
28
|
-
windowsHide: true
|
|
29
|
-
});
|
|
43
|
+
await auditGoal({ need: 'n' }, runnerReturning(envelope(SIX_DIMENSIONS.slice(0, 5))));
|
|
30
44
|
}
|
|
31
45
|
catch (e) {
|
|
32
|
-
|
|
46
|
+
fiveRejected = true;
|
|
47
|
+
rejectName = e.name;
|
|
48
|
+
}
|
|
49
|
+
// Any one dimension dropped must be refused — not just the last one.
|
|
50
|
+
let eachDropRejected = true;
|
|
51
|
+
const accepted = new Set();
|
|
52
|
+
for (const dropped of SIX_DIMENSIONS) {
|
|
53
|
+
const dims = SIX_DIMENSIONS.filter((d) => d !== dropped);
|
|
54
|
+
try {
|
|
55
|
+
await auditGoal({ need: 'n' }, runnerReturning(envelope(dims)));
|
|
56
|
+
eachDropRejected = false;
|
|
57
|
+
accepted.add(dropped);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* expected */
|
|
61
|
+
}
|
|
33
62
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
const result = combineProbes([
|
|
64
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
65
|
+
probe(completeAccepted, 'six-dimension audit is accepted'),
|
|
66
|
+
probe(completeCount === 6, `accepted audit carries exactly 6 dimensions (saw ${completeCount})`),
|
|
67
|
+
probe(fiveRejected, 'five-dimension audit is rejected'),
|
|
68
|
+
probe(rejectName === 'IncompleteAuditError', `rejection is IncompleteAuditError (saw ${rejectName}, ${IncompleteAuditError.name})`),
|
|
69
|
+
probe(eachDropRejected, `dropping any single dimension is rejected (accepted: ${[...accepted].join(',') || 'none'})`)
|
|
70
|
+
]);
|
|
71
|
+
const artifact = row.sourceFiles[0] ?? 'src/services/audit/audit-goal-service.ts';
|
|
72
|
+
if (result.ok)
|
|
73
|
+
return pass(ctx, artifact);
|
|
74
|
+
return fail(ctx, artifact, 'auditGoal() accepts a complete six-dimension payload and rejects any five-dimension payload', result.detail, 'J04 invariant broken: the audit does not cover exactly six dimensions');
|
|
42
75
|
}
|
|
@@ -1,21 +1,123 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { IncompleteFinalReviewError, MAX_EVIDENCE_BYTES_PER_FILE, outputBudgetForEvidence, prepareFinalReview, resolveOutputBudget, undeliverableDimensions } from '../../final-review/final-review-service.js';
|
|
5
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
6
|
+
/** The four dimensions the frozen baseline names. The 5th is NOT one of them. */
|
|
7
|
+
const FOUR_DIMENSIONS = [
|
|
8
|
+
'functional-completeness', 'problem-resolution', 'no-new-bugs', 'existing-functionality-intact'
|
|
8
9
|
];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const FIFTH_DIMENSION = 'capability-consistency';
|
|
11
|
+
/**
|
|
12
|
+
* One oversized, omitted source that backs `dimension`. `omitted` + over-cap +
|
|
13
|
+
* zero delivered bytes is the structural case: nothing can ever deliver it.
|
|
14
|
+
*/
|
|
15
|
+
function undeliverableSource(dimension, totalBytes) {
|
|
14
16
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
source: {
|
|
18
|
+
key: `k-${dimension}`,
|
|
19
|
+
label: dimension,
|
|
20
|
+
segments: [`${dimension}.md`],
|
|
21
|
+
supports: [dimension],
|
|
22
|
+
delivery: { kind: 'whole' }
|
|
23
|
+
},
|
|
24
|
+
relativePath: `${dimension}.md`,
|
|
25
|
+
absolutePath: `/nonexistent/${dimension}.md`,
|
|
26
|
+
status: 'omitted',
|
|
27
|
+
totalBytes,
|
|
28
|
+
includedBytes: 0,
|
|
29
|
+
content: '',
|
|
30
|
+
reason: 'over-cap'
|
|
20
31
|
};
|
|
21
32
|
}
|
|
33
|
+
function reviewJson(dimensions) {
|
|
34
|
+
return JSON.stringify({
|
|
35
|
+
rid: 'J05-fixture',
|
|
36
|
+
generatedAt: new Date(0).toISOString(),
|
|
37
|
+
dimensions: dimensions.map((dimension) => ({
|
|
38
|
+
dimension,
|
|
39
|
+
verdict: 'pass',
|
|
40
|
+
summary: 's',
|
|
41
|
+
evidence: [],
|
|
42
|
+
confidence: 'high'
|
|
43
|
+
})),
|
|
44
|
+
overallSummary: 's',
|
|
45
|
+
allPass: true,
|
|
46
|
+
needsAttention: []
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function runnerReturning(output) {
|
|
50
|
+
return { call: async () => ({ output, tokens: { input: 1, output: 1024 } }) };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Drive the real 4-dimension gate: a reply covering three of four dimensions
|
|
54
|
+
* must be refused, a reply covering all four must be accepted. Reading the
|
|
55
|
+
* four strings out of `final-review-service.ts` proved only that the literals
|
|
56
|
+
* were still typed somewhere in the file.
|
|
57
|
+
*/
|
|
58
|
+
async function probePrepareFinalReview() {
|
|
59
|
+
const root = mkdtempSync(join(tmpdir(), 'cbl-J05-'));
|
|
60
|
+
try {
|
|
61
|
+
const sessionId = 'guard-J05';
|
|
62
|
+
mkdirSync(join(root, '.peaks', '_runtime', sessionId, 'audit-goal'), { recursive: true });
|
|
63
|
+
writeFileSync(join(root, '.peaks', '_runtime', sessionId, 'audit-goal', 'J05-fixture.json'), JSON.stringify({ successCriteria: ['c'] }));
|
|
64
|
+
const opts = { projectRoot: root, sessionId };
|
|
65
|
+
let rejected = false;
|
|
66
|
+
let rejectName = 'none';
|
|
67
|
+
try {
|
|
68
|
+
await prepareFinalReview('J05-fixture', { ...opts, llmRunner: runnerReturning(reviewJson(FOUR_DIMENSIONS.slice(0, 3))) });
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
rejected = e.name === IncompleteFinalReviewError.name;
|
|
72
|
+
rejectName = e.name;
|
|
73
|
+
}
|
|
74
|
+
let accepted = false;
|
|
75
|
+
let acceptError = '';
|
|
76
|
+
try {
|
|
77
|
+
const out = await prepareFinalReview('J05-fixture', { ...opts, llmRunner: runnerReturning(reviewJson(FOUR_DIMENSIONS)) });
|
|
78
|
+
accepted = out.dimensions.length === 4;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
acceptError = `${e.name}: ${e.message.slice(0, 160)}`;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
rejected,
|
|
85
|
+
accepted,
|
|
86
|
+
detail: `three-dimension reply rejected=${String(rejected)} (${rejectName}); four-dimension reply accepted=${String(accepted)}${acceptError ? ` [${acceptError}]` : ''}`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
rmSync(root, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function runJ05Contract(ctx) {
|
|
94
|
+
const row = requireBaselineRow(ctx);
|
|
95
|
+
const missing = missingSourceFiles(ctx, row);
|
|
96
|
+
const prepared = await probePrepareFinalReview();
|
|
97
|
+
const oversized = FOUR_DIMENSIONS.map((d) => undeliverableSource(d, MAX_EVIDENCE_BYTES_PER_FILE + 1000));
|
|
98
|
+
const reported = undeliverableDimensions(oversized).map((r) => r.dimension);
|
|
99
|
+
const fifthOnly = undeliverableDimensions([undeliverableSource(FIFTH_DIMENSION, MAX_EVIDENCE_BYTES_PER_FILE + 1000)]);
|
|
100
|
+
const floor = outputBudgetForEvidence(0);
|
|
101
|
+
const ceiling = outputBudgetForEvidence(Number.MAX_SAFE_INTEGER);
|
|
102
|
+
let badOverrideThrows = false;
|
|
103
|
+
try {
|
|
104
|
+
resolveOutputBudget(0, { PEAKS_FINAL_REVIEW_MAX_OUTPUT_TOKENS: 'abc' });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
badOverrideThrows = true;
|
|
108
|
+
}
|
|
109
|
+
const result = combineProbes([
|
|
110
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
111
|
+
probe(prepared.rejected, `a three-dimension review is refused (${prepared.detail})`),
|
|
112
|
+
probe(prepared.accepted, `a four-dimension review is accepted (${prepared.detail})`),
|
|
113
|
+
probe(reported.length === 4 && FOUR_DIMENSIONS.every((d) => reported.includes(d)), `the canonical dimension set is exactly the four baseline dimensions (reported: ${reported.join(',') || 'none'})`),
|
|
114
|
+
probe(fifthOnly.length === 0, `the fifth dimension is not part of the required set (reported: ${fifthOnly.length})`),
|
|
115
|
+
probe(MAX_EVIDENCE_BYTES_PER_FILE === 10240, `per-file evidence cap divides the total by four (${String(MAX_EVIDENCE_BYTES_PER_FILE)})`),
|
|
116
|
+
probe(floor === 15288 && ceiling === 32000, `output budget is clamped on both ends (${String(floor)} / ${String(ceiling)})`),
|
|
117
|
+
probe(badOverrideThrows, 'a non-integer budget override throws instead of falling back silently')
|
|
118
|
+
]);
|
|
119
|
+
const artifact = row.sourceFiles[0] ?? 'src/services/final-review/final-review-service.ts';
|
|
120
|
+
if (result.ok)
|
|
121
|
+
return pass(ctx, artifact);
|
|
122
|
+
return fail(ctx, artifact, 'every final review contains exactly four dimensions, and ambiguous ones never auto-pass', result.detail, 'J05 invariant broken: the final review no longer enforces exactly four dimensions');
|
|
123
|
+
}
|
|
@@ -1,2 +1,16 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe for "the resume option always identifies the DEEPEST
|
|
4
|
+
* completed gate".
|
|
5
|
+
*
|
|
6
|
+
* The load-bearing part is that the SAME fixture is walked from shallow to
|
|
7
|
+
* deep: with only a PRD request the classifier must stop at `rd-planning`;
|
|
8
|
+
* the moment a deeper RD `qa-handoff` request appears (with its review
|
|
9
|
+
* artifacts) the point must move to `qa-validation`; adding the txt handoff
|
|
10
|
+
* must end the workflow. A classifier that returned a constant point, or that
|
|
11
|
+
* looked at file existence alone, cannot track that progression.
|
|
12
|
+
*
|
|
13
|
+
* The old check asserted that a file named `session-resume-service.ts` exists
|
|
14
|
+
* and contains the word "resume" — which the filename already guarantees.
|
|
15
|
+
*/
|
|
2
16
|
export declare function runJ06Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,43 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { classifyResume } from '../../skill/resume-detector.js';
|
|
5
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
6
|
+
const SESSION_ID = '2026-09-15-guard-j06';
|
|
7
|
+
function write(sessionDir, relative, body) {
|
|
8
|
+
const abs = join(sessionDir, relative);
|
|
9
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
10
|
+
writeFileSync(abs, body);
|
|
11
|
+
}
|
|
12
|
+
function request(body) {
|
|
13
|
+
return `# request\n\n## Status\n\n- state: ${body}\n`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Behavioural probe for "the resume option always identifies the DEEPEST
|
|
17
|
+
* completed gate".
|
|
18
|
+
*
|
|
19
|
+
* The load-bearing part is that the SAME fixture is walked from shallow to
|
|
20
|
+
* deep: with only a PRD request the classifier must stop at `rd-planning`;
|
|
21
|
+
* the moment a deeper RD `qa-handoff` request appears (with its review
|
|
22
|
+
* artifacts) the point must move to `qa-validation`; adding the txt handoff
|
|
23
|
+
* must end the workflow. A classifier that returned a constant point, or that
|
|
24
|
+
* looked at file existence alone, cannot track that progression.
|
|
25
|
+
*
|
|
26
|
+
* The old check asserted that a file named `session-resume-service.ts` exists
|
|
27
|
+
* and contains the word "resume" — which the filename already guarantees.
|
|
28
|
+
*/
|
|
12
29
|
export async function runJ06Contract(ctx) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
const row = requireBaselineRow(ctx);
|
|
31
|
+
const missing = missingSourceFiles(ctx, row);
|
|
32
|
+
const root = mkdtempSync(join(tmpdir(), 'cbl-J06-'));
|
|
33
|
+
const peaksRoot = join(root, '_runtime');
|
|
34
|
+
const sessionDir = join(peaksRoot, SESSION_ID);
|
|
35
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
36
|
+
try {
|
|
37
|
+
write(sessionDir, 'prd/requests/001-shallow.md', request('handed-off'));
|
|
38
|
+
const shallow = classifyResume(SESSION_ID, peaksRoot);
|
|
39
|
+
// `ridOf()` strips the `NNN-` index prefix, so the review artifacts are
|
|
40
|
+
// keyed by `deeper`, not `001-deeper`.
|
|
41
|
+
write(sessionDir, 'rd/requests/001-deeper.md', request('qa-handoff'));
|
|
42
|
+
write(sessionDir, 'rd/code-review-deeper.md', '# code review\n');
|
|
43
|
+
write(sessionDir, 'audit/security-deeper.md', '# security\n');
|
|
44
|
+
const deeper = classifyResume(SESSION_ID, peaksRoot);
|
|
45
|
+
write(sessionDir, 'txt/handoff.md', '# handoff\n');
|
|
46
|
+
const done = classifyResume(SESSION_ID, peaksRoot);
|
|
47
|
+
const result = combineProbes([
|
|
48
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
49
|
+
probe(shallow.kind === 'resume' && shallow.point === 'rd-planning', `shallow fixture resumes at rd-planning (saw kind=${shallow.kind} point=${String(shallow.point)})`),
|
|
50
|
+
probe(deeper.kind === 'resume' && deeper.point === 'qa-validation', `adding an RD qa-handoff request moves the point DEEPER to qa-validation (saw kind=${deeper.kind} point=${String(deeper.point)})`),
|
|
51
|
+
probe(done.kind === 'complete', `adding txt/handoff.md marks the workflow complete (saw kind=${done.kind} point=${String(done.point)})`)
|
|
52
|
+
]);
|
|
53
|
+
const artifact = row.sourceFiles[0] ?? 'src/services/skill/resume-detector.ts';
|
|
54
|
+
if (result.ok)
|
|
55
|
+
return pass(ctx, artifact);
|
|
56
|
+
return fail(ctx, artifact, 'resume identifies the deepest completed gate (rd-planning → qa-validation → complete)', result.detail, 'J06 invariant broken: resume no longer tracks the deepest completed gate');
|
|
22
57
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const src = readFileSync(join(ctx.projectRoot, f), 'utf8');
|
|
26
|
-
return src.toLowerCase().includes('resume');
|
|
27
|
-
});
|
|
28
|
-
if (!mentionsResume) {
|
|
29
|
-
return {
|
|
30
|
-
journeyId: 'J06',
|
|
31
|
-
contract: 'workflow-trace',
|
|
32
|
-
status: 'fail',
|
|
33
|
-
diff: { before: 'resume service files mention "resume"', after: 'none mention resume', reason: 'J06#1 broken: resume service files do not reference the resume concept' },
|
|
34
|
-
artifactPath: existing[0]
|
|
35
|
-
};
|
|
58
|
+
finally {
|
|
59
|
+
rmSync(root, { recursive: true, force: true });
|
|
36
60
|
}
|
|
37
|
-
return {
|
|
38
|
-
journeyId: 'J06',
|
|
39
|
-
contract: 'workflow-trace',
|
|
40
|
-
status: 'pass',
|
|
41
|
-
artifactPath: existing[0]
|
|
42
|
-
};
|
|
43
61
|
}
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the (mtime, sha256) gate.
|
|
4
|
+
*
|
|
5
|
+
* A cache entry is written for a passing test, then the file's CONTENT is
|
|
6
|
+
* changed while its mtime is restored to the original value. If the gate ever
|
|
7
|
+
* degraded to an mtime-only (or filename-only) check, the cache would report a
|
|
8
|
+
* hit for a file whose bytes changed — exactly the fake-green the invariant
|
|
9
|
+
* forbids. The mtime-only and status gates are probed the same way.
|
|
10
|
+
*/
|
|
2
11
|
export declare function runJ07Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,54 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'src/cli/commands/test-commands.ts',
|
|
11
|
-
'src/services/test/',
|
|
12
|
-
'src/services/test-runner/'
|
|
13
|
-
];
|
|
14
|
-
export async function runJ07Contract(ctx) {
|
|
15
|
-
// 1. CLI probe: `peaks test --help` must produce non-empty output.
|
|
16
|
-
let helpOk = false;
|
|
4
|
+
import { TEST_CACHE_DIR, isCacheable, mtimeOfFile, readTestCache, recordTestResult, sha256OfFile, testCacheDir, writeTestCache } from '../../test-cache/test-cache-service.js';
|
|
5
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
6
|
+
const TEST_NAME = 'fingerprint probe';
|
|
7
|
+
/** Highest vitest major the frozen baseline allows ("MUST NOT be locked at 5.x"). */
|
|
8
|
+
const MAX_VITEST_MAJOR = 4;
|
|
9
|
+
function vitestMajor(projectRoot) {
|
|
17
10
|
try {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}).toString('utf8');
|
|
25
|
-
helpOk = out.length > 0 && !out.includes('unknown command');
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
|
|
12
|
+
const raw = pkg.devDependencies?.['vitest'] ?? pkg.dependencies?.['vitest'];
|
|
13
|
+
if (typeof raw !== 'string')
|
|
14
|
+
return null;
|
|
15
|
+
const major = /(\d+)/.exec(raw);
|
|
16
|
+
return major === null ? null : Number(major[1]);
|
|
26
17
|
}
|
|
27
18
|
catch {
|
|
28
|
-
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Behavioural probe of the (mtime, sha256) gate.
|
|
24
|
+
*
|
|
25
|
+
* A cache entry is written for a passing test, then the file's CONTENT is
|
|
26
|
+
* changed while its mtime is restored to the original value. If the gate ever
|
|
27
|
+
* degraded to an mtime-only (or filename-only) check, the cache would report a
|
|
28
|
+
* hit for a file whose bytes changed — exactly the fake-green the invariant
|
|
29
|
+
* forbids. The mtime-only and status gates are probed the same way.
|
|
30
|
+
*/
|
|
31
|
+
export async function runJ07Contract(ctx) {
|
|
32
|
+
const row = requireBaselineRow(ctx);
|
|
33
|
+
const missing = missingSourceFiles(ctx, row);
|
|
34
|
+
const root = mkdtempSync(join(tmpdir(), 'cbl-J07-'));
|
|
35
|
+
const file = join(root, 'probe.test.ts');
|
|
36
|
+
try {
|
|
37
|
+
writeFileSync(file, 'export const a = 1;\n');
|
|
38
|
+
recordTestResult(root, file, 'vitest', {
|
|
39
|
+
testName: TEST_NAME,
|
|
40
|
+
status: 'passed',
|
|
41
|
+
durationMs: 1,
|
|
42
|
+
lastRun: new Date().toISOString()
|
|
43
|
+
});
|
|
44
|
+
const baseline = isCacheable(root, file, TEST_NAME);
|
|
45
|
+
const cached = readTestCache(root, file);
|
|
46
|
+
// Gate 1, isolated: the file's bytes change but the recorded mtime is
|
|
47
|
+
// up-to-date, so ONLY the sha check can catch it. If the gate degraded to
|
|
48
|
+
// an mtime-only check this reports a hit for a file nobody verified.
|
|
49
|
+
writeFileSync(file, 'export const a = 2;\n');
|
|
50
|
+
writeTestCache(root, { ...cached, fileMtime: mtimeOfFile(file) });
|
|
51
|
+
const afterSilentEdit = isCacheable(root, file, TEST_NAME);
|
|
52
|
+
// Gate 2, isolated: the sha is up-to-date but the recorded mtime is stale.
|
|
53
|
+
writeTestCache(root, { ...cached, fileSha256: sha256OfFile(file), fileMtime: cached.fileMtime - 5_000 });
|
|
54
|
+
const afterTouch = isCacheable(root, file, TEST_NAME);
|
|
55
|
+
// A non-passing status must never be served as a hit.
|
|
56
|
+
recordTestResult(root, file, 'vitest', {
|
|
57
|
+
testName: 'skipped probe',
|
|
58
|
+
status: 'skipped',
|
|
59
|
+
durationMs: 0,
|
|
60
|
+
lastRun: new Date().toISOString()
|
|
61
|
+
});
|
|
62
|
+
const skipped = isCacheable(root, file, 'skipped probe');
|
|
63
|
+
const unknown = isCacheable(root, file, 'never recorded');
|
|
64
|
+
const major = vitestMajor(ctx.projectRoot);
|
|
65
|
+
const result = combineProbes([
|
|
66
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
67
|
+
probe(baseline.hit, `an unchanged passing test is a cache hit (reason=${String(baseline.reason)})`),
|
|
68
|
+
probe(!afterSilentEdit.hit && afterSilentEdit.reason === 'sha-changed', `a same-mtime content edit is NOT a hit (hit=${String(afterSilentEdit.hit)} reason=${String(afterSilentEdit.reason)})`),
|
|
69
|
+
probe(!afterTouch.hit && afterTouch.reason === 'mtime-changed', `a touched file is NOT a hit (hit=${String(afterTouch.hit)} reason=${String(afterTouch.reason)})`),
|
|
70
|
+
probe(!skipped.hit && skipped.reason === 'previous-skipped', `a previously skipped test is NOT a hit (hit=${String(skipped.hit)} reason=${String(skipped.reason)})`),
|
|
71
|
+
probe(!unknown.hit && unknown.reason === 'no-cache', `an unrecorded test is NOT a hit (reason=${String(unknown.reason)})`),
|
|
72
|
+
probe(testCacheDir(ctx.projectRoot).replace(/\\/g, '/').endsWith(`.peaks/_runtime/${TEST_CACHE_DIR}`), `the cache writes under .peaks/_runtime/${TEST_CACHE_DIR} (got ${testCacheDir(ctx.projectRoot)})`),
|
|
73
|
+
probe(major !== null && major <= MAX_VITEST_MAJOR, `vitest major is <= ${String(MAX_VITEST_MAJOR)} (saw ${String(major)})`)
|
|
74
|
+
]);
|
|
75
|
+
const artifact = row.sourceFiles[1] ?? 'src/services/test-cache/test-cache-service.ts';
|
|
76
|
+
if (result.ok)
|
|
77
|
+
return pass(ctx, artifact);
|
|
78
|
+
return fail(ctx, artifact, 'the per-test fingerprint cache only serves a hit for a file whose mtime AND sha256 are unchanged and whose last status passed', result.detail, 'J07 invariant broken: the fingerprint cache can return passed for an unverified file');
|
|
29
79
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const abs = join(ctx.projectRoot, f);
|
|
33
|
-
if (!existsSync(abs))
|
|
34
|
-
return false;
|
|
35
|
-
if (existsSync(abs) && readFileSync(abs, 'utf8').includes('vitest'))
|
|
36
|
-
return true;
|
|
37
|
-
return false;
|
|
38
|
-
});
|
|
39
|
-
// 3. package.json probe: vitest is in dependencies.
|
|
40
|
-
const pkgPath = join(ctx.projectRoot, 'package.json');
|
|
41
|
-
let pkgHasVitest = false;
|
|
42
|
-
if (existsSync(pkgPath)) {
|
|
43
|
-
const pkg = readFileSync(pkgPath, 'utf8');
|
|
44
|
-
pkgHasVitest = /"vitest"\s*:/i.test(pkg);
|
|
80
|
+
finally {
|
|
81
|
+
rmSync(root, { recursive: true, force: true });
|
|
45
82
|
}
|
|
46
|
-
const ok = helpOk && sourceMentionsVitest && pkgHasVitest;
|
|
47
|
-
return {
|
|
48
|
-
journeyId: 'J07',
|
|
49
|
-
contract: 'cli-output-golden',
|
|
50
|
-
status: ok ? 'pass' : 'fail',
|
|
51
|
-
...(ok ? {} : { diff: { before: 'peaks test --help works, test commands reference vitest, package.json has vitest', after: `helpOk=${helpOk} sourceMentionsVitest=${sourceMentionsVitest} pkgHasVitest=${pkgHasVitest}`, reason: 'J07#1 broken: peaks test does not delegate to vitest' } }),
|
|
52
|
-
artifactPath: 'src/cli/commands/test-commands.ts'
|
|
53
|
-
};
|
|
54
83
|
}
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the crystallization gate.
|
|
4
|
+
*
|
|
5
|
+
* Before this the contract only asserted that one of three candidate files
|
|
6
|
+
* exists and mentions "crystalliz"/"sediment"/"promot" — the file it found is
|
|
7
|
+
* named `crystallization-service.ts`, so the check restated its own filename.
|
|
8
|
+
*
|
|
9
|
+
* Here the real gate is driven with a completed and an incomplete task state:
|
|
10
|
+
* a task whose `gates_passed` is false must be refused at the Zod boundary AND
|
|
11
|
+
* again at the service boundary.
|
|
12
|
+
*/
|
|
2
13
|
export declare function runJ08Contract(ctx: GuardContext): Promise<GuardRunResult>;
|