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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/services/capability-guard-runner/registry.ts
|
|
2
|
+
//
|
|
3
|
+
// The single production entry point for the 15 P0 guard contracts.
|
|
4
|
+
//
|
|
5
|
+
// Why a registry: before this, `baseline run-guard` hard-coded `J01`, and the
|
|
6
|
+
// only caller of `runAllGuards` was its own unit test (which passed synthetic
|
|
7
|
+
// contracts against `projectRoot: '/'`). There was no production path that ran
|
|
8
|
+
// all fifteen journeys — so "15 pass / 0 fail" was an assertion nobody made.
|
|
9
|
+
//
|
|
10
|
+
// Each contract declares the baseline row AND the verbatim invariant text it
|
|
11
|
+
// enforces; `assertBaselineRef` resolves that against
|
|
12
|
+
// openspec/baselines/current/capability-baseline.json before the contract runs.
|
|
13
|
+
import { P0_JOURNEY_IDS } from '../capability-baseline/types.js';
|
|
14
|
+
import { runJ01Contract } from './contracts/J01.js';
|
|
15
|
+
import { runJ02Contract } from './contracts/J02.js';
|
|
16
|
+
import { runJ03Contract } from './contracts/J03.js';
|
|
17
|
+
import { runJ04Contract } from './contracts/J04.js';
|
|
18
|
+
import { runJ05Contract } from './contracts/J05.js';
|
|
19
|
+
import { runJ06Contract } from './contracts/J06.js';
|
|
20
|
+
import { runJ07Contract } from './contracts/J07.js';
|
|
21
|
+
import { runJ08Contract } from './contracts/J08.js';
|
|
22
|
+
import { runJ09Contract } from './contracts/J09.js';
|
|
23
|
+
import { runJ10Contract } from './contracts/J10.js';
|
|
24
|
+
import { runJ11Contract } from './contracts/J11.js';
|
|
25
|
+
import { runJ12Contract } from './contracts/J12.js';
|
|
26
|
+
import { runJ13Contract } from './contracts/J13.js';
|
|
27
|
+
import { runJ14Contract } from './contracts/J14.js';
|
|
28
|
+
import { runJ15Contract } from './contracts/J15.js';
|
|
29
|
+
/** The integration test that pins each journey's contract. */
|
|
30
|
+
const ARTIFACTS = {
|
|
31
|
+
J01: 'tests/integration/capability-guard/J01-envelope-arg-shapes.test.ts',
|
|
32
|
+
J02: 'tests/integration/capability-guard/J02-workflow-trace.test.ts',
|
|
33
|
+
J03: 'tests/integration/capability-guard/J03-problem-resolution-flow.test.ts',
|
|
34
|
+
J04: 'tests/integration/capability-guard/J04-audit-goal-binding.test.ts',
|
|
35
|
+
J05: 'tests/integration/capability-guard/J05-final-review-shape.test.ts',
|
|
36
|
+
J06: 'tests/integration/capability-guard/J06-resume-deepest-gate.test.ts',
|
|
37
|
+
J07: 'tests/integration/capability-guard/J07-test-runner-fidelity.test.ts',
|
|
38
|
+
J08: 'tests/integration/capability-guard/J08-asset-roundtrip.test.ts',
|
|
39
|
+
J09: 'tests/integration/capability-guard/J09-sop-register.test.ts',
|
|
40
|
+
J10: 'tests/integration/capability-guard/J10-ide-install-assertion.test.ts',
|
|
41
|
+
J11: 'tests/integration/capability-guard/J11-doctor-cli-snapshot.test.ts',
|
|
42
|
+
J12: 'tests/integration/capability-guard/J12-lease-lifecycle.test.ts',
|
|
43
|
+
J13: 'tests/integration/capability-guard/J13-content-pipeline-trace.test.ts',
|
|
44
|
+
J14: 'tests/integration/capability-guard/J14-issue-orchestrator-trace.test.ts',
|
|
45
|
+
J15: 'tests/integration/capability-guard/J15-spec-coverage.test.ts'
|
|
46
|
+
};
|
|
47
|
+
/** One row per P0 journey. `invariant` must be verbatim baseline text. */
|
|
48
|
+
const SPECS = {
|
|
49
|
+
J01: {
|
|
50
|
+
kind: 'envelope-arg-shapes',
|
|
51
|
+
invariant: 'The CLI verb is never shown to the user as a required input',
|
|
52
|
+
execute: runJ01Contract
|
|
53
|
+
},
|
|
54
|
+
J02: {
|
|
55
|
+
kind: 'workflow-trace',
|
|
56
|
+
invariant: 'Every hard gate (audit / RD / QA / final-review) is enforced; a failure transitions state back to the prior role',
|
|
57
|
+
execute: runJ02Contract
|
|
58
|
+
},
|
|
59
|
+
J03: {
|
|
60
|
+
kind: 'workflow-trace',
|
|
61
|
+
invariant: 'No silent-catch or fake-green pattern is reintroduced in src/services/**',
|
|
62
|
+
execute: runJ03Contract
|
|
63
|
+
},
|
|
64
|
+
J04: {
|
|
65
|
+
kind: 'hook-assertion',
|
|
66
|
+
invariant: 'The audit covers exactly six dimensions: correctness, completeness, scope, risks, alternatives, constraints',
|
|
67
|
+
execute: runJ04Contract
|
|
68
|
+
},
|
|
69
|
+
J05: {
|
|
70
|
+
kind: 'workflow-trace',
|
|
71
|
+
invariant: 'Every final review contains exactly four dimensions: functional-completeness, problem-resolution, no-new-bugs, existing-functionality-intact',
|
|
72
|
+
execute: runJ05Contract
|
|
73
|
+
},
|
|
74
|
+
J06: {
|
|
75
|
+
kind: 'workflow-trace',
|
|
76
|
+
invariant: 'The resume option always identifies the deepest completed gate (audit / RD / QA / final-review)',
|
|
77
|
+
execute: runJ06Contract
|
|
78
|
+
},
|
|
79
|
+
J07: {
|
|
80
|
+
kind: 'cli-output-golden',
|
|
81
|
+
invariant: "Per-test fingerprint cache never returns 'passed' for an unverified file (fileMtime + fileSha256 gate)",
|
|
82
|
+
execute: runJ07Contract
|
|
83
|
+
},
|
|
84
|
+
J08: {
|
|
85
|
+
kind: 'asset-roundtrip',
|
|
86
|
+
invariant: 'Crystallization requires all gates (audit / RD / QA / final-review) to be in a passing state',
|
|
87
|
+
execute: runJ08Contract
|
|
88
|
+
},
|
|
89
|
+
J09: {
|
|
90
|
+
kind: 'sop-register',
|
|
91
|
+
invariant: 'An SOP id must match SOP_ID_PATTERN (lowercase kebab, no dots/slashes) to prevent path traversal',
|
|
92
|
+
execute: runJ09Contract
|
|
93
|
+
},
|
|
94
|
+
J10: {
|
|
95
|
+
kind: 'hook-assertion',
|
|
96
|
+
invariant: 'planHookInstall() runs before applyHookInstall() so the diff is reviewable',
|
|
97
|
+
execute: runJ10Contract
|
|
98
|
+
},
|
|
99
|
+
J11: {
|
|
100
|
+
kind: 'cli-output-golden',
|
|
101
|
+
invariant: 'Doctor checks live under src/services/doctor/doctor-service/checks/ as a code-driven fixed-registry tree',
|
|
102
|
+
execute: runJ11Contract
|
|
103
|
+
},
|
|
104
|
+
J12: {
|
|
105
|
+
kind: 'concurrency-lease',
|
|
106
|
+
invariant: 'Auto-release on terminal heartbeat is best-effort but idempotent: duplicate release is a no-op',
|
|
107
|
+
execute: runJ12Contract
|
|
108
|
+
},
|
|
109
|
+
J13: {
|
|
110
|
+
kind: 'workflow-trace',
|
|
111
|
+
invariant: 'The skill does NOT import peaks-code internals; it reuses the Loop Engineering primitives',
|
|
112
|
+
execute: runJ13Contract
|
|
113
|
+
},
|
|
114
|
+
J14: {
|
|
115
|
+
kind: 'workflow-trace',
|
|
116
|
+
invariant: 'Every fix carries an AI-modified declaration in the commit body (RL-3)',
|
|
117
|
+
execute: runJ14Contract
|
|
118
|
+
},
|
|
119
|
+
J15: {
|
|
120
|
+
kind: 'spec-coverage',
|
|
121
|
+
invariant: 'Coverage-summary discovery order is fixed: <projectRoot>/coverage/coverage-summary.json then /openspec/coverage-summary.json',
|
|
122
|
+
execute: runJ15Contract
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
export const GUARD_CONTRACTS = P0_JOURNEY_IDS.map((journeyId) => {
|
|
126
|
+
const spec = SPECS[journeyId];
|
|
127
|
+
return {
|
|
128
|
+
journeyId,
|
|
129
|
+
kind: spec.kind,
|
|
130
|
+
source: { baselineRow: journeyId, invariant: spec.invariant },
|
|
131
|
+
execute: spec.execute,
|
|
132
|
+
evidence: { kind: spec.kind, artifact: ARTIFACTS[journeyId] }
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
export function getGuardContract(journeyId) {
|
|
136
|
+
return GUARD_CONTRACTS.find((c) => c.journeyId === journeyId);
|
|
137
|
+
}
|
|
138
|
+
export function isJourneyId(value) {
|
|
139
|
+
return P0_JOURNEY_IDS.includes(value);
|
|
140
|
+
}
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
+
import type { CapabilityBaselineRow } from '../capability-baseline/types.js';
|
|
1
2
|
import type { GuardContract, GuardContext, GuardRunResult } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the contract's declared baseline row out of the frozen baseline and
|
|
5
|
+
* assert the declared invariant is verbatim text in that row.
|
|
6
|
+
*
|
|
7
|
+
* Before this, the check was `if (!source.baselineRow || !source.invariant)`,
|
|
8
|
+
* i.e. it only asserted the two fields were truthy. Nothing ever read the
|
|
9
|
+
* baseline, so a contract could name a journey / invariant that does not exist
|
|
10
|
+
* in the frozen file and still run to a green result.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertBaselineRef(contract: GuardContract, projectRoot: string): CapabilityBaselineRow;
|
|
2
13
|
export declare function runGuard(contract: GuardContract, ctx: GuardContext): Promise<GuardRunResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Run every contract and aggregate. A contract that throws (bad baseline
|
|
16
|
+
* reference, unreadable source file, a probe that blows up) is recorded as a
|
|
17
|
+
* `fail` result rather than aborting the whole run — the gate has to be able
|
|
18
|
+
* to say "this journey is broken", not just crash.
|
|
19
|
+
*/
|
|
3
20
|
export declare function runAllGuards(contracts: ReadonlyArray<GuardContract>, ctx: GuardContext): Promise<{
|
|
4
21
|
readonly pass: number;
|
|
5
22
|
readonly fail: number;
|
|
@@ -7,3 +24,12 @@ export declare function runAllGuards(contracts: ReadonlyArray<GuardContract>, ct
|
|
|
7
24
|
readonly total: number;
|
|
8
25
|
readonly results: ReadonlyArray<GuardRunResult>;
|
|
9
26
|
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Exit-code contract for a guard summary. `skipped` must not be
|
|
29
|
+
* indistinguishable from `pass`: a run that quietly skipped work is a
|
|
30
|
+
* different outcome from a run that verified work, so it gets its own code.
|
|
31
|
+
*/
|
|
32
|
+
export declare function exitCodeForGuardSummary(summary: {
|
|
33
|
+
readonly fail: number;
|
|
34
|
+
readonly skipped: number;
|
|
35
|
+
}): 0 | 1 | 2;
|
|
@@ -1,16 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/services/capability-guard-runner/runner.ts
|
|
2
|
+
import { readBaselineFile } from '../capability-baseline/store.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the contract's declared baseline row out of the frozen baseline and
|
|
5
|
+
* assert the declared invariant is verbatim text in that row.
|
|
6
|
+
*
|
|
7
|
+
* Before this, the check was `if (!source.baselineRow || !source.invariant)`,
|
|
8
|
+
* i.e. it only asserted the two fields were truthy. Nothing ever read the
|
|
9
|
+
* baseline, so a contract could name a journey / invariant that does not exist
|
|
10
|
+
* in the frozen file and still run to a green result.
|
|
11
|
+
*/
|
|
12
|
+
export function assertBaselineRef(contract, projectRoot) {
|
|
13
|
+
const r = readBaselineFile(projectRoot);
|
|
14
|
+
if (!r.ok) {
|
|
15
|
+
throw new Error(`GUARD_CONTRACT_MISSING_BASELINE_REF: ${contract.journeyId} cannot read the frozen baseline at ${projectRoot} (${r.error.code}: ${r.error.message})`);
|
|
4
16
|
}
|
|
17
|
+
const row = r.file.rows.find((x) => x.journeyId === contract.source.baselineRow);
|
|
18
|
+
if (!row) {
|
|
19
|
+
throw new Error(`GUARD_CONTRACT_MISSING_BASELINE_REF: ${contract.journeyId} references baseline row ${contract.source.baselineRow}, which is not in the frozen baseline`);
|
|
20
|
+
}
|
|
21
|
+
const declared = contract.source.invariant;
|
|
22
|
+
if (typeof declared !== 'string' || declared.trim().length === 0) {
|
|
23
|
+
throw new Error(`GUARD_CONTRACT_MISSING_BASELINE_REF: ${contract.journeyId} declares an empty invariant`);
|
|
24
|
+
}
|
|
25
|
+
if (!row.invariants.includes(declared)) {
|
|
26
|
+
throw new Error(`GUARD_CONTRACT_MISSING_BASELINE_REF: ${contract.journeyId} declares invariant "${declared}", which is not a verbatim invariant of baseline row ${row.journeyId}`);
|
|
27
|
+
}
|
|
28
|
+
return row;
|
|
5
29
|
}
|
|
6
30
|
export async function runGuard(contract, ctx) {
|
|
7
|
-
assertBaselineRef(contract);
|
|
8
|
-
return contract.execute(ctx);
|
|
31
|
+
assertBaselineRef(contract, ctx.projectRoot);
|
|
32
|
+
return contract.execute({ ...ctx, contract });
|
|
9
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Run every contract and aggregate. A contract that throws (bad baseline
|
|
36
|
+
* reference, unreadable source file, a probe that blows up) is recorded as a
|
|
37
|
+
* `fail` result rather than aborting the whole run — the gate has to be able
|
|
38
|
+
* to say "this journey is broken", not just crash.
|
|
39
|
+
*/
|
|
10
40
|
export async function runAllGuards(contracts, ctx) {
|
|
11
41
|
const results = [];
|
|
12
42
|
for (const c of contracts) {
|
|
13
|
-
|
|
43
|
+
try {
|
|
44
|
+
results.push(await runGuard(c, ctx));
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
results.push({
|
|
48
|
+
journeyId: c.journeyId,
|
|
49
|
+
contract: c.kind,
|
|
50
|
+
status: 'fail',
|
|
51
|
+
diff: {
|
|
52
|
+
before: 'contract runs against the frozen baseline',
|
|
53
|
+
after: e.message,
|
|
54
|
+
reason: `${c.journeyId} contract could not run`
|
|
55
|
+
},
|
|
56
|
+
artifactPath: c.evidence.artifact
|
|
57
|
+
});
|
|
58
|
+
}
|
|
14
59
|
}
|
|
15
60
|
return {
|
|
16
61
|
pass: results.filter((r) => r.status === 'pass').length,
|
|
@@ -20,3 +65,15 @@ export async function runAllGuards(contracts, ctx) {
|
|
|
20
65
|
results
|
|
21
66
|
};
|
|
22
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Exit-code contract for a guard summary. `skipped` must not be
|
|
70
|
+
* indistinguishable from `pass`: a run that quietly skipped work is a
|
|
71
|
+
* different outcome from a run that verified work, so it gets its own code.
|
|
72
|
+
*/
|
|
73
|
+
export function exitCodeForGuardSummary(summary) {
|
|
74
|
+
if (summary.fail > 0)
|
|
75
|
+
return 1;
|
|
76
|
+
if (summary.skipped > 0)
|
|
77
|
+
return 2;
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
@@ -51,6 +51,65 @@ export declare function resolveDispatchedStage(input: {
|
|
|
51
51
|
* attempts inside the same millisecond distinct.
|
|
52
52
|
*/
|
|
53
53
|
export declare function newCompactRunId(now: Date): string;
|
|
54
|
+
/**
|
|
55
|
+
* rid `2026-09-14-compact-dispatch-backoff`: the compact run this session has
|
|
56
|
+
* already DISPATCHED and whose outcome is still unknown — the backoff token.
|
|
57
|
+
*
|
|
58
|
+
* WHY A BACKOFF IS NEEDED AT ALL. Once the ratio crosses the auto-fire
|
|
59
|
+
* threshold it STAYS crossed: nothing in peaks-loop can compact a running
|
|
60
|
+
* session, and the harness fires only at its own red line. So the dispatch
|
|
61
|
+
* obligation became unsatisfiable and fired on every probe. Measured in one
|
|
62
|
+
* real session (2026-09-13T22:43:33Z → 2026-09-14T14:15:02Z, ~15.5 h):
|
|
63
|
+
* 1035 `dispatch` rows, 444 checkpoints, and ZERO compactions — 1075 by
|
|
64
|
+
* 14:22:09.112Z. The dispatch itself is idempotent — `ide-native` only
|
|
65
|
+
* installs a PreToolUse hook, and a second install of the same hook is a
|
|
66
|
+
* documented no-op — so 1074 of those rows installed nothing (their own
|
|
67
|
+
* `dispatchMessage` says `already installed`) and carried no new information.
|
|
68
|
+
* Only noise: a signal that fires a thousand times is not a signal.
|
|
69
|
+
*
|
|
70
|
+
* WHY NO NEW STORE. The one-record-per-session lifecycle store already holds
|
|
71
|
+
* the one fact the backoff needs: is a compact attempt dispatched and not yet
|
|
72
|
+
* superseded? `armed` and `compacting` are exactly those two stages, and
|
|
73
|
+
* `completed` / `failed` mean the attempt is over and the next crossing is a
|
|
74
|
+
* legitimate new one. The state lives here rather than in
|
|
75
|
+
* `compact-history.jsonl`, which is append-only and never rewritten — so the
|
|
76
|
+
* backoff cannot be built by editing history, which is the point.
|
|
77
|
+
*
|
|
78
|
+
* STALENESS IS DELIBERATELY IGNORED, for `settleOpenLifecycleRun`'s reason: a
|
|
79
|
+
* probe arriving after `staleAfterMs` has learned nothing about whether the run
|
|
80
|
+
* is still live. Age cannot make re-dispatching honest, so the stale window
|
|
81
|
+
* must not decide it. (`settleOpenLifecycleRun` reads with the same window for
|
|
82
|
+
* the same reason; the two must not grow two policies.)
|
|
83
|
+
*
|
|
84
|
+
* `queued` / `preparing` / `verifying` are NOT open here: they are transient
|
|
85
|
+
* stages inside a dispatch or a settle, and a process that died in one of them
|
|
86
|
+
* left no compact attempt to protect — the next probe should be free to
|
|
87
|
+
* dispatch. `failed` is not open either, by the same argument: a failure is a
|
|
88
|
+
* reason to try again, not a reason to stay quiet.
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* The answer to "is a compact attempt outstanding for this session?".
|
|
92
|
+
*
|
|
93
|
+
* `none` and `unresolvable` must not be the same value. The caller uses
|
|
94
|
+
* `none` to ADMIT a dispatch; `unresolvable` means the question could
|
|
95
|
+
* not be asked, and a caller that admits on "could not ask" has a gate
|
|
96
|
+
* that reads a string rather than the artifact it names.
|
|
97
|
+
*/
|
|
98
|
+
export type OpenDispatchRunRead = {
|
|
99
|
+
readonly kind: 'open';
|
|
100
|
+
readonly runId: string;
|
|
101
|
+
readonly stage: 'armed' | 'compacting';
|
|
102
|
+
readonly triggerRatio: number;
|
|
103
|
+
} | {
|
|
104
|
+
readonly kind: 'none';
|
|
105
|
+
} | {
|
|
106
|
+
readonly kind: 'unresolvable';
|
|
107
|
+
readonly reason: string;
|
|
108
|
+
};
|
|
109
|
+
export declare function readOpenDispatchRun(input: {
|
|
110
|
+
readonly projectRoot: string;
|
|
111
|
+
readonly sessionId: string;
|
|
112
|
+
}): OpenDispatchRunRead;
|
|
54
113
|
/**
|
|
55
114
|
* Slice 2026-08-01-compact-lifecycle (Task 5): the local transition
|
|
56
115
|
* builder. Carries `runId`, `triggerRatio` and `redLine` forward from
|
|
@@ -132,6 +191,18 @@ export declare function summarizeLifecycleError(error: unknown): string;
|
|
|
132
191
|
* `afterRatio` to append the "observed compaction point" row that makes the
|
|
133
192
|
* intent-vs-observed gap readable after a real session. The return value is
|
|
134
193
|
* telemetry only — callers that ignore it are unaffected.
|
|
194
|
+
*
|
|
195
|
+
* `lifecycleWritten` (repair R6) makes the returned record honest about
|
|
196
|
+
* whether the RUN actually moved. Before it, a failed write still returned
|
|
197
|
+
* the full envelope, so a store that could not be written reported a settle
|
|
198
|
+
* on every probe, forever: the run stayed `armed`, the next probe found it
|
|
199
|
+
* open again, and each one added another "observed compaction point" row —
|
|
200
|
+
* an unbounded append driven by a write that never happened. A failed write
|
|
201
|
+
* is not an absent run (the facts are real and stay true, so the caller may
|
|
202
|
+
* still record the measurement), but it is also not a settled one. This is
|
|
203
|
+
* the same field `settleOpenLifecycleRunOnCompactEvent` carries, for the
|
|
204
|
+
* same reason, so the two settle paths no longer disagree about what a write
|
|
205
|
+
* failure means.
|
|
135
206
|
*/
|
|
136
207
|
export declare function settleOpenLifecycleRun(input: {
|
|
137
208
|
readonly projectRoot: string;
|
|
@@ -140,10 +211,14 @@ export declare function settleOpenLifecycleRun(input: {
|
|
|
140
211
|
readonly source: string;
|
|
141
212
|
readonly autoFireThreshold: number;
|
|
142
213
|
readonly onLifecycleStage?: ((stage: CompactLifecycleStage, record: CompactLifecycleRecord) => void) | undefined;
|
|
214
|
+
/** Failure injection for the write below — the seam `CompactLifecyclePublisher` already takes. */
|
|
215
|
+
readonly failLifecycleWrite?: boolean | undefined;
|
|
143
216
|
}): {
|
|
144
217
|
readonly runId: string;
|
|
145
218
|
readonly triggerRatio: number;
|
|
146
219
|
readonly afterRatio: number;
|
|
220
|
+
/** `false` when the `completed` write threw, so the run is STILL open. */
|
|
221
|
+
readonly lifecycleWritten: boolean;
|
|
147
222
|
} | null;
|
|
148
223
|
/**
|
|
149
224
|
* rid `2026-09-13-compact-event-settle`: close out an open compact run because
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { readCompactLifecycle, writeCompactLifecycle } from '../compact-statusline/compact-lifecycle-store.js';
|
|
15
15
|
import { AUTO_COMPACT_RED_LINE_RATIO } from '../context/auto-compact-types.js';
|
|
16
|
+
import { tryGetSessionDir } from '../session/getSessionDir.js';
|
|
16
17
|
/**
|
|
17
18
|
* PRD-002b slice 2 — extract the few cross-cutting magic numbers
|
|
18
19
|
* that actually appear at runtime call sites in this orchestrator.
|
|
@@ -68,11 +69,18 @@ export function newCompactRunId(now) {
|
|
|
68
69
|
* later. Wrapping that unbounded read here keeps the magic number out
|
|
69
70
|
* of the settle path and makes the intent self-documenting.
|
|
70
71
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* is
|
|
72
|
+
* The session id is resolved through `tryGetSessionDir` FIRST, so the
|
|
73
|
+
* guard's throw is answered before the store is entered and the failing
|
|
74
|
+
* branch is a value the caller must handle rather than a `catch` nobody
|
|
75
|
+
* reads. The residual `catch` is therefore unreachable for a bad id;
|
|
76
|
+
* anything that reaches it is a store fault, and a store fault is still
|
|
77
|
+
* not "there is no open run" — hence `unresolvable`, never `none`.
|
|
74
78
|
*/
|
|
75
79
|
function readOpenCompactLifecycle(input) {
|
|
80
|
+
const resolved = tryGetSessionDir(input.projectRoot, input.sessionId);
|
|
81
|
+
if (!resolved.ok) {
|
|
82
|
+
return { kind: 'unresolvable', reason: resolved.reason };
|
|
83
|
+
}
|
|
76
84
|
let out;
|
|
77
85
|
try {
|
|
78
86
|
out = readCompactLifecycle({
|
|
@@ -85,10 +93,21 @@ function readOpenCompactLifecycle(input) {
|
|
|
85
93
|
staleAfterMs: Number.MAX_SAFE_INTEGER
|
|
86
94
|
});
|
|
87
95
|
}
|
|
88
|
-
catch {
|
|
89
|
-
return
|
|
96
|
+
catch (error) {
|
|
97
|
+
return { kind: 'unresolvable', reason: summarizeLifecycleError(error) };
|
|
90
98
|
}
|
|
91
|
-
return out.kind === 'valid' ? out.record :
|
|
99
|
+
return out.kind === 'valid' ? { kind: 'found', record: out.record } : { kind: 'none' };
|
|
100
|
+
}
|
|
101
|
+
export function readOpenDispatchRun(input) {
|
|
102
|
+
const read = readOpenCompactLifecycle(input);
|
|
103
|
+
if (read.kind === 'unresolvable')
|
|
104
|
+
return read;
|
|
105
|
+
if (read.kind === 'none')
|
|
106
|
+
return { kind: 'none' };
|
|
107
|
+
const record = read.record;
|
|
108
|
+
if (record.stage !== 'armed' && record.stage !== 'compacting')
|
|
109
|
+
return { kind: 'none' };
|
|
110
|
+
return { kind: 'open', runId: record.runId, stage: record.stage, triggerRatio: record.triggerRatio };
|
|
92
111
|
}
|
|
93
112
|
/**
|
|
94
113
|
* Slice 2026-08-01-compact-lifecycle (Task 5): the local transition
|
|
@@ -220,6 +239,18 @@ export function summarizeLifecycleError(error) {
|
|
|
220
239
|
* `afterRatio` to append the "observed compaction point" row that makes the
|
|
221
240
|
* intent-vs-observed gap readable after a real session. The return value is
|
|
222
241
|
* telemetry only — callers that ignore it are unaffected.
|
|
242
|
+
*
|
|
243
|
+
* `lifecycleWritten` (repair R6) makes the returned record honest about
|
|
244
|
+
* whether the RUN actually moved. Before it, a failed write still returned
|
|
245
|
+
* the full envelope, so a store that could not be written reported a settle
|
|
246
|
+
* on every probe, forever: the run stayed `armed`, the next probe found it
|
|
247
|
+
* open again, and each one added another "observed compaction point" row —
|
|
248
|
+
* an unbounded append driven by a write that never happened. A failed write
|
|
249
|
+
* is not an absent run (the facts are real and stay true, so the caller may
|
|
250
|
+
* still record the measurement), but it is also not a settled one. This is
|
|
251
|
+
* the same field `settleOpenLifecycleRunOnCompactEvent` carries, for the
|
|
252
|
+
* same reason, so the two settle paths no longer disagree about what a write
|
|
253
|
+
* failure means.
|
|
223
254
|
*/
|
|
224
255
|
export function settleOpenLifecycleRun(input) {
|
|
225
256
|
// A `conservative-fallback` probe means no signal was available at
|
|
@@ -229,12 +260,17 @@ export function settleOpenLifecycleRun(input) {
|
|
|
229
260
|
return null;
|
|
230
261
|
if (input.measuredRatio >= input.autoFireThreshold)
|
|
231
262
|
return null;
|
|
232
|
-
const
|
|
263
|
+
const openRead = readOpenCompactLifecycle({
|
|
233
264
|
projectRoot: input.projectRoot,
|
|
234
265
|
sessionId: input.sessionId
|
|
235
266
|
});
|
|
236
|
-
|
|
267
|
+
// Both `none` and `unresolvable` mean "nothing to settle" HERE, and the
|
|
268
|
+
// collapse is legitimate on this path rather than a fail-open: settling
|
|
269
|
+
// ADMITS nothing. No record can exist under an id that names no session
|
|
270
|
+
// directory, so there is nothing this probe could have been about.
|
|
271
|
+
if (openRead.kind !== 'found')
|
|
237
272
|
return null;
|
|
273
|
+
const prior = openRead.record;
|
|
238
274
|
// Only a run that was actually dispatched can be completed by a
|
|
239
275
|
// post-compact measurement. Both `compacting` (the in-band trigger is
|
|
240
276
|
// satisfied) and `armed` (a trigger was registered and could fire at
|
|
@@ -253,6 +289,8 @@ export function settleOpenLifecycleRun(input) {
|
|
|
253
289
|
...(withAfterRatio ? { afterRatio: input.measuredRatio } : {})
|
|
254
290
|
};
|
|
255
291
|
try {
|
|
292
|
+
if (input.failLifecycleWrite)
|
|
293
|
+
throw new Error('lifecycle store unavailable');
|
|
256
294
|
writeCompactLifecycle({
|
|
257
295
|
projectRoot: input.projectRoot,
|
|
258
296
|
sessionId: input.sessionId,
|
|
@@ -260,7 +298,10 @@ export function settleOpenLifecycleRun(input) {
|
|
|
260
298
|
});
|
|
261
299
|
}
|
|
262
300
|
catch {
|
|
263
|
-
|
|
301
|
+
// Best-effort telemetry, as everywhere in this file — but the failure is
|
|
302
|
+
// REPORTED to the caller as `lifecycleWritten: false` rather than folded
|
|
303
|
+
// into a record that reads as settled.
|
|
304
|
+
return false;
|
|
264
305
|
}
|
|
265
306
|
try {
|
|
266
307
|
input.onLifecycleStage?.(stage, record);
|
|
@@ -268,12 +309,14 @@ export function settleOpenLifecycleRun(input) {
|
|
|
268
309
|
catch {
|
|
269
310
|
// Observer failures are not ours to propagate.
|
|
270
311
|
}
|
|
312
|
+
return true;
|
|
271
313
|
};
|
|
272
314
|
// `verifying` = we hold a measurement and are checking it.
|
|
273
315
|
emit('verifying', false);
|
|
274
|
-
// `completed` = the measurement confirms the drop; publish it.
|
|
275
|
-
|
|
276
|
-
|
|
316
|
+
// `completed` = the measurement confirms the drop; publish it. This write
|
|
317
|
+
// is the one that closes the run, so it is the one that is reported.
|
|
318
|
+
const lifecycleWritten = emit('completed', true);
|
|
319
|
+
return { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio: input.measuredRatio, lifecycleWritten };
|
|
277
320
|
}
|
|
278
321
|
/**
|
|
279
322
|
* rid `2026-09-13-compact-event-settle`: close out an open compact run because
|
|
@@ -332,12 +375,16 @@ export function settleOpenLifecycleRun(input) {
|
|
|
332
375
|
* two apart ends up telling the user a falsehood (see `compact-event-settle.ts`).
|
|
333
376
|
*/
|
|
334
377
|
export function settleOpenLifecycleRunOnCompactEvent(input) {
|
|
335
|
-
const
|
|
378
|
+
const openRead = readOpenCompactLifecycle({
|
|
336
379
|
projectRoot: input.projectRoot,
|
|
337
380
|
sessionId: input.sessionId
|
|
338
381
|
});
|
|
339
|
-
|
|
382
|
+
// Same collapse as `settleOpenLifecycleRun` above, for the same reason:
|
|
383
|
+
// an unresolvable id names no session directory, so no open run exists
|
|
384
|
+
// for this event to be about, and `null` here admits nothing.
|
|
385
|
+
if (openRead.kind !== 'found')
|
|
340
386
|
return null;
|
|
387
|
+
const prior = openRead.record;
|
|
341
388
|
if (prior.stage !== 'compacting' && prior.stage !== 'armed')
|
|
342
389
|
return null;
|
|
343
390
|
const afterRatio = input.measuredRatio !== null && input.measuredRatio < prior.triggerRatio ? input.measuredRatio : null;
|
|
@@ -410,12 +457,14 @@ export function settleOpenLifecycleRunOnCompactEvent(input) {
|
|
|
410
457
|
export function fillEventSettledMeasurement(input) {
|
|
411
458
|
if (input.source === 'conservative-fallback')
|
|
412
459
|
return null;
|
|
413
|
-
const
|
|
460
|
+
const openRead = readOpenCompactLifecycle({
|
|
414
461
|
projectRoot: input.projectRoot,
|
|
415
462
|
sessionId: input.sessionId
|
|
416
463
|
});
|
|
417
|
-
|
|
464
|
+
// Same collapse as the two settle paths above — `null` here admits nothing.
|
|
465
|
+
if (openRead.kind !== 'found')
|
|
418
466
|
return null;
|
|
467
|
+
const prior = openRead.record;
|
|
419
468
|
// Exactly one shape is owed a number: the one the EVENT path leaves behind.
|
|
420
469
|
// `settleOpenLifecycleRun` never writes it (it always carries `afterRatio`),
|
|
421
470
|
// and a `failed` run never dispatched, so it has no row to pair with.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Slice 2026-07-28 — auto-compact mode table for 24h long-run awareness.
|
|
3
3
|
*
|
|
4
4
|
* The default `'standard'` mode preserves the v2.13.0 zero-pause contract
|
|
5
|
-
* thresholds (preCompact=0.85, redLine=0.95). The `'partial'`
|
|
6
|
-
* earlier for 24h long-run scenarios where the user has explicitly
|
|
5
|
+
* thresholds (autoFire=0.80, preCompact=0.85, redLine=0.95). The `'partial'`
|
|
6
|
+
* mode fires earlier for 24h long-run scenarios where the user has explicitly
|
|
7
7
|
* opted into higher compaction cadence via `peaks session 24h-mode`.
|
|
8
8
|
*
|
|
9
9
|
* Slice 2026-07-29-context-evaluation-accuracy Part 22: a new
|
|
@@ -26,5 +26,16 @@ export declare const AUTO_COMPACT_THRESHOLDS: Readonly<Record<AutoCompactMode, {
|
|
|
26
26
|
}>>;
|
|
27
27
|
export declare function thresholdFor(mode: AutoCompactMode, kind: 'autoFire' | 'preCompact' | 'redLine'): number;
|
|
28
28
|
export declare function isValidMode(value: string): value is AutoCompactMode;
|
|
29
|
+
/**
|
|
30
|
+
* Human-readable form of a mode's threshold row.
|
|
31
|
+
*
|
|
32
|
+
* Derived from `AUTO_COMPACT_THRESHOLDS` rather than hand-written. Diagnosis
|
|
33
|
+
* 2026-09-15 (C1) found this string advertising `0.85/0.95` for `standard`
|
|
34
|
+
* while the table had auto-fired at `0.80` since slice
|
|
35
|
+
* 2026-07-29-context-evaluation-accuracy added the `autoFire` tier — the
|
|
36
|
+
* description named only the two thresholds that were left over from the
|
|
37
|
+
* v2.13.0 contract, and skipped the one that actually fires. Reading the
|
|
38
|
+
* numbers out of the table makes that drift unrepresentable.
|
|
39
|
+
*/
|
|
29
40
|
export declare function describeMode(mode: AutoCompactMode): string;
|
|
30
41
|
export declare function isPartialModeEligible(contextPercent: number): boolean;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Slice 2026-07-28 — auto-compact mode table for 24h long-run awareness.
|
|
3
3
|
*
|
|
4
4
|
* The default `'standard'` mode preserves the v2.13.0 zero-pause contract
|
|
5
|
-
* thresholds (preCompact=0.85, redLine=0.95). The `'partial'`
|
|
6
|
-
* earlier for 24h long-run scenarios where the user has explicitly
|
|
5
|
+
* thresholds (autoFire=0.80, preCompact=0.85, redLine=0.95). The `'partial'`
|
|
6
|
+
* mode fires earlier for 24h long-run scenarios where the user has explicitly
|
|
7
7
|
* opted into higher compaction cadence via `peaks session 24h-mode`.
|
|
8
8
|
*
|
|
9
9
|
* Slice 2026-07-29-context-evaluation-accuracy Part 22: a new
|
|
@@ -28,10 +28,26 @@ export function thresholdFor(mode, kind) {
|
|
|
28
28
|
export function isValidMode(value) {
|
|
29
29
|
return value === 'standard' || value === 'partial';
|
|
30
30
|
}
|
|
31
|
+
function pct(ratio) {
|
|
32
|
+
return `${(ratio * 100).toFixed(0)}%`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Human-readable form of a mode's threshold row.
|
|
36
|
+
*
|
|
37
|
+
* Derived from `AUTO_COMPACT_THRESHOLDS` rather than hand-written. Diagnosis
|
|
38
|
+
* 2026-09-15 (C1) found this string advertising `0.85/0.95` for `standard`
|
|
39
|
+
* while the table had auto-fired at `0.80` since slice
|
|
40
|
+
* 2026-07-29-context-evaluation-accuracy added the `autoFire` tier — the
|
|
41
|
+
* description named only the two thresholds that were left over from the
|
|
42
|
+
* v2.13.0 contract, and skipped the one that actually fires. Reading the
|
|
43
|
+
* numbers out of the table makes that drift unrepresentable.
|
|
44
|
+
*/
|
|
31
45
|
export function describeMode(mode) {
|
|
46
|
+
const { autoFire, preCompact, redLine } = AUTO_COMPACT_THRESHOLDS[mode];
|
|
47
|
+
const bands = `auto-fire ${pct(autoFire)} / pre-compact ${pct(preCompact)} / red-line ${pct(redLine)}`;
|
|
32
48
|
return mode === 'standard'
|
|
33
|
-
?
|
|
34
|
-
:
|
|
49
|
+
? `standard (${bands} — v2.13.0 zero-pause contract)`
|
|
50
|
+
: `partial (${bands} — 24h long-run mode)`;
|
|
35
51
|
}
|
|
36
52
|
export function isPartialModeEligible(contextPercent) {
|
|
37
53
|
return contextPercent >= AUTO_COMPACT_THRESHOLDS.partial.preCompact;
|