peaks-loop 4.0.49 → 4.0.51
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 +38 -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/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/hooks-commands.js +55 -38
- package/dist/cli/commands/share-commands.js +113 -20
- 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/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/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-modes.d.ts +13 -2
- package/dist/services/code/auto-compact-modes.js +20 -4
- package/dist/services/code/post-compact-detector.js +20 -11
- package/dist/services/code/step-08-gate.js +21 -6
- package/dist/services/config/config-safety.js +11 -9
- package/dist/services/dispatch/sub-agent-dispatcher.d.ts +11 -30
- package/dist/services/dispatch/sub-agent-dispatcher.js +5 -48
- package/dist/services/final-review/pre-post-diff.js +10 -2
- package/dist/services/ide/adapters/claude-code-adapter.js +0 -1
- package/dist/services/ide/adapters/codex-adapter.js +1 -2
- package/dist/services/ide/adapters/cursor-adapter.js +1 -2
- package/dist/services/ide/adapters/hermes-adapter.js +1 -2
- package/dist/services/ide/adapters/openclaw-adapter.js +1 -2
- package/dist/services/ide/adapters/qoder-adapter.js +1 -2
- package/dist/services/ide/adapters/tongyi-lingma-adapter.js +1 -2
- package/dist/services/ide/adapters/trae-adapter.js +1 -2
- package/dist/services/ide/adapters/zcode-adapter.js +0 -1
- package/dist/services/ide/ide-types.d.ts +0 -2
- package/dist/services/observability/observability-service.d.ts +1 -1
- package/dist/services/scan/api-diff-types.js +20 -2
- package/dist/services/security/safe-settings-path.js +19 -1
- package/dist/services/skill/skill-search-service.d.ts +3 -3
- 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/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/package.json +9 -7
- package/scripts/copy-templates.mjs +0 -12
- package/scripts/install-skills.mjs +154 -53
- package/skills/bee/peaks-perf-audit/SKILL.md +2 -2
- package/skills/bee/peaks-perf-audit/references/audit-protocol.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +3 -3
- package/skills/bee/peaks-prd/references/prd-for-multi-pass.md +1 -1
- package/skills/bee/peaks-prd/references/workflow.md +1 -1
- package/skills/bee/peaks-qa/SKILL.md +6 -7
- package/skills/bee/peaks-qa/references/external-capability-guidance.md +1 -1
- package/skills/bee/peaks-qa/references/qa-fanout-contract.md +1 -1
- package/skills/bee/peaks-qa/references/reading-handoff-frontmatter.md +2 -2
- package/skills/bee/peaks-rd/SKILL.md +2 -3
- package/skills/bee/peaks-rd/references/code-reviewer-4dim-hint.md +1 -1
- package/skills/bee/peaks-rd/references/external-references.md +1 -1
- package/skills/bee/peaks-rd/references/mandatory-perf-baseline.md +1 -1
- package/skills/bee/peaks-rd/references/ocr-multilang-1.8.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +2 -2
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +11 -8
- package/skills/bee/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +7 -7
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +1 -1
- package/skills/bee/peaks-rd/references/reading-v2-slice-results.md +1 -1
- package/skills/bee/peaks-rd/references/v2-12-fanout-collapse.md +7 -5
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +3 -3
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +3 -3
- package/skills/bee/peaks-security-audit/references/audit-protocol.md +1 -1
- package/skills/bee/peaks-txt/references/context-capsule.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-audit/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +20 -18
- package/skills/peaks-code/references/context-governance.md +1 -1
- package/skills/peaks-code/references/dag-orchestrator.md +3 -4
- package/skills/peaks-code/references/external-references.md +1 -1
- package/skills/peaks-code/references/external-skill-invocation.md +2 -2
- package/skills/peaks-code/references/fanout-mandatory.md +3 -3
- package/skills/peaks-code/references/frontend-only-mode.md +2 -2
- package/skills/peaks-code/references/gstack-integration.md +1 -1
- package/skills/peaks-code/references/micro-cycle.md +1 -1
- package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
- package/skills/peaks-code/references/project-scan-checklist.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +1 -1
- package/skills/peaks-code/references/runbook.md +6 -3
- package/skills/peaks-code/references/session-overload-signal-index.md +6 -4
- package/skills/peaks-code/references/startup-sequence.md +17 -17
- package/skills/peaks-code/references/step-0-8-gate.md +1 -1
- package/skills/peaks-code/references/step-11-memory-sediment.md +2 -2
- package/skills/peaks-code/references/sub-agent-dispatch.md +26 -25
- package/skills/peaks-code/references/swarm-dispatch-contract.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +3 -3
- package/skills/peaks-code/references/worktree-governance.md +1 -1
- package/skills/peaks-final-review/SKILL.md +3 -3
- package/skills/peaks-ide/references/audit-log-helper.md +5 -4
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +4 -4
- package/skills/peaks-slice-decompose/references/cross-pass-edge-interpretation.md +1 -1
- package/skills/peaks-slice-decompose/references/granularity-decision.md +1 -1
- package/skills/peaks-slice-decompose/references/v2-schema.md +2 -2
- package/skills/peaks-solo/SKILL.md +1 -2
- 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,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
3
4
|
const FIXTURES = [
|
|
4
5
|
['make', 'implement a CLI parser'],
|
|
5
6
|
['make', 'refactor the service'],
|
|
@@ -9,9 +10,11 @@ const FIXTURES = [
|
|
|
9
10
|
['run', 'execute a workflow']
|
|
10
11
|
];
|
|
11
12
|
export async function runJ01Contract(ctx) {
|
|
13
|
+
const row = requireBaselineRow(ctx);
|
|
14
|
+
const missing = missingSourceFiles(ctx, row);
|
|
12
15
|
const bin = process.env.PEAKS_BIN_OVERRIDE ?? join(ctx.projectRoot, 'bin', 'peaks.js');
|
|
13
|
-
|
|
14
|
-
let
|
|
16
|
+
const failures = [];
|
|
17
|
+
let routed = 0;
|
|
15
18
|
for (const [command, input] of FIXTURES) {
|
|
16
19
|
try {
|
|
17
20
|
const stdout = execFileSync('node', [bin, command, input], {
|
|
@@ -21,29 +24,25 @@ export async function runJ01Contract(ctx) {
|
|
|
21
24
|
}).toString('utf8');
|
|
22
25
|
const env = JSON.parse(stdout);
|
|
23
26
|
if (!env.ok) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
failures.push(`${command} ${input}: ok=false`);
|
|
28
|
+
}
|
|
29
|
+
else if (typeof env.data?.routedSkill === 'string' && env.data.routedSkill.length > 0) {
|
|
30
|
+
routed += 1;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
catch (e) {
|
|
30
|
-
|
|
31
|
-
firstFailure = `${command} ${input}: ${e.message}`;
|
|
32
|
-
break;
|
|
34
|
+
failures.push(`${command} ${input}: ${e.message.slice(0, 120)}`);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
diff: { before: 'all 6 routing cases ok', after: firstFailure, reason: 'J01#1 broken: super-command routing NL path deviates from frozen baseline' },
|
|
47
|
-
artifactPath: 'tests/integration/super-command-routing.test.ts'
|
|
48
|
-
};
|
|
37
|
+
const result = combineProbes([
|
|
38
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
39
|
+
probe(failures.length === 0, `all ${String(FIXTURES.length)} NL routing cases return ok (failures: ${failures.join('; ') || 'none'})`),
|
|
40
|
+
// The invariant is that the SYSTEM picks the skill: a bare ok envelope is
|
|
41
|
+
// not enough, the answer must name the skill it chose.
|
|
42
|
+
probe(routed > 0, `the envelope names the routed skill (${String(routed)}/${String(FIXTURES.length)})`)
|
|
43
|
+
]);
|
|
44
|
+
const artifact = row.sourceFiles[2] ?? 'tests/integration/super-command-routing.test.ts';
|
|
45
|
+
if (result.ok)
|
|
46
|
+
return pass(ctx, artifact);
|
|
47
|
+
return fail(ctx, artifact, 'every NL fixture routes through the super-command surface and the envelope names the chosen skill', result.detail, 'J01 invariant broken: super-command routing deviates from the frozen baseline');
|
|
49
48
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
-
export declare function runJ02Contract(ctx: GuardContext
|
|
2
|
+
export declare function runJ02Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,34 +1,120 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { mkdtempSync } from 'node:fs';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
5
6
|
const STATES = ['spec-locked', 'implemented', 'qa-handoff', 'handed-off'];
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Per-child timeout. J02 runs six peaks CLIs in a fresh cwd; on cold CI
|
|
8
|
+
// runners the first invocation pays tsx startup before the resolved file is
|
|
9
|
+
// hot, and any single child that hangs would block the whole guard run until
|
|
10
|
+
// the default node timeout (forever). 60s is comfortable on warm hosts and
|
|
11
|
+
// tight enough that a genuine hang surfaces in the gate step within the
|
|
12
|
+
// publish workflow's per-step budget.
|
|
13
|
+
const CHILD_TIMEOUT_MS = 60_000;
|
|
14
|
+
export async function runJ02Contract(ctx) {
|
|
15
|
+
const row = requireBaselineRow(ctx);
|
|
16
|
+
const missing = missingSourceFiles(ctx, row);
|
|
17
|
+
// Must be ABSOLUTE: the child runs with `cwd: tmp`, so a relative
|
|
18
|
+
// `bin/peaks.js` would be resolved against the temp workspace.
|
|
19
|
+
const bin = resolve(ctx.projectRoot, 'bin', 'peaks.js');
|
|
8
20
|
const tmp = mkdtempSync(join(tmpdir(), 'cbl-J02-'));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
// Each peaks-CLI invocation in this contract is wrapped so a child that
|
|
22
|
+
// hangs (or a child that produces no stdout) cannot silently pin the gate
|
|
23
|
+
// step until CI's job-level timeout fires. `execFileSync` already throws on
|
|
24
|
+
// non-zero exit; we additionally enforce timeout and surface the actual
|
|
25
|
+
// stderr in the error so a future failure can be diagnosed without re-running
|
|
26
|
+
// with a debugger.
|
|
27
|
+
const run = (args) => {
|
|
28
|
+
try {
|
|
29
|
+
const stdout = execFileSync('node', [bin, ...args], {
|
|
30
|
+
cwd: tmp,
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
timeout: CHILD_TIMEOUT_MS,
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
// The contract spawns peaks CLIs in a temp workspace; on a CI runner
|
|
35
|
+
// there is no IDE context, so peaks would refuse every `request.*`
|
|
36
|
+
// command with CALLER_ID_INVALID. Provide a deterministic caller id
|
|
37
|
+
// tied to the contract name. The contract is the only producer of
|
|
38
|
+
// this artifact, so the synthetic id cannot collide with anything
|
|
39
|
+
// real a developer is working on.
|
|
40
|
+
env: { ...process.env, PEAKS_CALLER_ID: `guard-J02-${ctx.sessionId}` }
|
|
41
|
+
});
|
|
42
|
+
return { stdout, stderr: '' };
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
const err = e;
|
|
46
|
+
const stderr = typeof err.stderr === 'string'
|
|
47
|
+
? err.stderr
|
|
48
|
+
: Buffer.isBuffer(err.stderr) ? err.stderr.toString('utf8') : '';
|
|
49
|
+
const stdout = typeof err.stdout === 'string'
|
|
50
|
+
? err.stdout
|
|
51
|
+
: Buffer.isBuffer(err.stdout) ? err.stdout.toString('utf8') : '';
|
|
52
|
+
// Preserve the original error type/message but attach stderr so the
|
|
53
|
+
// outer try-catch's `e.message.slice(0, 160)` sees something useful.
|
|
54
|
+
// Truncate stdout aggressively to keep the audit envelope bounded, but
|
|
55
|
+
// pick the HEAD and TAIL of the buffer so the leading envelope header
|
|
56
|
+
// and the trailing error are both visible.
|
|
57
|
+
const stdoutHead = stdout.slice(0, 600);
|
|
58
|
+
const stdoutTail = stdout.length > 1200 ? stdout.slice(-400) : '';
|
|
59
|
+
const stdoutPart = stdoutTail
|
|
60
|
+
? `${stdoutHead}...<truncated ${stdout.length - 1000}B>...${stdoutTail}`
|
|
61
|
+
: stdoutHead;
|
|
62
|
+
const wrapped = new Error(`${err.message} | stderr=${stderr.slice(0, 400)} | stdout=${stdoutPart}`);
|
|
63
|
+
wrapped.stdout = stdout;
|
|
64
|
+
throw wrapped;
|
|
65
|
+
}
|
|
33
66
|
};
|
|
67
|
+
try {
|
|
68
|
+
const ws = run(['workspace', 'init', '--project', tmp, '--json']);
|
|
69
|
+
const { data: { sessionId } } = JSON.parse(ws.stdout);
|
|
70
|
+
const rid = '2026-08-03-j02-fixture';
|
|
71
|
+
const initOut = run(['request', 'init', '--role', 'rd', '--id', rid, '--project', tmp, '--session-id', sessionId, '--apply', '--json']);
|
|
72
|
+
const initEnv = JSON.parse(initOut.stdout);
|
|
73
|
+
// `request init` writes the file as `NNN-<id-slug>.md`. The transition CLI accepts
|
|
74
|
+
// the file's basename (without .md) as the requestId. Derive it from data.path.
|
|
75
|
+
const baseName = initEnv.data.path.split(/[\\/]/).pop() ?? '';
|
|
76
|
+
const requestId = baseName.replace(/\.md$/i, '');
|
|
77
|
+
const transition = (state, extra) => run([
|
|
78
|
+
'request', 'transition', requestId, '--role', 'rd', '--state', state,
|
|
79
|
+
'--project', tmp, '--session-id', sessionId, '--confirm',
|
|
80
|
+
'--reason', 'J02 contract fixture', ...extra, '--json'
|
|
81
|
+
]).stdout;
|
|
82
|
+
// Hard-gate probe, run FIRST: from the freshly initialised state, jumping
|
|
83
|
+
// straight to the terminal state skips every intermediate gate and must not
|
|
84
|
+
// be accepted without the explicit incomplete-work escape hatch. Doing this
|
|
85
|
+
// before the legal walk is what makes it a skip (from `qa-handoff` the move
|
|
86
|
+
// to `handed-off` is legal and proves nothing).
|
|
87
|
+
let gateSkipRefused = false;
|
|
88
|
+
let skipError = 'not attempted';
|
|
89
|
+
try {
|
|
90
|
+
transition('handed-off', []);
|
|
91
|
+
skipError = 'transition was accepted';
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
gateSkipRefused = true;
|
|
95
|
+
skipError = e.message.slice(0, 160);
|
|
96
|
+
}
|
|
97
|
+
let last = '';
|
|
98
|
+
try {
|
|
99
|
+
for (const s of STATES) {
|
|
100
|
+
const env = JSON.parse(transition(s, ['--allow-incomplete']));
|
|
101
|
+
last = env.data.state;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
last = `error: ${e.message.slice(0, 160)}`;
|
|
106
|
+
}
|
|
107
|
+
const result = combineProbes([
|
|
108
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
109
|
+
probe(gateSkipRefused, `an incomplete jump to handed-off is refused (${skipError})`),
|
|
110
|
+
probe(last === 'handed-off', `the RD state machine reaches handed-off (saw ${last})`)
|
|
111
|
+
]);
|
|
112
|
+
const artifact = row.sourceFiles[3] ?? 'tests/integration/job-e2e.test.ts';
|
|
113
|
+
if (result.ok)
|
|
114
|
+
return pass(ctx, artifact);
|
|
115
|
+
return fail(ctx, artifact, 'every hard gate is enforced and the RD state machine still reaches handed-off', result.detail, 'J02 invariant broken: the RD state machine or its hard-gate enforcement changed');
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
119
|
+
}
|
|
34
120
|
}
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the "no silent-catch / fake-green reintroduced"
|
|
4
|
+
* invariant.
|
|
5
|
+
*
|
|
6
|
+
* The previous version read `final-review-types.ts` and asserted it contained
|
|
7
|
+
* four dimension strings — a file whose own name is `final-review`, so the
|
|
8
|
+
* check restated its filename.
|
|
9
|
+
*
|
|
10
|
+
* Here the repository's own AST guard (`scripts/lint/silent-warning-detector.mjs`)
|
|
11
|
+
* is executed and its per-rule violation counts are compared against the frozen
|
|
12
|
+
* ceilings. A newly swallowed `catch` in `src/**` raises a count and reddens the
|
|
13
|
+
* journey.
|
|
14
|
+
*/
|
|
2
15
|
export declare function runJ03Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -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>;
|