peaks-loop 4.0.46 → 4.0.48
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 +54 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/agents/karpathy-reviewer.md +11 -10
- package/dist/cli/cli-helpers.d.ts +34 -0
- package/dist/cli/cli-helpers.js +57 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
- package/dist/cli/commands/code-runtime-commands.js +139 -16
- package/dist/cli/commands/compact-command.js +241 -1
- package/dist/cli/commands/config-commands.js +15 -9
- package/dist/cli/commands/container-commands.js +3 -3
- package/dist/cli/commands/core/skill-command.js +45 -10
- package/dist/cli/commands/cron-commands.js +2 -1
- package/dist/cli/commands/dashboard-long-run.js +6 -0
- package/dist/cli/commands/dispatch-commands.js +11 -1
- package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
- package/dist/cli/commands/e2e-verify.js +3 -3
- package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
- package/dist/cli/commands/hooks-commands.js +14 -5
- package/dist/cli/commands/job-commands.js +8 -0
- package/dist/cli/commands/loop-commands.js +1 -0
- package/dist/cli/commands/loop-eval-commands.js +15 -0
- package/dist/cli/commands/perf-audit-commands.js +2 -0
- package/dist/cli/commands/playwright-commands.js +14 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/qa-commands.js +22 -0
- package/dist/cli/commands/reinject-command.d.ts +72 -0
- package/dist/cli/commands/reinject-command.js +174 -0
- package/dist/cli/commands/request-commands.js +14 -3
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -0
- package/dist/cli/commands/shadcn-commands.js +1 -0
- package/dist/cli/commands/slice-integrate-commands.js +5 -0
- package/dist/cli/commands/statusline-commands.js +44 -4
- package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
- package/dist/cli/commands/sub-agent/detached.js +47 -22
- package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
- package/dist/cli/commands/test-commands.js +2 -1
- package/dist/cli/commands/verdict-aggregate-command.js +95 -13
- package/dist/cli/commands/vm-commands.js +7 -7
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +24 -2
- package/dist/cli/commands/worktree-lease-commands.js +4 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/program.js +5 -0
- package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
- package/dist/services/adapter/adapter-registry.js +1 -1
- package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
- package/dist/services/artifacts/artifact-prerequisites.js +130 -65
- package/dist/services/artifacts/artifact-service.js +1 -1
- package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
- package/dist/services/artifacts/request-artifact-service.js +18 -8
- package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
- package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
- package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
- package/dist/services/audit-independent/perf-audit-service.js +27 -5
- package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
- package/dist/services/audit-independent/security-audit-service.js +28 -6
- package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
- package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
- package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
- package/dist/services/code/auto-compact-lifecycle.d.ts +130 -1
- package/dist/services/code/auto-compact-lifecycle.js +180 -4
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +166 -34
- package/dist/services/code/compact-event-settle.d.ts +122 -0
- package/dist/services/code/compact-event-settle.js +219 -0
- package/dist/services/code/orchestrator-can-do.d.ts +4 -2
- package/dist/services/code/orchestrator-can-do.js +37 -5
- package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
- package/dist/services/codegraph/codegraph-process-runner.js +3 -2
- package/dist/services/compact/request-transition-hook.js +5 -2
- package/dist/services/compact-history/compact-history-service.d.ts +75 -0
- package/dist/services/compact-history/compact-history-service.js +49 -0
- package/dist/services/config/config-restore.d.ts +12 -1
- package/dist/services/config/config-restore.js +35 -4
- package/dist/services/config/config-rollback.js +6 -1
- package/dist/services/config/config-safety.d.ts +52 -0
- package/dist/services/config/config-safety.js +75 -1
- package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
- package/dist/services/context/auto-compact-dispatcher.js +113 -40
- package/dist/services/context/auto-compact-reader.d.ts +68 -28
- package/dist/services/context/auto-compact-reader.js +155 -1
- package/dist/services/context/auto-compact-types.d.ts +89 -12
- package/dist/services/context/auto-compact-types.js +16 -32
- package/dist/services/context/harness-context-witness.d.ts +310 -0
- package/dist/services/context/harness-context-witness.js +606 -0
- package/dist/services/context/harness-window-config.d.ts +412 -0
- package/dist/services/context/harness-window-config.js +607 -0
- package/dist/services/context/main-session-monitor.d.ts +27 -0
- package/dist/services/context/main-session-monitor.js +32 -1
- package/dist/services/context/post-compact-reinjection.d.ts +221 -0
- package/dist/services/context/post-compact-reinjection.js +491 -0
- package/dist/services/dispatch/merge-back-runner.js +5 -5
- package/dist/services/dispatch/service-shutdown.js +3 -3
- package/dist/services/doc/doc-generator.js +2 -1
- package/dist/services/env/shell-probe.js +1 -1
- package/dist/services/evidence/evidence-generator.js +86 -49
- package/dist/services/final-review/final-review-service.d.ts +9 -0
- package/dist/services/final-review/final-review-service.js +36 -12
- package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
- package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
- package/dist/services/hooks/auto-compact-hook-install.js +8 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
- package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
- package/dist/services/ide/ide-registry.d.ts +31 -0
- package/dist/services/ide/ide-registry.js +35 -0
- package/dist/services/ide/ide-types.d.ts +59 -0
- package/dist/services/job/job-state-store.js +7 -0
- package/dist/services/lint/detect-eslint.js +2 -2
- package/dist/services/lint/eslint-runner.js +3 -1
- package/dist/services/loop/evaluator-dispatcher.js +2 -1
- package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
- package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
- package/dist/services/memory/project-memory-service/store/paths.js +15 -6
- package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
- package/dist/services/prd/best-practice-auto-trigger.js +1 -0
- package/dist/services/prd/handoff-auto-regen.js +31 -27
- package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
- package/dist/services/prd/handoff-frontmatter.js +75 -0
- package/dist/services/prd/handoff-service.d.ts +41 -2
- package/dist/services/prd/handoff-service.js +81 -8
- package/dist/services/prd/handoff-types.d.ts +3 -2
- package/dist/services/prd/handoff-types.js +3 -2
- package/dist/services/qa/qa-business-review-state.js +9 -0
- package/dist/services/release/version-precheck-service.d.ts +2 -1
- package/dist/services/release/version-precheck-service.js +82 -12
- package/dist/services/runtime/vendor-adapter.d.ts +29 -4
- package/dist/services/runtime/vendors/claude-code.js +1 -1
- package/dist/services/runtime/vendors/codex.js +1 -1
- package/dist/services/runtime/vendors/copilot.js +1 -1
- package/dist/services/sc/sc-service.js +1 -1
- package/dist/services/scan/diff-scope-service.js +2 -2
- package/dist/services/scan/file-size-scan.js +2 -2
- package/dist/services/scan/karpathy-service.js +2 -2
- package/dist/services/scan/orphan-service.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -2
- package/dist/services/session/session-checkpoint-service.js +8 -0
- package/dist/services/skill/resume-detector.js +29 -11
- package/dist/services/skillhub/tar-runtime.js +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +14 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +99 -3
- package/dist/services/skills/hooks-settings-service.d.ts +12 -0
- package/dist/services/skills/hooks-settings-service.js +91 -14
- package/dist/services/skills/session-start-hook-constants.d.ts +86 -0
- package/dist/services/skills/session-start-hook-constants.js +86 -0
- package/dist/services/skills/skill-presence-service.js +9 -0
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +31 -12
- package/dist/services/slice/slice-decompose-runners.js +2 -1
- package/dist/services/slice/slice-review-state.js +8 -0
- package/dist/services/upgrade/upgrade-service.js +1 -0
- package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
- package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
- package/dist/services/workflow/pipeline-verify-service.js +24 -23
- package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
- package/dist/services/workflow/workflow-skip-service.js +2 -1
- package/dist/services/workspace/claude-settings-template.d.ts +56 -8
- package/dist/services/workspace/claude-settings-template.js +98 -20
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-claude-settings-materializer.js +124 -9
- package/dist/services/workspace/workspace-service.js +8 -0
- package/dist/services/worktree/host-worktree-reconciler.js +1 -0
- package/dist/services/worktree/long-path-cleanup.js +3 -2
- package/dist/shared/process.js +1 -1
- package/package.json +6 -6
- package/scripts/install-skills.mjs +1 -0
- package/scripts/watch.mjs +3 -1
- package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +8 -6
- package/skills/bee/peaks-qa/SKILL.md +7 -7
- package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
- package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
- package/skills/bee/peaks-rd/SKILL.md +10 -8
- package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
- package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.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 +3 -3
- package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
- package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +13 -7
- package/skills/peaks-code/references/runbook.md +3 -2
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
- package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
- package/skills/peaks-content/SKILL.md +1 -1
- package/skills/peaks-doctor/SKILL.md +1 -1
- package/skills/peaks-final-review/SKILL.md +1 -1
- package/skills/peaks-ide/SKILL.md +1 -1
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +1 -1
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-sop/SKILL.md +1 -1
- package/skills/peaks-status/SKILL.md +1 -1
- package/skills/peaks-test/SKILL.md +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* The service owns:
|
|
9
9
|
* - 5-state detection of the security-audit runtime
|
|
10
10
|
* (handoff-missing / template-missing / dispatch-failed / template-malformed / ready)
|
|
11
|
-
* - Loading + sha256 verification of the prd/handoff
|
|
11
|
+
* - Loading + sha256 verification of the slice's prd/handoff-<rid>.md
|
|
12
12
|
* - Loading the project-level security-template.md
|
|
13
13
|
* - Producing the audit envelope (verdict + violations) to write
|
|
14
14
|
* to `.peaks/_runtime/<sid>/audit/security-<rid>.md`
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
* 5-state detection result. Mirrors `detectEcc` in `services/code-review/ecc-bridge.ts`.
|
|
37
37
|
*
|
|
38
38
|
* - `ready` — handoff + template + project all present
|
|
39
|
-
* - `handoff-missing` — `.peaks/_runtime/<sid>/prd/handoff
|
|
39
|
+
* - `handoff-missing` — this slice's `.peaks/_runtime/<sid>/prd/handoff-<rid>.md`
|
|
40
|
+
* (or the pre-rid-scoping `prd/handoff.md`) absent
|
|
40
41
|
* - `template-missing` — `.peaks/project-scan/security-template.md` absent
|
|
41
42
|
* - `dispatch-failed` — parent LLM threw before returning the audit envelope
|
|
42
43
|
* - `envelope-malformed` — parent LLM returned a value that fails `isSecurityAuditEnvelope`
|
|
@@ -107,6 +108,15 @@ export declare function readSecurityTemplate(projectRoot: string): string | null
|
|
|
107
108
|
export declare function detectSecurityAudit(input: {
|
|
108
109
|
readonly projectRoot: string;
|
|
109
110
|
readonly sessionId: string;
|
|
111
|
+
/**
|
|
112
|
+
* The slice whose capsule this run audits. Slice
|
|
113
|
+
* `2026-09-14-prd-capsule-rid-scoping` put the rid in the capsule's
|
|
114
|
+
* filename, so a caller that knows it must pass it or the probe resolves
|
|
115
|
+
* only the pre-rid-scoping bare name. Both callers pass it:
|
|
116
|
+
* `runSecurityAudit` always did, and `peaks security-audit detect` forwards
|
|
117
|
+
* its long-standing `--rid` flag as of the post-verification repair round.
|
|
118
|
+
*/
|
|
119
|
+
readonly requestId?: string;
|
|
110
120
|
readonly dispatchError?: unknown;
|
|
111
121
|
readonly envelope?: unknown;
|
|
112
122
|
}): SecurityAuditDetectResult;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* The service owns:
|
|
9
9
|
* - 5-state detection of the security-audit runtime
|
|
10
10
|
* (handoff-missing / template-missing / dispatch-failed / template-malformed / ready)
|
|
11
|
-
* - Loading + sha256 verification of the prd/handoff
|
|
11
|
+
* - Loading + sha256 verification of the slice's prd/handoff-<rid>.md
|
|
12
12
|
* - Loading the project-level security-template.md
|
|
13
13
|
* - Producing the audit envelope (verdict + violations) to write
|
|
14
14
|
* to `.peaks/_runtime/<sid>/audit/security-<rid>.md`
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
36
36
|
import { join, resolve, isAbsolute } from 'node:path';
|
|
37
37
|
import { createHash } from 'node:crypto';
|
|
38
|
+
import { REQUEST_ID_PATTERN } from '../artifacts/request-artifact-service.js';
|
|
39
|
+
import { resolveHandoffPath } from '../prd/handoff-service.js';
|
|
38
40
|
/**
|
|
39
41
|
* Validate a raw value as a SecurityAuditEnvelope. Mirrors the
|
|
40
42
|
* `isEccEnvelope` strict-shape pattern.
|
|
@@ -145,8 +147,12 @@ export function readSecurityTemplate(projectRoot) {
|
|
|
145
147
|
export function detectSecurityAudit(input) {
|
|
146
148
|
const warnings = [];
|
|
147
149
|
const nextActions = [];
|
|
148
|
-
const handoffPath =
|
|
149
|
-
|
|
150
|
+
const handoffPath = resolveHandoffPath({
|
|
151
|
+
projectRoot: input.projectRoot,
|
|
152
|
+
sessionId: input.sessionId,
|
|
153
|
+
...(input.requestId !== undefined ? { requestId: input.requestId } : {})
|
|
154
|
+
});
|
|
155
|
+
const handoffPresent = handoffPath !== null;
|
|
150
156
|
const templatePath = join(input.projectRoot, '.peaks', 'project-scan', 'security-template.md');
|
|
151
157
|
const templatePresent = existsSync(templatePath);
|
|
152
158
|
if (!handoffPresent) {
|
|
@@ -154,7 +160,12 @@ export function detectSecurityAudit(input) {
|
|
|
154
160
|
state: 'handoff-missing',
|
|
155
161
|
handoffPresent: false,
|
|
156
162
|
templatePresent,
|
|
157
|
-
warnings: [
|
|
163
|
+
warnings: [
|
|
164
|
+
`peaks-prd handoff not found under ${join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'prd')}`,
|
|
165
|
+
...(input.requestId === undefined
|
|
166
|
+
? ['No --rid was supplied, so only the pre-rid-scoping `prd/handoff.md` could be probed. Pass --rid to resolve this slice\'s `prd/handoff-<rid>.md`.']
|
|
167
|
+
: [])
|
|
168
|
+
],
|
|
158
169
|
nextActions: [
|
|
159
170
|
'Run peaks-prd handoff init to produce a sha256-locked handoff before running peaks security-audit.',
|
|
160
171
|
'Until the handoff exists, peaks-security-audit cannot start (gate fail).'
|
|
@@ -268,6 +279,12 @@ export function renderSecurityAuditArtifact(env, opts) {
|
|
|
268
279
|
* Returns the absolute path on success.
|
|
269
280
|
*/
|
|
270
281
|
export function writeSecurityAuditArtifact(projectRoot, sessionId, rid, body) {
|
|
282
|
+
// The rid is a filename below, and the write is tmp+rename, so an
|
|
283
|
+
// unvalidated rid can OVERWRITE an arbitrary `.md` rather than merely create
|
|
284
|
+
// one. Guarded here (not only at the CLI boundary) so no caller can skip it.
|
|
285
|
+
if (!REQUEST_ID_PATTERN.test(rid)) {
|
|
286
|
+
throw new Error(`Invalid request id: ${rid} (expected letters, digits, dots, underscores, or dashes)`);
|
|
287
|
+
}
|
|
271
288
|
const targetDir = join(projectRoot, '.peaks', '_runtime', sessionId, 'audit');
|
|
272
289
|
mkdirSync(targetDir, { recursive: true });
|
|
273
290
|
const targetPath = join(targetDir, `security-${rid}.md`);
|
|
@@ -290,6 +307,7 @@ export function runSecurityAudit(input) {
|
|
|
290
307
|
const detect = detectSecurityAudit({
|
|
291
308
|
projectRoot: input.projectRoot,
|
|
292
309
|
sessionId: input.sessionId,
|
|
310
|
+
requestId: input.rid,
|
|
293
311
|
...(input.dispatchError !== undefined ? { dispatchError: input.dispatchError } : {}),
|
|
294
312
|
...(input.envelope !== undefined ? { envelope: input.envelope } : {})
|
|
295
313
|
});
|
|
@@ -298,8 +316,12 @@ export function runSecurityAudit(input) {
|
|
|
298
316
|
}
|
|
299
317
|
// detect.state === 'ready' implies input.envelope passed isSecurityAuditEnvelope.
|
|
300
318
|
const env = input.envelope;
|
|
301
|
-
const handoffPath =
|
|
302
|
-
|
|
319
|
+
const handoffPath = resolveHandoffPath({
|
|
320
|
+
projectRoot: input.projectRoot,
|
|
321
|
+
sessionId: input.sessionId,
|
|
322
|
+
requestId: input.rid
|
|
323
|
+
});
|
|
324
|
+
const verified = handoffPath === null ? null : readAndVerifyHandoff(handoffPath, input.projectRoot);
|
|
303
325
|
const handoffHash = verified?.frontmatter.sha256 ?? 'unknown';
|
|
304
326
|
const rendered = renderSecurityAuditArtifact(env, {
|
|
305
327
|
rid: input.rid,
|
|
@@ -16,7 +16,8 @@ export async function runJ01Contract(ctx) {
|
|
|
16
16
|
try {
|
|
17
17
|
const stdout = execFileSync('node', [bin, command, input], {
|
|
18
18
|
cwd: ctx.projectRoot,
|
|
19
|
-
env: { ...process.env, PEAKS_CALLER_ID: `guard-J01-${ctx.sessionId}` }
|
|
19
|
+
env: { ...process.env, PEAKS_CALLER_ID: `guard-J01-${ctx.sessionId}` },
|
|
20
|
+
windowsHide: true
|
|
20
21
|
}).toString('utf8');
|
|
21
22
|
const env = JSON.parse(stdout);
|
|
22
23
|
if (!env.ok) {
|
|
@@ -6,10 +6,10 @@ const STATES = ['spec-locked', 'implemented', 'qa-handoff', 'handed-off'];
|
|
|
6
6
|
export async function runJ02Contract(ctx, projectRoot = ctx.projectRoot) {
|
|
7
7
|
const bin = join(ctx.projectRoot, 'bin', 'peaks.js');
|
|
8
8
|
const tmp = mkdtempSync(join(tmpdir(), 'cbl-J02-'));
|
|
9
|
-
const ws = execFileSync('node', [bin, 'workspace', 'init', '--project', tmp, '--json'], { cwd: tmp }).toString('utf8');
|
|
9
|
+
const ws = execFileSync('node', [bin, 'workspace', 'init', '--project', tmp, '--json'], { cwd: tmp, windowsHide: true }).toString('utf8');
|
|
10
10
|
const { data: { sessionId } } = JSON.parse(ws);
|
|
11
11
|
const rid = '2026-08-03-j02-fixture';
|
|
12
|
-
const initOut = execFileSync('node', [bin, 'request', 'init', '--role', 'rd', '--id', rid, '--project', tmp, '--session-id', sessionId, '--apply', '--json'], { cwd: tmp }).toString('utf8');
|
|
12
|
+
const initOut = execFileSync('node', [bin, 'request', 'init', '--role', 'rd', '--id', rid, '--project', tmp, '--session-id', sessionId, '--apply', '--json'], { cwd: tmp, windowsHide: true }).toString('utf8');
|
|
13
13
|
const initEnv = JSON.parse(initOut);
|
|
14
14
|
// `request init` writes the file as `NNN-<id-slug>.md`. The transition CLI accepts
|
|
15
15
|
// the file's basename (without .md) as the requestId. Derive it from data.path.
|
|
@@ -19,7 +19,7 @@ export async function runJ02Contract(ctx, projectRoot = ctx.projectRoot) {
|
|
|
19
19
|
for (const s of STATES) {
|
|
20
20
|
const out = execFileSync('node', [bin, 'request', 'transition', requestId, '--role', 'rd', '--state', s,
|
|
21
21
|
'--project', tmp, '--session-id', sessionId, '--confirm', '--allow-incomplete',
|
|
22
|
-
'--reason', 'J02 contract fixture', '--json'], { cwd: tmp }).toString('utf8');
|
|
22
|
+
'--reason', 'J02 contract fixture', '--json'], { cwd: tmp, windowsHide: true }).toString('utf8');
|
|
23
23
|
const env = JSON.parse(out);
|
|
24
24
|
last = env.data.state;
|
|
25
25
|
}
|
|
@@ -10,7 +10,8 @@ export async function runJ04Contract(ctx) {
|
|
|
10
10
|
try {
|
|
11
11
|
const stdout = execFileSync('node', [bin, 'audit', 'goal', '--help'], {
|
|
12
12
|
cwd: ctx.projectRoot,
|
|
13
|
-
env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` }
|
|
13
|
+
env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` },
|
|
14
|
+
windowsHide: true
|
|
14
15
|
}).toString('utf8');
|
|
15
16
|
helpOk = stdout.length > 0 && !stdout.includes('unknown command');
|
|
16
17
|
}
|
|
@@ -23,7 +24,8 @@ export async function runJ04Contract(ctx) {
|
|
|
23
24
|
execFileSync('node', [bin, 'audit', 'goal', '--project', tmp, '--json'], {
|
|
24
25
|
cwd: ctx.projectRoot,
|
|
25
26
|
env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` },
|
|
26
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
27
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
28
|
+
windowsHide: true
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
catch (e) {
|
|
@@ -19,7 +19,8 @@ export async function runJ07Contract(ctx) {
|
|
|
19
19
|
const out = execFileSync('node', [bin, 'test', '--help'], {
|
|
20
20
|
cwd: ctx.projectRoot,
|
|
21
21
|
env: { ...process.env, PEAKS_CALLER_ID: `guard-J07-${ctx.sessionId}` },
|
|
22
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
22
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
23
|
+
windowsHide: true
|
|
23
24
|
}).toString('utf8');
|
|
24
25
|
helpOk = out.length > 0 && !out.includes('unknown command');
|
|
25
26
|
}
|
|
@@ -126,6 +126,12 @@ export declare function summarizeLifecycleError(error: unknown): string;
|
|
|
126
126
|
* `afterRatio: 0` there would publish a fabricated number;
|
|
127
127
|
* - the ratio is still at or above the auto-fire threshold — the
|
|
128
128
|
* compact has not landed, so the run stays open.
|
|
129
|
+
*
|
|
130
|
+
* Returns the settled record, or `null` when nothing settled. Slice
|
|
131
|
+
* 2026-09-13-auto-compact-trigger-ownership: the caller needs the settled
|
|
132
|
+
* `afterRatio` to append the "observed compaction point" row that makes the
|
|
133
|
+
* intent-vs-observed gap readable after a real session. The return value is
|
|
134
|
+
* telemetry only — callers that ignore it are unaffected.
|
|
129
135
|
*/
|
|
130
136
|
export declare function settleOpenLifecycleRun(input: {
|
|
131
137
|
readonly projectRoot: string;
|
|
@@ -134,5 +140,128 @@ export declare function settleOpenLifecycleRun(input: {
|
|
|
134
140
|
readonly source: string;
|
|
135
141
|
readonly autoFireThreshold: number;
|
|
136
142
|
readonly onLifecycleStage?: ((stage: CompactLifecycleStage, record: CompactLifecycleRecord) => void) | undefined;
|
|
137
|
-
}):
|
|
143
|
+
}): {
|
|
144
|
+
readonly runId: string;
|
|
145
|
+
readonly triggerRatio: number;
|
|
146
|
+
readonly afterRatio: number;
|
|
147
|
+
} | null;
|
|
148
|
+
/**
|
|
149
|
+
* rid `2026-09-13-compact-event-settle`: close out an open compact run because
|
|
150
|
+
* the HARNESS said one completed — `PostCompact` — rather than because a later
|
|
151
|
+
* probe noticed the ratio had fallen.
|
|
152
|
+
*
|
|
153
|
+
* WHY THIS IS A SECOND FUNCTION AND NOT A FLAG ON THE ONE ABOVE. The function
|
|
154
|
+
* above is defined by two MEASUREMENT gates: it refuses when nothing could be
|
|
155
|
+
* measured, and refuses when the number it got has not dropped far enough. Both
|
|
156
|
+
* are correct for a probe, whose ratio is an INFERENCE about whether something
|
|
157
|
+
* happened. Handed a harness event, both are wrong in the same direction —
|
|
158
|
+
* the harness has already stated that the compaction happened, so a probe that
|
|
159
|
+
* could not measure, or measured something larger, contradicts nothing. The
|
|
160
|
+
* event is the evidence; the ratio is a consequence.
|
|
161
|
+
*
|
|
162
|
+
* What survives from the probe path is the ATTRIBUTION gate, and only that:
|
|
163
|
+
* there must be an open run (`compacting` / `armed`) for this event to be
|
|
164
|
+
* about. A `PostCompact` on a session where peaks-loop never dispatched has
|
|
165
|
+
* nothing to settle — objectively, the run the event would complete does not
|
|
166
|
+
* exist. (`queued` / `preparing` are excluded for the probe path's reason: a
|
|
167
|
+
* run that died before dispatch never had a compaction to complete.)
|
|
168
|
+
*
|
|
169
|
+
* `afterRatio` is recorded ONLY when it is a genuine DROP below the ratio the
|
|
170
|
+
* dispatch was made at. Immediately after a compaction, `readContextPercent`
|
|
171
|
+
* prefers the statusline file, which may still hold the PRE-compact value; the
|
|
172
|
+
* one thing this row must not do is launder that stale reading into an
|
|
173
|
+
* `afterRatio` and publish "the context did not shrink" as a measurement. A
|
|
174
|
+
* `null` here means "no honest post-compact number was available at the moment
|
|
175
|
+
* the event fired" — and that is NOT self-healing: the record is left at
|
|
176
|
+
* `completed` with no number, `computeWindowCalibration` skips `observed` rows
|
|
177
|
+
* that carry none, and the probe path refuses a run that is no longer open. The
|
|
178
|
+
* pair is then closed by `fillEventSettledMeasurement` below — but only on the
|
|
179
|
+
* probes that reach it, which is not all of them: that call sits in the
|
|
180
|
+
* BELOW-THRESHOLD branch of `runAutoCompact` (`auto-compact-orchestrator.ts:567`
|
|
181
|
+
* guards it, `:589` calls it). A probe that instead commits to compacting does
|
|
182
|
+
* not merely defer the measurement: `advance('queued')` writes a fresh run to
|
|
183
|
+
* the same one-record-per-session store (`auto-compact-orchestrator.ts:668`), so
|
|
184
|
+
* the `completed`-without-`afterRatio` record this pair was owed is gone and the
|
|
185
|
+
* pair stays unmeasured. That loss is inherent rather than an oversight — once a
|
|
186
|
+
* second compaction has happened, no later ratio can be attributed to the first,
|
|
187
|
+
* so there is nothing honest left to fill — and it is visible as `unmeasured` in
|
|
188
|
+
* `peaks compact history` (QA residual R9). A fabricated number is not
|
|
189
|
+
* recoverable at all, which is why the stale reading is dropped rather than
|
|
190
|
+
* corrected.
|
|
191
|
+
*
|
|
192
|
+
* `verifying` is deliberately NOT emitted: its documented meaning is "we hold a
|
|
193
|
+
* measurement and are checking it", and on this path there may be no
|
|
194
|
+
* measurement at all. Emitting it would move the same untruth from the history
|
|
195
|
+
* row into the lifecycle record.
|
|
196
|
+
*
|
|
197
|
+
* Returns the settled facts, or `null` when there was nothing to settle. `null`
|
|
198
|
+
* means exactly ONE thing here — there was no OPEN run for this event to be
|
|
199
|
+
* about. A run that was open but whose record could not be written is not
|
|
200
|
+
* `null`: it returns the facts read off that run with `lifecycleWritten: false`,
|
|
201
|
+
* because a failed write is not an absent run, and a caller that cannot tell the
|
|
202
|
+
* two apart ends up telling the user a falsehood (see `compact-event-settle.ts`).
|
|
203
|
+
*/
|
|
204
|
+
export declare function settleOpenLifecycleRunOnCompactEvent(input: {
|
|
205
|
+
readonly projectRoot: string;
|
|
206
|
+
readonly sessionId: string;
|
|
207
|
+
readonly measuredRatio: number | null;
|
|
208
|
+
readonly onLifecycleStage?: ((stage: CompactLifecycleStage, record: CompactLifecycleRecord) => void) | undefined;
|
|
209
|
+
/** Failure injection for the write below — the seam `CompactLifecyclePublisher` already takes. */
|
|
210
|
+
readonly failLifecycleWrite?: boolean | undefined;
|
|
211
|
+
}): {
|
|
212
|
+
readonly runId: string;
|
|
213
|
+
readonly triggerRatio: number;
|
|
214
|
+
readonly afterRatio: number | null;
|
|
215
|
+
/**
|
|
216
|
+
* `false` when the run could not be marked settled — `writeCompactLifecycle`
|
|
217
|
+
* threw. The three facts above were read off the OPEN run, so they stay true
|
|
218
|
+
* and a caller may still record the observation; what it must not do is report
|
|
219
|
+
* the run as settled. `settleOpenLifecycleRun` above does not suppress its
|
|
220
|
+
* returned record on a failed write either, so the two now agree that a write
|
|
221
|
+
* failure is not "nothing happened". This field exists because this function's
|
|
222
|
+
* caller, unlike the sibling's, renders a sentence about the outcome.
|
|
223
|
+
*/
|
|
224
|
+
readonly lifecycleWritten: boolean;
|
|
225
|
+
} | null;
|
|
226
|
+
/**
|
|
227
|
+
* rid `2026-09-13-compact-event-settle` (repair R1): supply the measurement a
|
|
228
|
+
* harness-settled run was left owing.
|
|
229
|
+
*
|
|
230
|
+
* The function above deliberately refuses to launder a post-compact reading
|
|
231
|
+
* that has not dropped — and right after a compaction that refusal is the
|
|
232
|
+
* NORMAL case, because the statusline still holds the pre-compact value. The
|
|
233
|
+
* run is then closed at `completed` with no `afterRatio`, so the dispatch's
|
|
234
|
+
* calibration pair never closes and "intent vs observed" stays blank for
|
|
235
|
+
* exactly the compactions this slice exists to witness. This function is what
|
|
236
|
+
* makes the function above's promise payable.
|
|
237
|
+
*
|
|
238
|
+
* WHY NOT WIDEN `settleOpenLifecycleRun`. That one re-emits `verifying` before
|
|
239
|
+
* `completed`, which on an already-`completed` record is a backwards stage
|
|
240
|
+
* transition with no observer to serve. This is not a settlement — the run IS
|
|
241
|
+
* settled; only the number is owed. So no stage is rewritten here.
|
|
242
|
+
*
|
|
243
|
+
* WRITING `afterRatio` ONTO THE RECORD IS THE IDEMPOTENCE TOKEN: every later
|
|
244
|
+
* probe finds it present and returns `null`, so however many probes follow, one
|
|
245
|
+
* compaction yields exactly one late measurement.
|
|
246
|
+
*
|
|
247
|
+
* THE DROP GATE IS THE EVENT PATH'S OWN (`measuredRatio < triggerRatio`), not
|
|
248
|
+
* the probe path's `autoFireThreshold`. `afterRatio` has to mean "below the
|
|
249
|
+
* ratio this run was dispatched at" — the rule the event path already enforces
|
|
250
|
+
* — or a run dispatched under the threshold (a forced or banded dispatch) would
|
|
251
|
+
* let a NON-drop through the one path that can still write a `completed` record.
|
|
252
|
+
* `conservative-fallback` is refused for the probe path's reason: its `0` is
|
|
253
|
+
* the absence of a measurement, not an empty context.
|
|
254
|
+
*
|
|
255
|
+
* Returns the filled record, or `null` when no run is owed a measurement.
|
|
256
|
+
*/
|
|
257
|
+
export declare function fillEventSettledMeasurement(input: {
|
|
258
|
+
readonly projectRoot: string;
|
|
259
|
+
readonly sessionId: string;
|
|
260
|
+
readonly measuredRatio: number;
|
|
261
|
+
readonly source: string;
|
|
262
|
+
}): {
|
|
263
|
+
readonly runId: string;
|
|
264
|
+
readonly triggerRatio: number;
|
|
265
|
+
readonly afterRatio: number;
|
|
266
|
+
} | null;
|
|
138
267
|
export {};
|
|
@@ -214,28 +214,34 @@ export function summarizeLifecycleError(error) {
|
|
|
214
214
|
* `afterRatio: 0` there would publish a fabricated number;
|
|
215
215
|
* - the ratio is still at or above the auto-fire threshold — the
|
|
216
216
|
* compact has not landed, so the run stays open.
|
|
217
|
+
*
|
|
218
|
+
* Returns the settled record, or `null` when nothing settled. Slice
|
|
219
|
+
* 2026-09-13-auto-compact-trigger-ownership: the caller needs the settled
|
|
220
|
+
* `afterRatio` to append the "observed compaction point" row that makes the
|
|
221
|
+
* intent-vs-observed gap readable after a real session. The return value is
|
|
222
|
+
* telemetry only — callers that ignore it are unaffected.
|
|
217
223
|
*/
|
|
218
224
|
export function settleOpenLifecycleRun(input) {
|
|
219
225
|
// A `conservative-fallback` probe means no signal was available at
|
|
220
226
|
// all. Its `ratio: 0` is the absence of a measurement, so it can
|
|
221
227
|
// never be evidence that the context shrank.
|
|
222
228
|
if (input.source === 'conservative-fallback')
|
|
223
|
-
return;
|
|
229
|
+
return null;
|
|
224
230
|
if (input.measuredRatio >= input.autoFireThreshold)
|
|
225
|
-
return;
|
|
231
|
+
return null;
|
|
226
232
|
const prior = readOpenCompactLifecycle({
|
|
227
233
|
projectRoot: input.projectRoot,
|
|
228
234
|
sessionId: input.sessionId
|
|
229
235
|
});
|
|
230
236
|
if (prior === null)
|
|
231
|
-
return;
|
|
237
|
+
return null;
|
|
232
238
|
// Only a run that was actually dispatched can be completed by a
|
|
233
239
|
// post-compact measurement. Both `compacting` (the in-band trigger is
|
|
234
240
|
// satisfied) and `armed` (a trigger was registered and could fire at
|
|
235
241
|
// any time) qualify: the measured drop is proof that SOME compact
|
|
236
242
|
// landed, and this is the only open run to attribute it to.
|
|
237
243
|
if (prior.stage !== 'compacting' && prior.stage !== 'armed')
|
|
238
|
-
return;
|
|
244
|
+
return null;
|
|
239
245
|
const emit = (stage, withAfterRatio) => {
|
|
240
246
|
const record = {
|
|
241
247
|
schemaVersion: 1,
|
|
@@ -267,4 +273,174 @@ export function settleOpenLifecycleRun(input) {
|
|
|
267
273
|
emit('verifying', false);
|
|
268
274
|
// `completed` = the measurement confirms the drop; publish it.
|
|
269
275
|
emit('completed', true);
|
|
276
|
+
return { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio: input.measuredRatio };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* rid `2026-09-13-compact-event-settle`: close out an open compact run because
|
|
280
|
+
* the HARNESS said one completed — `PostCompact` — rather than because a later
|
|
281
|
+
* probe noticed the ratio had fallen.
|
|
282
|
+
*
|
|
283
|
+
* WHY THIS IS A SECOND FUNCTION AND NOT A FLAG ON THE ONE ABOVE. The function
|
|
284
|
+
* above is defined by two MEASUREMENT gates: it refuses when nothing could be
|
|
285
|
+
* measured, and refuses when the number it got has not dropped far enough. Both
|
|
286
|
+
* are correct for a probe, whose ratio is an INFERENCE about whether something
|
|
287
|
+
* happened. Handed a harness event, both are wrong in the same direction —
|
|
288
|
+
* the harness has already stated that the compaction happened, so a probe that
|
|
289
|
+
* could not measure, or measured something larger, contradicts nothing. The
|
|
290
|
+
* event is the evidence; the ratio is a consequence.
|
|
291
|
+
*
|
|
292
|
+
* What survives from the probe path is the ATTRIBUTION gate, and only that:
|
|
293
|
+
* there must be an open run (`compacting` / `armed`) for this event to be
|
|
294
|
+
* about. A `PostCompact` on a session where peaks-loop never dispatched has
|
|
295
|
+
* nothing to settle — objectively, the run the event would complete does not
|
|
296
|
+
* exist. (`queued` / `preparing` are excluded for the probe path's reason: a
|
|
297
|
+
* run that died before dispatch never had a compaction to complete.)
|
|
298
|
+
*
|
|
299
|
+
* `afterRatio` is recorded ONLY when it is a genuine DROP below the ratio the
|
|
300
|
+
* dispatch was made at. Immediately after a compaction, `readContextPercent`
|
|
301
|
+
* prefers the statusline file, which may still hold the PRE-compact value; the
|
|
302
|
+
* one thing this row must not do is launder that stale reading into an
|
|
303
|
+
* `afterRatio` and publish "the context did not shrink" as a measurement. A
|
|
304
|
+
* `null` here means "no honest post-compact number was available at the moment
|
|
305
|
+
* the event fired" — and that is NOT self-healing: the record is left at
|
|
306
|
+
* `completed` with no number, `computeWindowCalibration` skips `observed` rows
|
|
307
|
+
* that carry none, and the probe path refuses a run that is no longer open. The
|
|
308
|
+
* pair is then closed by `fillEventSettledMeasurement` below — but only on the
|
|
309
|
+
* probes that reach it, which is not all of them: that call sits in the
|
|
310
|
+
* BELOW-THRESHOLD branch of `runAutoCompact` (`auto-compact-orchestrator.ts:567`
|
|
311
|
+
* guards it, `:589` calls it). A probe that instead commits to compacting does
|
|
312
|
+
* not merely defer the measurement: `advance('queued')` writes a fresh run to
|
|
313
|
+
* the same one-record-per-session store (`auto-compact-orchestrator.ts:668`), so
|
|
314
|
+
* the `completed`-without-`afterRatio` record this pair was owed is gone and the
|
|
315
|
+
* pair stays unmeasured. That loss is inherent rather than an oversight — once a
|
|
316
|
+
* second compaction has happened, no later ratio can be attributed to the first,
|
|
317
|
+
* so there is nothing honest left to fill — and it is visible as `unmeasured` in
|
|
318
|
+
* `peaks compact history` (QA residual R9). A fabricated number is not
|
|
319
|
+
* recoverable at all, which is why the stale reading is dropped rather than
|
|
320
|
+
* corrected.
|
|
321
|
+
*
|
|
322
|
+
* `verifying` is deliberately NOT emitted: its documented meaning is "we hold a
|
|
323
|
+
* measurement and are checking it", and on this path there may be no
|
|
324
|
+
* measurement at all. Emitting it would move the same untruth from the history
|
|
325
|
+
* row into the lifecycle record.
|
|
326
|
+
*
|
|
327
|
+
* Returns the settled facts, or `null` when there was nothing to settle. `null`
|
|
328
|
+
* means exactly ONE thing here — there was no OPEN run for this event to be
|
|
329
|
+
* about. A run that was open but whose record could not be written is not
|
|
330
|
+
* `null`: it returns the facts read off that run with `lifecycleWritten: false`,
|
|
331
|
+
* because a failed write is not an absent run, and a caller that cannot tell the
|
|
332
|
+
* two apart ends up telling the user a falsehood (see `compact-event-settle.ts`).
|
|
333
|
+
*/
|
|
334
|
+
export function settleOpenLifecycleRunOnCompactEvent(input) {
|
|
335
|
+
const prior = readOpenCompactLifecycle({
|
|
336
|
+
projectRoot: input.projectRoot,
|
|
337
|
+
sessionId: input.sessionId
|
|
338
|
+
});
|
|
339
|
+
if (prior === null)
|
|
340
|
+
return null;
|
|
341
|
+
if (prior.stage !== 'compacting' && prior.stage !== 'armed')
|
|
342
|
+
return null;
|
|
343
|
+
const afterRatio = input.measuredRatio !== null && input.measuredRatio < prior.triggerRatio ? input.measuredRatio : null;
|
|
344
|
+
const record = {
|
|
345
|
+
schemaVersion: 1,
|
|
346
|
+
runId: prior.runId,
|
|
347
|
+
stage: 'completed',
|
|
348
|
+
updatedAt: new Date().toISOString(),
|
|
349
|
+
triggerRatio: prior.triggerRatio,
|
|
350
|
+
redLine: prior.redLine,
|
|
351
|
+
...(afterRatio !== null ? { afterRatio } : {})
|
|
352
|
+
};
|
|
353
|
+
const facts = { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio };
|
|
354
|
+
try {
|
|
355
|
+
if (input.failLifecycleWrite)
|
|
356
|
+
throw new Error('lifecycle store unavailable');
|
|
357
|
+
writeCompactLifecycle({
|
|
358
|
+
projectRoot: input.projectRoot,
|
|
359
|
+
sessionId: input.sessionId,
|
|
360
|
+
record
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Deliberately NOT `null`. The run WAS open and the write FAILED; returning
|
|
365
|
+
// the same value as "no run is open" is the conflation the repo's own lint
|
|
366
|
+
// names at this exact line (`catch-return-null — caller cannot distinguish
|
|
367
|
+
// failure from success`), and it reached the user as "No compact run was
|
|
368
|
+
// open", which is false.
|
|
369
|
+
return { ...facts, lifecycleWritten: false };
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
input.onLifecycleStage?.('completed', record);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Observer failures are not ours to propagate.
|
|
376
|
+
}
|
|
377
|
+
return { ...facts, lifecycleWritten: true };
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* rid `2026-09-13-compact-event-settle` (repair R1): supply the measurement a
|
|
381
|
+
* harness-settled run was left owing.
|
|
382
|
+
*
|
|
383
|
+
* The function above deliberately refuses to launder a post-compact reading
|
|
384
|
+
* that has not dropped — and right after a compaction that refusal is the
|
|
385
|
+
* NORMAL case, because the statusline still holds the pre-compact value. The
|
|
386
|
+
* run is then closed at `completed` with no `afterRatio`, so the dispatch's
|
|
387
|
+
* calibration pair never closes and "intent vs observed" stays blank for
|
|
388
|
+
* exactly the compactions this slice exists to witness. This function is what
|
|
389
|
+
* makes the function above's promise payable.
|
|
390
|
+
*
|
|
391
|
+
* WHY NOT WIDEN `settleOpenLifecycleRun`. That one re-emits `verifying` before
|
|
392
|
+
* `completed`, which on an already-`completed` record is a backwards stage
|
|
393
|
+
* transition with no observer to serve. This is not a settlement — the run IS
|
|
394
|
+
* settled; only the number is owed. So no stage is rewritten here.
|
|
395
|
+
*
|
|
396
|
+
* WRITING `afterRatio` ONTO THE RECORD IS THE IDEMPOTENCE TOKEN: every later
|
|
397
|
+
* probe finds it present and returns `null`, so however many probes follow, one
|
|
398
|
+
* compaction yields exactly one late measurement.
|
|
399
|
+
*
|
|
400
|
+
* THE DROP GATE IS THE EVENT PATH'S OWN (`measuredRatio < triggerRatio`), not
|
|
401
|
+
* the probe path's `autoFireThreshold`. `afterRatio` has to mean "below the
|
|
402
|
+
* ratio this run was dispatched at" — the rule the event path already enforces
|
|
403
|
+
* — or a run dispatched under the threshold (a forced or banded dispatch) would
|
|
404
|
+
* let a NON-drop through the one path that can still write a `completed` record.
|
|
405
|
+
* `conservative-fallback` is refused for the probe path's reason: its `0` is
|
|
406
|
+
* the absence of a measurement, not an empty context.
|
|
407
|
+
*
|
|
408
|
+
* Returns the filled record, or `null` when no run is owed a measurement.
|
|
409
|
+
*/
|
|
410
|
+
export function fillEventSettledMeasurement(input) {
|
|
411
|
+
if (input.source === 'conservative-fallback')
|
|
412
|
+
return null;
|
|
413
|
+
const prior = readOpenCompactLifecycle({
|
|
414
|
+
projectRoot: input.projectRoot,
|
|
415
|
+
sessionId: input.sessionId
|
|
416
|
+
});
|
|
417
|
+
if (prior === null)
|
|
418
|
+
return null;
|
|
419
|
+
// Exactly one shape is owed a number: the one the EVENT path leaves behind.
|
|
420
|
+
// `settleOpenLifecycleRun` never writes it (it always carries `afterRatio`),
|
|
421
|
+
// and a `failed` run never dispatched, so it has no row to pair with.
|
|
422
|
+
if (prior.stage !== 'completed' || prior.afterRatio !== undefined)
|
|
423
|
+
return null;
|
|
424
|
+
if (input.measuredRatio >= prior.triggerRatio)
|
|
425
|
+
return null;
|
|
426
|
+
const record = {
|
|
427
|
+
schemaVersion: 1,
|
|
428
|
+
runId: prior.runId,
|
|
429
|
+
stage: 'completed',
|
|
430
|
+
updatedAt: new Date().toISOString(),
|
|
431
|
+
triggerRatio: prior.triggerRatio,
|
|
432
|
+
redLine: prior.redLine,
|
|
433
|
+
afterRatio: input.measuredRatio
|
|
434
|
+
};
|
|
435
|
+
try {
|
|
436
|
+
writeCompactLifecycle({
|
|
437
|
+
projectRoot: input.projectRoot,
|
|
438
|
+
sessionId: input.sessionId,
|
|
439
|
+
record
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
return { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio: input.measuredRatio };
|
|
270
446
|
}
|
|
@@ -31,7 +31,21 @@ export interface AutoCompactInput {
|
|
|
31
31
|
* autonomously at 0.85+ with zero human / zero LLM intervention.
|
|
32
32
|
*/
|
|
33
33
|
readonly force?: boolean | undefined;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* INERT — nothing reads this. It is threaded from the CLI's
|
|
36
|
+
* `--bypass-red-line` (a published flag, kept for backward compatibility;
|
|
37
|
+
* see the option's own note) down to `evaluateAutoCompactDecision`, where the
|
|
38
|
+
* red-line branch ignores it.
|
|
39
|
+
*
|
|
40
|
+
* Why nothing reads it any more: it used to skip a gate that refused to
|
|
41
|
+
* dispatch below 0.85. That gate was removed (slice
|
|
42
|
+
* 2026-09-13-auto-compact-trigger-ownership, T3/A1) — peaks-loop cannot
|
|
43
|
+
* compact a running session, so the refusal could not shorten the wait it
|
|
44
|
+
* was waiting for, and it deadlocked the runner at the worst moment. With the
|
|
45
|
+
* gate gone, the red line still ASKS the harness to compact (when it always
|
|
46
|
+
* did) and no longer needs a bypass. Kept as a field so the published
|
|
47
|
+
* signature does not change under callers that pass it.
|
|
48
|
+
*/
|
|
35
49
|
readonly bypassRedLine?: boolean | undefined;
|
|
36
50
|
/** Current session id (default = resolve via session-id-service). */
|
|
37
51
|
readonly sessionId?: string | undefined;
|
|
@@ -93,7 +107,7 @@ export interface AutoCompactTestHooks {
|
|
|
93
107
|
*
|
|
94
108
|
* - ratio < preCompact → 'none' or 'soft-warn'
|
|
95
109
|
* - ratio ≥ preCompact → 'pre-compact' (async-friendly path)
|
|
96
|
-
* - ratio ≥ redLine → 'red-line' (
|
|
110
|
+
* - ratio ≥ redLine → 'red-line' (ask the harness; dispatch continues)
|
|
97
111
|
*
|
|
98
112
|
* Slice 2026-07-28 (rid-027): `mode` selects the threshold table.
|
|
99
113
|
* Default `'standard'` (0.85/0.95). `'partial'` (0.70/0.85) is used
|
|
@@ -109,8 +123,13 @@ export declare function evaluateCompactTrigger(ratio: number, mode?: AutoCompact
|
|
|
109
123
|
* - 0.85 ≤ ratio < 0.95 → pre-compact; if in-flight batch
|
|
110
124
|
* present, defer (D6.e); else dispatch
|
|
111
125
|
* IDE compact asynchronously.
|
|
112
|
-
* - ratio ≥ 0.95 → red-line;
|
|
113
|
-
* regardless of in-flight batch.
|
|
126
|
+
* - ratio ≥ 0.95 → red-line; ask the harness to compact
|
|
127
|
+
* regardless of in-flight batch. Nothing is
|
|
128
|
+
* gated — dispatch is NOT blocked (slice
|
|
129
|
+
* 2026-09-13-auto-compact-trigger-ownership:
|
|
130
|
+
* peaks-loop cannot compact a running session,
|
|
131
|
+
* so a "block" gated nothing and deadlocked the
|
|
132
|
+
* runner).
|
|
114
133
|
*/
|
|
115
134
|
export declare function evaluateAutoCompactDecision(input: {
|
|
116
135
|
ratio: number;
|
|
@@ -129,6 +148,7 @@ export declare function evaluateAutoCompactDecision(input: {
|
|
|
129
148
|
*/
|
|
130
149
|
inflightBatch?: boolean | InFlightBatchProbe | undefined;
|
|
131
150
|
force?: boolean | undefined;
|
|
151
|
+
/** Accepted and ignored — see `AutoCompactInput.bypassRedLine`. */
|
|
132
152
|
bypassRedLine?: boolean | undefined;
|
|
133
153
|
mode?: AutoCompactMode | undefined;
|
|
134
154
|
/**
|
|
@@ -170,13 +190,14 @@ export declare function buildConvergencePlan(input: {
|
|
|
170
190
|
* 5. If trigger.kind === 'pre-compact' AND in-flight batch → wait.
|
|
171
191
|
* 6. If trigger.kind === 'pre-compact' → async dispatch (write
|
|
172
192
|
* checkpoint + IDE compact; orchestrator returns immediately).
|
|
173
|
-
* 7. If trigger.kind === 'red-line' →
|
|
174
|
-
*
|
|
193
|
+
* 7. If trigger.kind === 'red-line' → dispatch the IDE compact, report
|
|
194
|
+
* that the harness has been asked and that dispatch continues.
|
|
175
195
|
*
|
|
176
196
|
* The caller (CLI or skill body) handles the actual return — D7's
|
|
177
|
-
* post-compact-detect will pick up the checkpoint on the next turn.
|
|
178
|
-
*
|
|
179
|
-
*
|
|
197
|
+
* post-compact-detect will pick up the checkpoint on the next turn. For
|
|
198
|
+
* red-line, the caller keeps working; it re-probes and, if the ratio is
|
|
199
|
+
* still rising with no compact from the harness, reports that instead of
|
|
200
|
+
* stalling.
|
|
180
201
|
*/
|
|
181
202
|
export declare function runAutoCompact(input: AutoCompactInput): Promise<AutoCompactResult>;
|
|
182
203
|
/** Re-export for callers that need to surface the trigger shape. */
|
|
@@ -200,6 +221,29 @@ export interface CompactHistoryEvent {
|
|
|
200
221
|
readonly ok: boolean;
|
|
201
222
|
readonly checkpointPath: string;
|
|
202
223
|
readonly dispatchMessage: string;
|
|
224
|
+
/**
|
|
225
|
+
* Slice 2026-09-13-auto-compact-trigger-ownership (T4): the calibration
|
|
226
|
+
* instrument. `windowTokens` is the denominator peaks-loop divided by
|
|
227
|
+
* (`probe.capacityTokens`), so `beforeRatio * windowTokens` is the exact
|
|
228
|
+
* TOKEN POINT peaks-loop asked the harness to compact at. That number is
|
|
229
|
+
* the deliverable — NOT a hand-picked threshold: this slice could not run a
|
|
230
|
+
* real Claude Code session, so the real trigger point of
|
|
231
|
+
* `CLAUDE_CODE_AUTO_COMPACT_WINDOW` (documented as a window, observed to
|
|
232
|
+
* fire near the window's end) has no measured answer yet. Recording the
|
|
233
|
+
* intent lets the first real session produce one.
|
|
234
|
+
*/
|
|
235
|
+
readonly windowTokens?: number | null;
|
|
236
|
+
/** Which layer produced `windowTokens` (see `ContextWindowSource`). */
|
|
237
|
+
readonly windowSource?: string | null;
|
|
238
|
+
/**
|
|
239
|
+
* `dispatch` (default, and the only kind earlier releases wrote) or
|
|
240
|
+
* `observed` — a row appended when a later probe MEASURED the ratio after
|
|
241
|
+
* a dispatched compact, proving one landed. The pair is what yields the
|
|
242
|
+
* intent-vs-observed delta.
|
|
243
|
+
*/
|
|
244
|
+
readonly kind?: 'dispatch' | 'observed';
|
|
245
|
+
/** `observed` rows only: the measured post-compact ratio. */
|
|
246
|
+
readonly afterRatio?: number;
|
|
203
247
|
}
|
|
204
248
|
export declare function appendCompactHistoryEvent(input: {
|
|
205
249
|
readonly projectRoot: string;
|