peaks-loop 4.0.7 → 4.0.9
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 +58 -288
- package/dist/cli/commands/_register.js +2 -0
- package/dist/cli/commands/baseline-commands.d.ts +3 -0
- package/dist/cli/commands/baseline-commands.js +146 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -11
- package/dist/cli/commands/code-mode-gate-commands.js +1 -43
- package/dist/cli/commands/code-runtime-commands.js +36 -14
- package/dist/cli/commands/core/skill-command.js +75 -13
- package/dist/cli/commands/core/standards-command.js +5 -50
- package/dist/cli/commands/dispatch-commands.d.ts +20 -0
- package/dist/cli/commands/dispatch-commands.js +39 -38
- package/dist/cli/commands/dispatch-from-dag.js +17 -0
- package/dist/cli/commands/heartbeat-commands.d.ts +21 -0
- package/dist/cli/commands/heartbeat-commands.js +40 -0
- package/dist/cli/commands/hook-handle.js +5 -2
- package/dist/cli/commands/hooks-commands.js +9 -26
- package/dist/cli/commands/loop-eval-commands.js +5 -2
- package/dist/cli/commands/statusline-commands.js +1 -1
- package/dist/cli/commands/sub-agent-commands.js +0 -2
- package/dist/cli/commands/sub-agent-shared.d.ts +7 -9
- package/dist/cli/commands/workflow-commands.js +8 -0
- package/dist/cli/commands/workflow-lifecycle-commands.d.ts +63 -0
- package/dist/cli/commands/workflow-lifecycle-commands.js +302 -0
- package/dist/cli/commands/workspace/init-command.js +19 -0
- package/dist/services/audit/backing-detector.d.ts +2 -19
- package/dist/services/audit/backing-detector.js +7 -66
- package/dist/services/audit/enforcers/active-skill-resolver.d.ts +25 -9
- package/dist/services/audit/enforcers/active-skill-resolver.js +83 -21
- package/dist/services/audit/enforcers/lint-audit-regression.js +1 -10
- package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
- package/dist/services/capability-audit-service/cross-check.js +11 -0
- package/dist/services/capability-audit-service/index.d.ts +3 -0
- package/dist/services/capability-audit-service/index.js +2 -0
- package/dist/services/capability-audit-service/runner.d.ts +27 -0
- package/dist/services/capability-audit-service/runner.js +40 -0
- package/dist/services/capability-audit-service/staleness.d.ts +1 -0
- package/dist/services/capability-audit-service/staleness.js +7 -0
- package/dist/services/capability-audit-service/types.d.ts +24 -0
- package/dist/services/capability-audit-service/types.js +1 -0
- package/dist/services/capability-baseline/store.d.ts +33 -0
- package/dist/services/capability-baseline/store.js +80 -0
- package/dist/services/capability-baseline/types.d.ts +45 -0
- package/dist/services/capability-baseline/types.js +5 -0
- package/dist/services/capability-baseline/validator.d.ts +19 -0
- package/dist/services/capability-baseline/validator.js +38 -0
- package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
- package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
- package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
- package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
- package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
- package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
- package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
- package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
- package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
- package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
- package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
- package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
- package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
- package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
- package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
- package/dist/services/capability-guard-runner/diff.d.ts +2 -0
- package/dist/services/capability-guard-runner/diff.js +7 -0
- package/dist/services/capability-guard-runner/runner.d.ts +9 -0
- package/dist/services/capability-guard-runner/runner.js +22 -0
- package/dist/services/capability-guard-runner/types.d.ts +39 -0
- package/dist/services/capability-guard-runner/types.js +1 -0
- package/dist/services/code/auto-compact-orchestrator.d.ts +35 -2
- package/dist/services/code/auto-compact-orchestrator.js +29 -9
- package/dist/services/context/build-dispatch-system-prompt.js +0 -18
- package/dist/services/dispatch/dispatch-record-writer.d.ts +25 -1
- package/dist/services/dispatch/dispatch-record-writer.js +85 -15
- package/dist/services/dispatch/test-tool-detection.d.ts +0 -14
- package/dist/services/dispatch/test-tool-detection.js +0 -22
- package/dist/services/doctor/doctor-service/checks/skill-presence.d.ts +8 -0
- package/dist/services/doctor/doctor-service/checks/skill-presence.js +9 -1
- package/dist/services/evolution/evolution-types.d.ts +16 -16
- package/dist/services/final-review/final-review-service.d.ts +8 -0
- package/dist/services/final-review/final-review-service.js +14 -0
- package/dist/services/final-review/final-review-types.d.ts +1 -1
- package/dist/services/hooks/presence-marker-detector.d.ts +6 -0
- package/dist/services/hooks/presence-marker-detector.js +48 -0
- package/dist/services/ide/adapters/claude-code-adapter.js +20 -0
- package/dist/services/ide/adapters/codex-adapter.js +20 -1
- package/dist/services/ide/adapters/cursor-adapter.js +21 -1
- package/dist/services/ide/adapters/hermes-adapter.js +20 -1
- package/dist/services/ide/adapters/openclaw-adapter.js +20 -1
- package/dist/services/ide/adapters/qoder-adapter.js +20 -1
- package/dist/services/ide/adapters/tongyi-lingma-adapter.js +20 -1
- package/dist/services/ide/adapters/trae-adapter.js +21 -1
- package/dist/services/ide/adapters/zcode-adapter.js +19 -0
- package/dist/services/ide/ide-types.d.ts +15 -0
- package/dist/services/job/job-progress-store.d.ts +2 -2
- package/dist/services/job/job-types.d.ts +26 -26
- package/dist/services/observability/observability-service.d.ts +4 -4
- package/dist/services/scan/archetype-service.js +2 -52
- package/dist/services/sediment/json-schema.d.ts +4 -4
- package/dist/services/session/caller-binding-service.d.ts +21 -0
- package/dist/services/session/caller-binding-service.js +35 -0
- package/dist/services/session/caller-id-types.d.ts +62 -13
- package/dist/services/session/caller-id-types.js +25 -13
- package/dist/services/session/index.d.ts +4 -2
- package/dist/services/session/index.js +8 -2
- package/dist/services/session/platform-fallbacks.d.ts +25 -17
- package/dist/services/session/platform-fallbacks.js +26 -28
- package/dist/services/session/resolve-caller-id.d.ts +64 -29
- package/dist/services/session/resolve-caller-id.js +122 -48
- package/dist/services/session/session-manager.d.ts +0 -29
- package/dist/services/session/session-manager.js +0 -33
- package/dist/services/session/session-resume-service.d.ts +4 -0
- package/dist/services/session/session-resume-service.js +4 -0
- package/dist/services/skills/hooks-settings-service.d.ts +0 -41
- package/dist/services/skills/hooks-settings-service.js +5 -61
- package/dist/services/skills/presence-lease-service.d.ts +73 -0
- package/dist/services/skills/presence-lease-service.js +358 -0
- package/dist/services/skills/presence-lease-types.d.ts +75 -0
- package/dist/services/skills/presence-lease-types.js +16 -0
- package/dist/services/skills/skill-presence-service.js +86 -22
- package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
- package/dist/services/skills/skill-statusline-renderer.js +312 -100
- package/dist/services/slice/slice-check-service.js +0 -59
- package/dist/services/standards/project-standards-service.js +0 -44
- package/dist/services/workflow/workflow-graph-store.d.ts +77 -0
- package/dist/services/workflow/workflow-graph-store.js +278 -0
- package/dist/services/workflow/workflow-graph-types.d.ts +56 -0
- package/dist/services/workflow/workflow-graph-types.js +67 -0
- package/dist/services/workflow/workflow-inflight-probe.d.ts +52 -0
- package/dist/services/workflow/workflow-inflight-probe.js +86 -0
- package/dist/services/workflow/workflow-node-lifecycle.d.ts +67 -0
- package/dist/services/workflow/workflow-node-lifecycle.js +348 -0
- package/dist/services/workflow/workflow-presence-lifecycle.d.ts +58 -0
- package/dist/services/workflow/workflow-presence-lifecycle.js +196 -0
- package/dist/services/workspace/reconcile-service.d.ts +31 -0
- package/dist/services/workspace/reconcile-service.js +132 -1
- package/package.json +5 -4
- package/skills/peaks-code/SKILL.md +5 -43
- package/skills/peaks-code/references/completion-handoff.md +1 -1
- package/skills/peaks-code/references/skill-presence-and-title.md +1 -1
- package/dist/cli/commands/sub-agent-exec-command.d.ts +0 -25
- package/dist/cli/commands/sub-agent-exec-command.js +0 -132
- package/dist/services/standards/find-skills-integration.d.ts +0 -59
- package/dist/services/standards/find-skills-integration.js +0 -225
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function assertBaselineRef(contract) {
|
|
2
|
+
if (!contract.source.baselineRow || !contract.source.invariant) {
|
|
3
|
+
throw new Error('GUARD_CONTRACT_MISSING_BASELINE_REF: contract must reference a baseline row and invariant');
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export async function runGuard(contract, ctx) {
|
|
7
|
+
assertBaselineRef(contract);
|
|
8
|
+
return contract.execute(ctx);
|
|
9
|
+
}
|
|
10
|
+
export async function runAllGuards(contracts, ctx) {
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const c of contracts) {
|
|
13
|
+
results.push(await runGuard(c, ctx));
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
pass: results.filter((r) => r.status === 'pass').length,
|
|
17
|
+
fail: results.filter((r) => r.status === 'fail').length,
|
|
18
|
+
skipped: results.filter((r) => r.status === 'skipped').length,
|
|
19
|
+
total: results.length,
|
|
20
|
+
results
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { JourneyId } from '../capability-baseline/types.js';
|
|
2
|
+
export type ContractKind = 'cli-envelope' | 'workflow-trace' | 'hook-assertion' | 'cli-output-golden' | 'asset-roundtrip' | 'concurrency-lease' | 'sop-register' | 'spec-coverage' | 'envelope-arg-shapes';
|
|
3
|
+
export interface GuardContract {
|
|
4
|
+
readonly journeyId: JourneyId;
|
|
5
|
+
readonly kind: ContractKind;
|
|
6
|
+
readonly source: {
|
|
7
|
+
readonly baselineRow: JourneyId;
|
|
8
|
+
readonly invariant: string;
|
|
9
|
+
};
|
|
10
|
+
readonly execute: (ctx: GuardContext) => Promise<GuardRunResult>;
|
|
11
|
+
readonly evidence: {
|
|
12
|
+
readonly kind: ContractKind;
|
|
13
|
+
readonly artifact: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface GuardContext {
|
|
17
|
+
readonly projectRoot: string;
|
|
18
|
+
readonly sessionId: string;
|
|
19
|
+
readonly contract: GuardContract;
|
|
20
|
+
readonly baselineInvariant: string;
|
|
21
|
+
}
|
|
22
|
+
export type GuardStatus = 'pass' | 'fail' | 'skipped';
|
|
23
|
+
export interface GuardDiff {
|
|
24
|
+
readonly before: string;
|
|
25
|
+
readonly after: string;
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
}
|
|
28
|
+
export interface GuardRunResult {
|
|
29
|
+
readonly journeyId: JourneyId;
|
|
30
|
+
readonly contract: ContractKind;
|
|
31
|
+
readonly status: GuardStatus;
|
|
32
|
+
readonly diff?: GuardDiff;
|
|
33
|
+
readonly artifactPath: string;
|
|
34
|
+
}
|
|
35
|
+
export type GuardErrorCode = 'GUARD_CONTRACT_MISSING_BASELINE_REF' | 'GUARD_DIFF_DETECTED' | 'GUARD_TEST_FLAKY';
|
|
36
|
+
export interface GuardError {
|
|
37
|
+
readonly code: GuardErrorCode;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -5,8 +5,26 @@ import { type CompactLifecycleRecord, type CompactLifecycleStage } from '../comp
|
|
|
5
5
|
export interface AutoCompactInput {
|
|
6
6
|
/** Project root for context (default cwd). */
|
|
7
7
|
readonly projectRoot: string;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Caller-provided in-flight batch probe (default false). Slice
|
|
10
|
+
* 4.0.8 (D4d): production callers should pass a `probeInflightBatch`
|
|
11
|
+
* function (see below). The legacy `inFlightBatch` boolean
|
|
12
|
+
* remains as a TEST-ONLY seam — the CLI gates it behind
|
|
13
|
+
* `process.env.PEAKS_TEST_SEAM === '1'` so a production run
|
|
14
|
+
* never consults the boolean as truth.
|
|
15
|
+
*/
|
|
9
16
|
readonly inFlightBatch?: InFlightBatchProbe | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Slice 4.0.8 (D4d): graph-only in-flight probe. When supplied,
|
|
19
|
+
* the orchestrator calls this function and uses the boolean
|
|
20
|
+
* result as the production `inFlightBatch` signal. The legacy
|
|
21
|
+
* boolean parameter is suppressed in production callers — the
|
|
22
|
+
* CLI's `--in-flight-batch` flag is gated behind
|
|
23
|
+
* `PEAKS_TEST_SEAM === '1'`. The pure decision function
|
|
24
|
+
* `evaluateAutoCompactDecision` retains the boolean signature
|
|
25
|
+
* for the test seam + the red-line override.
|
|
26
|
+
*/
|
|
27
|
+
readonly probeInflightBatch?: (() => boolean) | undefined;
|
|
10
28
|
/**
|
|
11
29
|
* Force execute even when ratio < threshold (test seam). In
|
|
12
30
|
* production this is always `false` — peaks-loop drives compact
|
|
@@ -96,7 +114,20 @@ export declare function evaluateCompactTrigger(ratio: number, mode?: AutoCompact
|
|
|
96
114
|
*/
|
|
97
115
|
export declare function evaluateAutoCompactDecision(input: {
|
|
98
116
|
ratio: number;
|
|
99
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Caller-provided in-flight batch signal. Accepts either a plain
|
|
119
|
+
* boolean (convenience / repro seam) or the full `InFlightBatchProbe`
|
|
120
|
+
* shape (production callers — graph-probe-backed). When the boolean
|
|
121
|
+
* form is `true`, the normalized probe carries
|
|
122
|
+
* `hasInFlightBatch: true`. Slice 4.0.8 hotfix.
|
|
123
|
+
*/
|
|
124
|
+
inFlightBatch?: boolean | InFlightBatchProbe | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Legacy camelCase alias for `inFlightBatch`. Production repro
|
|
127
|
+
* inputs use `inflightBatch: true|false`; the orchestrator
|
|
128
|
+
* normalizes it to the same internal shape. Slice 4.0.8 hotfix.
|
|
129
|
+
*/
|
|
130
|
+
inflightBatch?: boolean | InFlightBatchProbe | undefined;
|
|
100
131
|
force?: boolean | undefined;
|
|
101
132
|
bypassRedLine?: boolean | undefined;
|
|
102
133
|
mode?: AutoCompactMode | undefined;
|
|
@@ -111,6 +142,8 @@ export declare function evaluateAutoCompactDecision(input: {
|
|
|
111
142
|
shouldCompact: boolean;
|
|
112
143
|
reason: 'below-threshold' | 'in-flight-batch' | 'pre-compact' | 'red-line';
|
|
113
144
|
trigger: CompactTrigger;
|
|
145
|
+
/** Typed verdict enum (RD §4 24h-mode contract). Slice 4.0.8 hotfix. */
|
|
146
|
+
action: 'ok' | 'soft-warn' | 'auto-compact-now' | 'red-line' | 'defer';
|
|
114
147
|
};
|
|
115
148
|
/**
|
|
116
149
|
* Build the convergence plan that D7's post-compact-detect will read
|
|
@@ -108,19 +108,29 @@ export function evaluateCompactTrigger(ratio, mode = 'standard') {
|
|
|
108
108
|
*/
|
|
109
109
|
export function evaluateAutoCompactDecision(input) {
|
|
110
110
|
const trigger = evaluateCompactTrigger(input.ratio, input.mode ?? 'standard');
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
// Normalize both `inFlightBatch` and `inflightBatch` (boolean | probe)
|
|
112
|
+
// into a single `InFlightBatchProbe` shape. The probe reads
|
|
113
|
+
// `hasInFlightBatch`; the boolean reads truthiness.
|
|
114
|
+
const rawProbe = input.inFlightBatch ?? input.inflightBatch;
|
|
115
|
+
const probe = typeof rawProbe === 'boolean'
|
|
116
|
+
? { hasInFlightBatch: rawProbe }
|
|
117
|
+
: rawProbe;
|
|
118
|
+
if (trigger.kind === 'none') {
|
|
119
|
+
return { shouldCompact: false, reason: 'below-threshold', trigger, action: 'ok' };
|
|
120
|
+
}
|
|
121
|
+
if (trigger.kind === 'soft-warn') {
|
|
122
|
+
return { shouldCompact: false, reason: 'below-threshold', trigger, action: 'soft-warn' };
|
|
113
123
|
}
|
|
114
124
|
if (trigger.kind === 'red-line') {
|
|
115
125
|
// Red line: ignore in-flight batch — synchronous dispatch wins.
|
|
116
|
-
return { shouldCompact: true, reason: 'red-line', trigger };
|
|
126
|
+
return { shouldCompact: true, reason: 'red-line', trigger, action: 'red-line' };
|
|
117
127
|
}
|
|
118
128
|
// pre-compact zone (0.85 ≤ ratio < 0.95): honor D6.e in-flight deferral.
|
|
119
|
-
if (
|
|
120
|
-
return { shouldCompact: false, reason: 'in-flight-batch', trigger };
|
|
129
|
+
if (probe?.hasInFlightBatch === true) {
|
|
130
|
+
return { shouldCompact: false, reason: 'in-flight-batch', trigger, action: 'defer' };
|
|
121
131
|
}
|
|
122
132
|
if (input.force) {
|
|
123
|
-
return { shouldCompact: true, reason: 'pre-compact', trigger };
|
|
133
|
+
return { shouldCompact: true, reason: 'pre-compact', trigger, action: 'auto-compact-now' };
|
|
124
134
|
}
|
|
125
135
|
// Slice 2026-07-31-rid-mac-transcript-estimate-trigger: transcript-estimate
|
|
126
136
|
// is the ONLY signal available on Mac Claude Code (no env-var, no statusline
|
|
@@ -131,9 +141,9 @@ export function evaluateAutoCompactDecision(input) {
|
|
|
131
141
|
// source is present (`claude-code-env` would have been P1, `statusline-poll`
|
|
132
142
|
// P2, `user-overridden` P4) — Mac's only signal is `transcript-estimate`.
|
|
133
143
|
if (input.source === 'transcript-estimate' && input.ratio >= AUTO_COMPACT_PRE_COMPACT_RATIO)
|
|
134
|
-
return { shouldCompact: true, reason: 'pre-compact', trigger };
|
|
144
|
+
return { shouldCompact: true, reason: 'pre-compact', trigger, action: 'auto-compact-now' };
|
|
135
145
|
// Default: peaks-loop drives pre-compact autonomously.
|
|
136
|
-
return { shouldCompact: true, reason: 'pre-compact', trigger };
|
|
146
|
+
return { shouldCompact: true, reason: 'pre-compact', trigger, action: 'auto-compact-now' };
|
|
137
147
|
}
|
|
138
148
|
/**
|
|
139
149
|
* Build the convergence plan that D7's post-compact-detect will read
|
|
@@ -514,7 +524,17 @@ export async function runAutoCompact(input) {
|
|
|
514
524
|
});
|
|
515
525
|
const decision = evaluateAutoCompactDecision({
|
|
516
526
|
ratio: probe.ratio,
|
|
517
|
-
|
|
527
|
+
// Slice 4.0.8 (D4d): production `inFlightBatch` MUST come from
|
|
528
|
+
// the graph probe (see `workflow-inflight-probe.ts`). The
|
|
529
|
+
// legacy boolean is preserved as a TEST-ONLY seam: when
|
|
530
|
+
// `probeInflightBatch` is supplied, we call it and pass the
|
|
531
|
+
// result as a `InFlightBatchProbe` so the pure decision function
|
|
532
|
+
// sees a graph-backed value. When only the boolean is
|
|
533
|
+
// supplied, we treat it as the test seam (the CLI gates it
|
|
534
|
+
// behind `PEAKS_TEST_SEAM === '1'`).
|
|
535
|
+
inFlightBatch: input.probeInflightBatch !== undefined
|
|
536
|
+
? { hasInFlightBatch: input.probeInflightBatch() }
|
|
537
|
+
: input.inFlightBatch,
|
|
518
538
|
force: input.force,
|
|
519
539
|
bypassRedLine: input.bypassRedLine,
|
|
520
540
|
mode,
|
|
@@ -123,24 +123,6 @@ function renderContextBlock(probe) {
|
|
|
123
123
|
if (probe !== null && probe !== undefined) {
|
|
124
124
|
const usedPct = (probe.ratio * 100).toFixed(1);
|
|
125
125
|
const freePct = ((1 - probe.ratio) * 100).toFixed(1);
|
|
126
|
-
// Slice 4.0.7-dogfood-PR-11 (ice-cola surface probe 2026-08-02):
|
|
127
|
-
// when the source is conservative-fallback, the ratio is
|
|
128
|
-
// hard-coded to 0 (no signal available). The pre-rid prompt
|
|
129
|
-
// rendered "0.0% used (100.0% free)" without flagging that
|
|
130
|
-
// this is a fallback, so the sub-agent thought it had 100%
|
|
131
|
-
// free and never re-checked. The fix short-circuits to the
|
|
132
|
-
// "no probe available" message when the source is not one of
|
|
133
|
-
// the trusted values emitted by peaks-loop's own readers.
|
|
134
|
-
const KNOWN_SOURCES = new Set(['token-counted', 'transcript-estimate', 'statusline']);
|
|
135
|
-
if (!KNOWN_SOURCES.has(probe.source)) {
|
|
136
|
-
return `## Context window (no probe available)
|
|
137
|
-
|
|
138
|
-
The orchestrator captured a probe with source: \`${probe.source}\` (ratio ${usedPct}%) but the dispatch layer does not trust this source. The pre-rid dispatch prompt rendered this as "0.0% used (100.0% free)" without flagging the fallback, which caused sub-agents to think they had 100% free and never re-check. **Treat the displayed ratio as unverified.**
|
|
139
|
-
|
|
140
|
-
If you need to evaluate context pressure, run \`peaks code context-now --project <root>\` and trust its \`ratio\` field. Do not estimate from message length.
|
|
141
|
-
|
|
142
|
-
`;
|
|
143
|
-
}
|
|
144
126
|
const action = probe.ratio >= 0.95
|
|
145
127
|
? 'RED-LINE — call `peaks compact auto --execute` immediately.'
|
|
146
128
|
: probe.ratio >= 0.85
|
|
@@ -43,7 +43,7 @@ export interface DispatchRecord {
|
|
|
43
43
|
* truth for "this record is v3.1-form"; readers check
|
|
44
44
|
* `version === '3.1'` for forward-compatible dispatching.
|
|
45
45
|
*/
|
|
46
|
-
readonly version: '
|
|
46
|
+
readonly version: '4.0.0';
|
|
47
47
|
readonly createdAt: string;
|
|
48
48
|
readonly completedAt: string | null;
|
|
49
49
|
readonly outcome: DispatchOutcome;
|
|
@@ -120,6 +120,22 @@ export interface DispatchRecord {
|
|
|
120
120
|
* merge transcript.
|
|
121
121
|
*/
|
|
122
122
|
readonly mergeBackAttempts: number;
|
|
123
|
+
/**
|
|
124
|
+
* Slice 4.0.8 RD §4 D4c (presence-lease-graph): the workflow id +
|
|
125
|
+
* graph node id + graphRef this dispatch is bound to. Persisted
|
|
126
|
+
* directly in the dispatch record (NOT in a sidecar) so the
|
|
127
|
+
* envelope-writer `markCompleted` can auto-transition the bound
|
|
128
|
+
* graph node to `envelope-received` with `ackStatus=pending` in
|
|
129
|
+
* one protected update.
|
|
130
|
+
*
|
|
131
|
+
* The schema bump from `3.2 → 4.0.0` is BREAKING in the sense
|
|
132
|
+
* that the literal type is narrowed. The optional `?` keeps back-
|
|
133
|
+
* compat for old records that pre-date the binding (the
|
|
134
|
+
* `upgradeRecord` reader defaults them to `null`).
|
|
135
|
+
*/
|
|
136
|
+
readonly workflowId: string | null;
|
|
137
|
+
readonly graphNodeId: string | null;
|
|
138
|
+
readonly graphRef: string | null;
|
|
123
139
|
}
|
|
124
140
|
/** Input for the initial write. */
|
|
125
141
|
export type WriteInitialDispatchInput = {
|
|
@@ -148,6 +164,14 @@ export type WriteInitialDispatchInput = {
|
|
|
148
164
|
* time when `--isolation` is requested.
|
|
149
165
|
*/
|
|
150
166
|
isolationStartedAt?: string | null;
|
|
167
|
+
/**
|
|
168
|
+
* Slice 4.0.8: workflow graph binding for the dispatch. Defaults
|
|
169
|
+
* to `null` so a non-graph dispatch (legacy CLI flow, ad-hoc
|
|
170
|
+
* dispatch) still writes a v4.0.0 record.
|
|
171
|
+
*/
|
|
172
|
+
workflowId?: string | null;
|
|
173
|
+
graphNodeId?: string | null;
|
|
174
|
+
graphRef?: string | null;
|
|
151
175
|
};
|
|
152
176
|
/** Heartbeat write input. */
|
|
153
177
|
export type AppendHeartbeatInput = {
|
|
@@ -44,7 +44,7 @@ export function writeInitialDispatchRecord(input) {
|
|
|
44
44
|
const path = dispatchRecordPath(projectRoot, sessionId, requestId, now());
|
|
45
45
|
const safePath = assertSafeDispatchRecordPath(path, projectRoot);
|
|
46
46
|
const record = {
|
|
47
|
-
version: '
|
|
47
|
+
version: '4.0.0',
|
|
48
48
|
createdAt: now().toISOString(),
|
|
49
49
|
completedAt: null,
|
|
50
50
|
outcome: 'no-execution',
|
|
@@ -91,7 +91,14 @@ export function writeInitialDispatchRecord(input) {
|
|
|
91
91
|
// New records start with empty serviceKill and zero attempts;
|
|
92
92
|
// the merge-back-runner (Task 9) populates them in place.
|
|
93
93
|
serviceKill: [],
|
|
94
|
-
mergeBackAttempts: 0
|
|
94
|
+
mergeBackAttempts: 0,
|
|
95
|
+
// Slice 4.0.8: workflow graph binding (RD §4 D4c). Defaults to
|
|
96
|
+
// `null` for legacy / ad-hoc dispatches that do not bind a graph
|
|
97
|
+
// node; v4.0.0 schema is structural (required field), so a `null`
|
|
98
|
+
// is the explicit "no binding" state.
|
|
99
|
+
workflowId: typeof input.workflowId === 'string' && /^[a-zA-Z0-9._-]{1,200}$/.test(input.workflowId) ? input.workflowId : null,
|
|
100
|
+
graphNodeId: typeof input.graphNodeId === 'string' && /^[a-zA-Z0-9._-]{1,200}$/.test(input.graphNodeId) ? input.graphNodeId : null,
|
|
101
|
+
graphRef: typeof input.graphRef === 'string' && input.graphRef.length > 0 ? input.graphRef : null,
|
|
95
102
|
};
|
|
96
103
|
writeAtomic(safePath, record);
|
|
97
104
|
// Slice 2026-06-23-audit-4th #A4: register the path in the
|
|
@@ -425,6 +432,68 @@ export function markCompleted(input) {
|
|
|
425
432
|
writeAtomic(input.recordPath, next);
|
|
426
433
|
return { record: next };
|
|
427
434
|
});
|
|
435
|
+
// Slice 4.0.8 RD §4 D4c: after validating the artifact/envelope
|
|
436
|
+
// association with `dispatchRef`, automatically transition the
|
|
437
|
+
// bound graph node to `envelope-received` with
|
|
438
|
+
// `ackStatus=pending`. This is the auto-transition the parent
|
|
439
|
+
// ack protocol relies on — the canonical dispatch chain is
|
|
440
|
+
// `prepare -> dispatched -> running -> envelope-received -> consumed-by-parent`.
|
|
441
|
+
// The transition runs through `workflow-node-lifecycle.writeEnvelope`
|
|
442
|
+
// so the same typed error contract (PEAKS_ENVELOPE_GRAPH_MISMATCH,
|
|
443
|
+
// PEAKS_GRAPH_REF_BROKEN, etc.) is reused. Failures are swallowed
|
|
444
|
+
// (best-effort; the dispatch record itself is the source of truth
|
|
445
|
+
// and the transition is observable through the graph store).
|
|
446
|
+
if (result.record.workflowId !== null && result.record.graphNodeId !== null && result.record.graphRef !== null) {
|
|
447
|
+
try {
|
|
448
|
+
// Lazy ESM dynamic import: the graph lifecycle service is not
|
|
449
|
+
// on the dispatch hot path; it would be wasteful to import it
|
|
450
|
+
// for non-graph-bound dispatches. ESM dynamic import returns
|
|
451
|
+
// a promise; we do not await (best-effort) — the dispatch
|
|
452
|
+
// record write is the load-bearing artifact, and the graph
|
|
453
|
+
// node transition is observable to the next CLI call.
|
|
454
|
+
void (async () => {
|
|
455
|
+
try {
|
|
456
|
+
const lifecycleMod = await import('../workflow/workflow-node-lifecycle.js');
|
|
457
|
+
const storeMod = await import('../workflow/workflow-graph-store.js');
|
|
458
|
+
const sessionRoot = (() => {
|
|
459
|
+
try {
|
|
460
|
+
return storeMod.graphPathFor({
|
|
461
|
+
projectRoot: input.projectRoot ?? '',
|
|
462
|
+
sessionId: result.record.sessionId,
|
|
463
|
+
graphRef: result.record.graphRef ?? '',
|
|
464
|
+
workflowId: result.record.workflowId ?? '',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
})();
|
|
471
|
+
if (sessionRoot === null)
|
|
472
|
+
return;
|
|
473
|
+
const graph = storeMod.readGraph({
|
|
474
|
+
projectRoot: input.projectRoot ?? '',
|
|
475
|
+
sessionId: result.record.sessionId,
|
|
476
|
+
graphRef: result.record.graphRef ?? '',
|
|
477
|
+
workflowId: result.record.workflowId ?? '',
|
|
478
|
+
});
|
|
479
|
+
const node = graph.nodes.find((n) => n.id === result.record.graphNodeId);
|
|
480
|
+
if (node === undefined)
|
|
481
|
+
return;
|
|
482
|
+
// Use the dispatch record's path as the dispatchRef. The
|
|
483
|
+
// record itself is the load-bearing artifact; the
|
|
484
|
+
// graph-node transition is a derived side-effect.
|
|
485
|
+
const dispatchRef = input.recordPath;
|
|
486
|
+
lifecycleMod.writeEnvelope({
|
|
487
|
+
graphNode: node,
|
|
488
|
+
dispatchRef,
|
|
489
|
+
envelopeDispatchRef: dispatchRef,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
catch { /* best-effort graph transition */ }
|
|
493
|
+
})();
|
|
494
|
+
}
|
|
495
|
+
catch { /* best-effort */ }
|
|
496
|
+
}
|
|
428
497
|
// Slice 2026-06-23-audit-4th #A4: update the active-dispatches
|
|
429
498
|
// index. Best-effort (the on-disk record is the source of truth);
|
|
430
499
|
// we only attempt the update when the trusted projectRoot is
|
|
@@ -549,18 +618,13 @@ function upgradeRecord(parsed) {
|
|
|
549
618
|
throw new Error('Dispatch record root must be an object');
|
|
550
619
|
}
|
|
551
620
|
const obj = parsed;
|
|
552
|
-
// Slice
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
// upgraded to v3.1 on read. The forward-compat check rejects
|
|
556
|
-
// records with an unknown version string (which would
|
|
557
|
-
// indicate either a much-older build or a much-newer one
|
|
558
|
-
// with a schema we have not implemented). v1 records
|
|
559
|
-
// (pre-audit-trail) must be regenerated.
|
|
621
|
+
// Slice 4.0.8: 3.2 → 4.0.0 schema bump. The literal type narrows
|
|
622
|
+
// to '4.0.0' but legacy v3.2 / v3.1 / 3 / 2 / 1 records are
|
|
623
|
+
// accepted transparently and upgraded on read.
|
|
560
624
|
const rawVersion = obj.version;
|
|
561
|
-
if (rawVersion !== '3.2' && rawVersion !== '3.1' && rawVersion !== 3 && rawVersion !== 2 && rawVersion !== 1) {
|
|
562
|
-
throw new Error(`Dispatch record version mismatch: expected '3.2', '3.1', 3, 2, or 1, got ${JSON.stringify(rawVersion)}. ` +
|
|
563
|
-
'The v1 →
|
|
625
|
+
if (rawVersion !== '4.0.0' && rawVersion !== '3.2' && rawVersion !== '3.1' && rawVersion !== 3 && rawVersion !== 2 && rawVersion !== 1) {
|
|
626
|
+
throw new Error(`Dispatch record version mismatch: expected '4.0.0', '3.2', '3.1', 3, 2, or 1, got ${JSON.stringify(rawVersion)}. ` +
|
|
627
|
+
'The v1 → v4.0.0 migration is in-file; records from much older or newer builds must be regenerated.');
|
|
564
628
|
}
|
|
565
629
|
const role = stringField(obj, 'role');
|
|
566
630
|
const requestId = stringField(obj, 'requestId');
|
|
@@ -602,7 +666,7 @@ function upgradeRecord(parsed) {
|
|
|
602
666
|
? obj.batchId
|
|
603
667
|
: 'legacy-batch';
|
|
604
668
|
return {
|
|
605
|
-
version: '
|
|
669
|
+
version: '4.0.0',
|
|
606
670
|
createdAt,
|
|
607
671
|
completedAt,
|
|
608
672
|
outcome,
|
|
@@ -652,7 +716,13 @@ function upgradeRecord(parsed) {
|
|
|
652
716
|
: [],
|
|
653
717
|
mergeBackAttempts: typeof obj.mergeBackAttempts === 'number' && Number.isFinite(obj.mergeBackAttempts) && obj.mergeBackAttempts >= 0
|
|
654
718
|
? Math.floor(obj.mergeBackAttempts)
|
|
655
|
-
: 0
|
|
719
|
+
: 0,
|
|
720
|
+
// Slice 4.0.8: 3.2 → 4.0.0 migration. v3.2 records on disk
|
|
721
|
+
// pre-date the workflow-graph binding; default all three
|
|
722
|
+
// fields to `null` so a legacy record upgrades transparently.
|
|
723
|
+
workflowId: typeof obj.workflowId === 'string' && /^[a-zA-Z0-9._-]{1,200}$/.test(obj.workflowId) ? obj.workflowId : null,
|
|
724
|
+
graphNodeId: typeof obj.graphNodeId === 'string' && /^[a-zA-Z0-9._-]{1,200}$/.test(obj.graphNodeId) ? obj.graphNodeId : null,
|
|
725
|
+
graphRef: typeof obj.graphRef === 'string' ? obj.graphRef : null
|
|
656
726
|
};
|
|
657
727
|
}
|
|
658
728
|
function isObject(v) {
|
|
@@ -34,20 +34,6 @@ export declare const TEST_TOOL_DETECTION_BLOCK = "## Test Tool Detection (mandat
|
|
|
34
34
|
* (e.g. projectRoot for context) without churning every call site.
|
|
35
35
|
*/
|
|
36
36
|
export declare function formatTestToolDetection(): string;
|
|
37
|
-
/**
|
|
38
|
-
* Slice 4.0.7-dogfood-PR-9: optional monorepo block prepended when
|
|
39
|
-
* `<projectRoot>/pnpm-workspace.yaml` exists. The pre-rid static
|
|
40
|
-
* template did not mention monorepos at all, so sub-agents on a
|
|
41
|
-
* monorepo project (e.g. ice-cola, 4 packages) ran `pnpm test` which
|
|
42
|
-
* fanned out to every sub-package — a 36-minute wall clock for what
|
|
43
|
-
* was a single-slice change. The block tells the sub-agent to scope
|
|
44
|
-
* to a single sub-package via `pnpm -r --filter @scope/name test`
|
|
45
|
-
* and to detect the layout via `peaks test --json` first.
|
|
46
|
-
*
|
|
47
|
-
* The block is empty when the project is not a monorepo, so the
|
|
48
|
-
* single-package case is byte-identical to the pre-rid prompt.
|
|
49
|
-
*/
|
|
50
|
-
export declare const MONOREPO_TEST_TOOL_BLOCK = "## Monorepo Test Tool Detection (only when pnpm-workspace.yaml exists)\n\nWhen `<projectRoot>/pnpm-workspace.yaml` is present, the project is a pnpm monorepo. The root `package.json#scripts.test` is typically `pnpm -r test` (recursive across all packages) and `pnpm test` from the root will fan out to every sub-package. **Do not run that.** Scope to a single sub-package:\n\n- detect the active sub-package by looking for `packages/<name>/package.json` (or via `pnpm -r --filter ${YOUR_PACKAGE_NAME} test`)\n- run `pnpm --filter @<scope>/<name> test <pattern>` for one sub-package only\n- if a top-level test script is genuinely needed across multiple packages, prefix with `PEAKS_FULL_TEST=1` AND justify in the final report\n\nA repo without `pnpm-workspace.yaml` has no monorepo block; the pre-rid static template applies unchanged.";
|
|
51
37
|
/**
|
|
52
38
|
* Slice 2026-07-29-dispatch-stall-governance / S5 (AC-4.1 / AC-4.2) —
|
|
53
39
|
* pure scope classifier. Returns a typed result so callers (the
|
|
@@ -54,28 +54,6 @@ If unsure which framework the consumer project uses, run \`peaks test --json\` f
|
|
|
54
54
|
export function formatTestToolDetection() {
|
|
55
55
|
return TEST_TOOL_DETECTION_BLOCK;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Slice 4.0.7-dogfood-PR-9: optional monorepo block prepended when
|
|
59
|
-
* `<projectRoot>/pnpm-workspace.yaml` exists. The pre-rid static
|
|
60
|
-
* template did not mention monorepos at all, so sub-agents on a
|
|
61
|
-
* monorepo project (e.g. ice-cola, 4 packages) ran `pnpm test` which
|
|
62
|
-
* fanned out to every sub-package — a 36-minute wall clock for what
|
|
63
|
-
* was a single-slice change. The block tells the sub-agent to scope
|
|
64
|
-
* to a single sub-package via `pnpm -r --filter @scope/name test`
|
|
65
|
-
* and to detect the layout via `peaks test --json` first.
|
|
66
|
-
*
|
|
67
|
-
* The block is empty when the project is not a monorepo, so the
|
|
68
|
-
* single-package case is byte-identical to the pre-rid prompt.
|
|
69
|
-
*/
|
|
70
|
-
export const MONOREPO_TEST_TOOL_BLOCK = `## Monorepo Test Tool Detection (only when pnpm-workspace.yaml exists)
|
|
71
|
-
|
|
72
|
-
When \`<projectRoot>/pnpm-workspace.yaml\` is present, the project is a pnpm monorepo. The root \`package.json#scripts.test\` is typically \`pnpm -r test\` (recursive across all packages) and \`pnpm test\` from the root will fan out to every sub-package. **Do not run that.** Scope to a single sub-package:
|
|
73
|
-
|
|
74
|
-
- detect the active sub-package by looking for \`packages/<name>/package.json\` (or via \`pnpm -r --filter \${YOUR_PACKAGE_NAME} test\`)
|
|
75
|
-
- run \`pnpm --filter @<scope>/<name> test <pattern>\` for one sub-package only
|
|
76
|
-
- if a top-level test script is genuinely needed across multiple packages, prefix with \`PEAKS_FULL_TEST=1\` AND justify in the final report
|
|
77
|
-
|
|
78
|
-
A repo without \`pnpm-workspace.yaml\` has no monorepo block; the pre-rid static template applies unchanged.`;
|
|
79
57
|
export function classifyTestCommand(command, env = process.env) {
|
|
80
58
|
if (typeof command !== 'string') {
|
|
81
59
|
return {
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* Check: skill presence current + freshness
|
|
3
3
|
* (`skill-presence:current` and `skill-presence:freshness`).
|
|
4
4
|
*
|
|
5
|
+
* Slice 4.0.8 (RD §3 + §4 D3): the diagnostic also surfaces
|
|
6
|
+
* stale / lost / broken-graph leases by running a `dry-run` GC
|
|
7
|
+
* summary through `gcStalePresenceLeases`. The summary carries
|
|
8
|
+
* `removed` / `retained` counts plus per-lease warnings (typed
|
|
9
|
+
* `PEAKS_GRAPH_REF_BROKEN`, `PEAKS_GRAPH_CORRUPTED`); the doctor
|
|
10
|
+
* surfaces them so the operator can run `peaks workspace reconcile`
|
|
11
|
+
* to repair.
|
|
12
|
+
*
|
|
5
13
|
* Two checks sharing one read of the `presence` context field.
|
|
6
14
|
* - `skill-presence:current` is informational: passes when the
|
|
7
15
|
* presence is wired, with a message describing the active
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* Check: skill presence current + freshness
|
|
3
3
|
* (`skill-presence:current` and `skill-presence:freshness`).
|
|
4
4
|
*
|
|
5
|
+
* Slice 4.0.8 (RD §3 + §4 D3): the diagnostic also surfaces
|
|
6
|
+
* stale / lost / broken-graph leases by running a `dry-run` GC
|
|
7
|
+
* summary through `gcStalePresenceLeases`. The summary carries
|
|
8
|
+
* `removed` / `retained` counts plus per-lease warnings (typed
|
|
9
|
+
* `PEAKS_GRAPH_REF_BROKEN`, `PEAKS_GRAPH_CORRUPTED`); the doctor
|
|
10
|
+
* surfaces them so the operator can run `peaks workspace reconcile`
|
|
11
|
+
* to repair.
|
|
12
|
+
*
|
|
5
13
|
* Two checks sharing one read of the `presence` context field.
|
|
6
14
|
* - `skill-presence:current` is informational: passes when the
|
|
7
15
|
* presence is wired, with a message describing the active
|
|
@@ -56,7 +64,7 @@ function run({ options, presence }) {
|
|
|
56
64
|
checks.push({
|
|
57
65
|
id: 'skill-presence:freshness',
|
|
58
66
|
ok: false,
|
|
59
|
-
message: `Skill presence ${presence.skill} is stale (set ${presence.setAt}, ~${ageHours}h ago); run peaks skill presence:clear if the role has ended`
|
|
67
|
+
message: `Skill presence ${presence.skill} is stale (set ${presence.setAt}, ~${ageHours}h ago); run peaks skill presence:clear if the role has ended, or \`peaks skill lease gc\` to drain the canonical lease`
|
|
60
68
|
});
|
|
61
69
|
}
|
|
62
70
|
else {
|