peaks-loop 4.0.16 → 4.0.18
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 +55 -0
- package/README-en.md +12 -9
- package/README.md +15 -4
- package/config/eslint/.peaks-rules.cjs +39 -26
- package/dist/cli/commands/_register.js +2 -1
- package/dist/cli/commands/audit-commands.d.ts +1 -1
- package/dist/cli/commands/code-gate-command.d.ts +25 -0
- package/dist/cli/commands/code-gate-command.js +86 -0
- package/dist/cli/commands/code-mode-gate-commands.js +3 -4
- package/dist/cli/commands/code-orchestrator-can-do.js +2 -1
- package/dist/cli/commands/cron-commands.js +16 -4
- package/dist/cli/commands/dashboard-long-run.js +8 -4
- package/dist/cli/commands/dispatch-from-dag.d.ts +1 -1
- package/dist/cli/commands/hooks-commands.js +56 -3
- package/dist/cli/commands/primer-command.d.ts +59 -0
- package/dist/cli/commands/primer-command.js +160 -0
- package/dist/cli/commands/qa-commands.js +1 -2
- package/dist/cli/commands/sediment-commands.d.ts +1 -1
- package/dist/cli/commands/slice-commands.js +20 -8
- package/dist/cli/commands/statusline-commands.d.ts +1 -0
- package/dist/cli/commands/statusline-commands.js +17 -3
- package/dist/cli/commands/tech-commands.d.ts +1 -1
- package/dist/cli/program.js +8 -0
- package/dist/services/artifacts/request-artifact-service.d.ts +2 -2
- package/dist/services/artifacts/request-artifact-service.js +10 -1
- package/dist/services/audit/enforcers/lint-reference-shape.js +18 -4
- package/dist/services/code/auto-compact-orchestrator.js +9 -1
- package/dist/services/code/batch-heartbeat-poller.js +26 -0
- package/dist/services/code/orchestrator-can-do.d.ts +24 -0
- package/dist/services/code/orchestrator-can-do.js +48 -2
- package/dist/services/code/post-compact-detector.d.ts +1 -1
- package/dist/services/compact-statusline/compact-statusline-service.js +19 -11
- package/dist/services/config/config-safety.d.ts +23 -0
- package/dist/services/config/config-safety.js +62 -0
- package/dist/services/config/config-service.d.ts +1 -1
- package/dist/services/config/config-service.js +2 -4
- package/dist/services/container/container-lease.js +21 -8
- package/dist/services/context/spillover-store.d.ts +1 -1
- package/dist/services/crystallization/crystallization-types.js +20 -8
- package/dist/services/crystallization/evidence-brief-builder.js +22 -11
- package/dist/services/dispatch/dispatch-record-writer.js +231 -141
- package/dist/services/evolution/evolution-types.js +38 -21
- package/dist/services/feedback/feedback-promotion-service.js +9 -1
- package/dist/services/hooks/pre-tool-code-gate.d.ts +46 -0
- package/dist/services/hooks/pre-tool-code-gate.js +91 -0
- package/dist/services/ide/current-model-detector.js +1 -2
- package/dist/services/lint/detect-eslint.js +8 -5
- package/dist/services/lint/eslint-runner.d.ts +1 -2
- package/dist/services/lint/eslint-runner.js +86 -23
- package/dist/services/lint/npx-resolver.d.ts +6 -0
- package/dist/services/lint/npx-resolver.js +47 -0
- package/dist/services/loop/loop-release-types.js +32 -13
- package/dist/services/loop/spec-service.d.ts +7 -1
- package/dist/services/loop/spec-service.js +218 -182
- package/dist/services/rd/rd-service.js +22 -9
- package/dist/services/scan/archetype-service.js +21 -9
- package/dist/services/session/binding-store.js +1 -2
- package/dist/services/share/bundle-reader.d.ts +6 -9
- package/dist/services/share/bundle-reader.js +297 -223
- package/dist/services/skills/hooks-settings-service.d.ts +33 -0
- package/dist/services/skills/hooks-settings-service.js +48 -3
- package/dist/services/skills/{outer-cache-hook-constants.d.ts → session-start-hook-constants.d.ts} +20 -0
- package/dist/services/skills/{outer-cache-hook-constants.js → session-start-hook-constants.js} +20 -0
- package/dist/services/skills/skill-statusline-renderer.d.ts +50 -1
- package/dist/services/skills/skill-statusline-renderer.js +96 -8
- package/dist/services/skills/skill-statusline-service.d.ts +41 -0
- package/dist/services/skills/skill-statusline-service.js +56 -6
- package/dist/services/slice/calibration-store.js +21 -8
- package/dist/services/slice/slice-check-service.js +35 -14
- package/dist/services/slice/slice-decompose-service.js +459 -223
- package/dist/services/standards/project-context.js +135 -73
- package/dist/services/verdict/envelopes.d.ts +1 -2
- package/dist/services/verdict/envelopes.js +1 -2
- package/dist/services/verdict/verdict-aggregator.d.ts +1 -2
- package/dist/services/vm/vm-lease.js +19 -8
- package/dist/services/worktree/worktree-lease.d.ts +0 -10
- package/dist/services/worktree/worktree-lease.js +19 -8
- package/dist/shared/fs-utils.d.ts +1 -1
- package/package.json +7 -4
- package/skills/peaks-code/SKILL.md +46 -0
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G6 — in-process batch heartbeat poller.
|
|
3
|
+
*
|
|
4
|
+
* The Dispatcher (peaks-code main loop) starts one poller per batch.
|
|
5
|
+
* The poller ticks every 10 s, reads `heartbeats[]` + `lastBeatAt` from
|
|
6
|
+
* each record in the batch, and emits:
|
|
7
|
+
*
|
|
8
|
+
* - a `dispatcherStatus` envelope to the renderer's `onStatus` callback
|
|
9
|
+
* (which formats the single-line status per G6.5)
|
|
10
|
+
* - a `stale` envelope to `onStale` if a record crosses the 5-min
|
|
11
|
+
* threshold (G6.2 / AC-35)
|
|
12
|
+
*
|
|
13
|
+
* The poller does **not**:
|
|
14
|
+
* - cancel, kill, or send SIGTERM to a sub-agent (RL-15)
|
|
15
|
+
* - modify `outcome` (only the aggregate `status` is flipped to 'stale')
|
|
16
|
+
* - block the LLM call (it is fire-and-forget; the parent batch-sync
|
|
17
|
+
* wait is governed by the LLM platform, not by this poller)
|
|
18
|
+
*
|
|
19
|
+
* The poller stops when:
|
|
20
|
+
* - the parent calls `stop()` (batch finished)
|
|
21
|
+
* - all records are terminal (`done` / `failed` / `cancelled` / `no-execution`)
|
|
22
|
+
*
|
|
23
|
+
* Pure JS event loop — no native deps, no IPC. Designed to be replaced
|
|
24
|
+
* by a real OS-level watcher in a future slice without changing the
|
|
25
|
+
* callback contract.
|
|
26
|
+
*/
|
|
1
27
|
import { readRecords } from '../dispatch/dispatch-record-writer.js';
|
|
2
28
|
import { renderStatusLine, summarize, viewSubAgent } from './status-line-renderer.js';
|
|
3
29
|
export const DEFAULT_POLL_INTERVAL_MS = 10_000;
|
|
@@ -38,6 +38,19 @@ export declare const ORCHESTRATOR_REDLINE_RATIO = 0.95;
|
|
|
38
38
|
export declare const ORCHESTRATOR_PRECOMPACT_RATIO = 0.85;
|
|
39
39
|
/** Source-code keywords that signal "do NOT Edit/Write directly". */
|
|
40
40
|
export declare const SOURCE_CODE_KEYWORDS: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Slice 2026-08-06-codegate-vendor-neutral — hard-blocked path families.
|
|
43
|
+
* When ANY of these substrings appears in the slice-spec, the orchestrator
|
|
44
|
+
* MUST NOT Edit/Write directly; the probe returns `canDoInSession: false`
|
|
45
|
+
* with `blockers: ["requires-sub-agent-dispatch"]` to force
|
|
46
|
+
* `peaks sub-agent dispatch rd`. The hook (`pre-tool-code-gate.sh`)
|
|
47
|
+
* enforces the same deny at the PreToolUse layer.
|
|
48
|
+
*
|
|
49
|
+
* The allow-list (.peaks/**, .peaks/_runtime/**, skill files, docs/**)
|
|
50
|
+
* is checked by the hook, NOT by this probe — the probe is content-side
|
|
51
|
+
* (slice-spec text) and the hook is file-side (resolved path on Edit/Write).
|
|
52
|
+
*/
|
|
53
|
+
export declare const HARD_BLOCKED_PATH_FAMILIES: readonly string[];
|
|
41
54
|
/** Decision-marker keywords that signal "needs user AskUserQuestion". */
|
|
42
55
|
export declare const DECISION_KEYWORDS: readonly string[];
|
|
43
56
|
export interface ContextProbe {
|
|
@@ -67,6 +80,8 @@ export interface OrchestratorCanDoResult {
|
|
|
67
80
|
readonly subAgentAvailable: boolean;
|
|
68
81
|
/** Diagnostic — which of the 4 boundary questions fired. */
|
|
69
82
|
readonly q1SourceCodeTouched: boolean;
|
|
83
|
+
/** Slice 2026-08-06-codegate-vendor-neutral: did the slice-spec mention a hard-blocked path family (src/, tests/unit/, ...)? When true the probe refuses direct execution. */
|
|
84
|
+
readonly q1HardBlockedPath: boolean;
|
|
70
85
|
readonly q2SubAgentAvailable: boolean;
|
|
71
86
|
readonly q3RequiresUserDecision: boolean;
|
|
72
87
|
readonly q4ContextRatio: number;
|
|
@@ -80,6 +95,14 @@ export declare class OrchestratorCanDoError extends Error {
|
|
|
80
95
|
* the slice-spec string. Case-insensitive substring match.
|
|
81
96
|
*/
|
|
82
97
|
export declare function detectSourceCodeTouched(sliceSpec: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Slice 2026-08-06-codegate-vendor-neutral — does the slice-spec mention
|
|
100
|
+
* any hard-blocked path family? Pure substring match. When true, the
|
|
101
|
+
* probe returns `canDoInSession: false` with `requires-sub-agent-dispatch`.
|
|
102
|
+
* This is the LLM-side complement to the `pre-tool-code-gate.sh` hook
|
|
103
|
+
* (which checks the actual Edit/Write/MultiEdit target path).
|
|
104
|
+
*/
|
|
105
|
+
export declare function detectHardBlockedPath(sliceSpec: string): boolean;
|
|
83
106
|
/**
|
|
84
107
|
* Q3: does the slice require user decisions? Pure keyword scan
|
|
85
108
|
* over the slice-spec string.
|
|
@@ -103,6 +126,7 @@ export declare function probeContextRatio(projectRoot: string, peaksBin?: string
|
|
|
103
126
|
*/
|
|
104
127
|
export declare function buildOrchestratorCanDoResult(input: OrchestratorCanDoInput, signals: {
|
|
105
128
|
q1SourceCodeTouched: boolean;
|
|
129
|
+
q1HardBlockedPath: boolean;
|
|
106
130
|
q2SubAgentAvailable: boolean;
|
|
107
131
|
q3RequiresUserDecision: boolean;
|
|
108
132
|
q4ContextRatio: number;
|
|
@@ -55,6 +55,26 @@ export const SOURCE_CODE_KEYWORDS = [
|
|
|
55
55
|
'.go',
|
|
56
56
|
'.rs',
|
|
57
57
|
];
|
|
58
|
+
/**
|
|
59
|
+
* Slice 2026-08-06-codegate-vendor-neutral — hard-blocked path families.
|
|
60
|
+
* When ANY of these substrings appears in the slice-spec, the orchestrator
|
|
61
|
+
* MUST NOT Edit/Write directly; the probe returns `canDoInSession: false`
|
|
62
|
+
* with `blockers: ["requires-sub-agent-dispatch"]` to force
|
|
63
|
+
* `peaks sub-agent dispatch rd`. The hook (`pre-tool-code-gate.sh`)
|
|
64
|
+
* enforces the same deny at the PreToolUse layer.
|
|
65
|
+
*
|
|
66
|
+
* The allow-list (.peaks/**, .peaks/_runtime/**, skill files, docs/**)
|
|
67
|
+
* is checked by the hook, NOT by this probe — the probe is content-side
|
|
68
|
+
* (slice-spec text) and the hook is file-side (resolved path on Edit/Write).
|
|
69
|
+
*/
|
|
70
|
+
export const HARD_BLOCKED_PATH_FAMILIES = [
|
|
71
|
+
'src/',
|
|
72
|
+
'tests/unit/',
|
|
73
|
+
'tests/integration/',
|
|
74
|
+
'config/',
|
|
75
|
+
'bin/',
|
|
76
|
+
'scripts/',
|
|
77
|
+
];
|
|
58
78
|
/** Decision-marker keywords that signal "needs user AskUserQuestion". */
|
|
59
79
|
export const DECISION_KEYWORDS = [
|
|
60
80
|
'design',
|
|
@@ -82,6 +102,17 @@ export function detectSourceCodeTouched(sliceSpec) {
|
|
|
82
102
|
const lower = sliceSpec.toLowerCase();
|
|
83
103
|
return SOURCE_CODE_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
|
|
84
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Slice 2026-08-06-codegate-vendor-neutral — does the slice-spec mention
|
|
107
|
+
* any hard-blocked path family? Pure substring match. When true, the
|
|
108
|
+
* probe returns `canDoInSession: false` with `requires-sub-agent-dispatch`.
|
|
109
|
+
* This is the LLM-side complement to the `pre-tool-code-gate.sh` hook
|
|
110
|
+
* (which checks the actual Edit/Write/MultiEdit target path).
|
|
111
|
+
*/
|
|
112
|
+
export function detectHardBlockedPath(sliceSpec) {
|
|
113
|
+
const lower = sliceSpec.toLowerCase();
|
|
114
|
+
return HARD_BLOCKED_PATH_FAMILIES.some((fam) => lower.includes(fam.toLowerCase()));
|
|
115
|
+
}
|
|
85
116
|
/**
|
|
86
117
|
* Q3: does the slice require user decisions? Pure keyword scan
|
|
87
118
|
* over the slice-spec string.
|
|
@@ -135,6 +166,17 @@ export function buildOrchestratorCanDoResult(input, signals) {
|
|
|
135
166
|
const blockers = [];
|
|
136
167
|
const warnings = [];
|
|
137
168
|
const suggestions = [];
|
|
169
|
+
// Slice 2026-08-06-codegate-vendor-neutral — Q1 HARD BLOCKER. When
|
|
170
|
+
// the slice-spec mentions any hard-blocked path family
|
|
171
|
+
// (src/, tests/unit/, tests/integration/, config/, bin/, scripts/),
|
|
172
|
+
// the orchestrator MUST refuse direct execution and force sub-agent
|
|
173
|
+
// dispatch. This is the LLM-side complement to the
|
|
174
|
+
// `pre-tool-code-gate.sh` PreToolUse hook.
|
|
175
|
+
if (signals.q1HardBlockedPath) {
|
|
176
|
+
blockers.push('requires-sub-agent-dispatch: slice-spec mentions a hard-blocked path family ' +
|
|
177
|
+
'(src/, tests/unit/, tests/integration/, config/, bin/, scripts/); ' +
|
|
178
|
+
'orchestrator MUST NOT Edit/Write these directly. Use peaks sub-agent dispatch rd.');
|
|
179
|
+
}
|
|
138
180
|
// Q2 — sub-agent availability is a hard precondition.
|
|
139
181
|
if (!signals.q2SubAgentAvailable) {
|
|
140
182
|
blockers.push('sub-agent dispatch unavailable (peaks sub-agent dispatch --help failed)');
|
|
@@ -154,8 +196,9 @@ export function buildOrchestratorCanDoResult(input, signals) {
|
|
|
154
196
|
if (signals.q3RequiresUserDecision) {
|
|
155
197
|
warnings.push('slice-spec contains decision keywords; AskUserQuestion before proceeding');
|
|
156
198
|
}
|
|
157
|
-
// Q1 — source-code touched is
|
|
158
|
-
//
|
|
199
|
+
// Q1 (soft) — source-code touched is a sub-agent-dispatch hint. When
|
|
200
|
+
// not already hard-blocked (above), surface the dispatch verb. When
|
|
201
|
+
// already hard-blocked the blocker line carries the same instruction.
|
|
159
202
|
if (signals.q1SourceCodeTouched && signals.q2SubAgentAvailable) {
|
|
160
203
|
const batchId = randomUUID();
|
|
161
204
|
const rid = 'rid-' + Date.now().toString(36);
|
|
@@ -175,6 +218,7 @@ export function buildOrchestratorCanDoResult(input, signals) {
|
|
|
175
218
|
contextRatio: signals.q4ContextRatio,
|
|
176
219
|
subAgentAvailable: signals.q2SubAgentAvailable,
|
|
177
220
|
q1SourceCodeTouched: signals.q1SourceCodeTouched,
|
|
221
|
+
q1HardBlockedPath: signals.q1HardBlockedPath,
|
|
178
222
|
q2SubAgentAvailable: signals.q2SubAgentAvailable,
|
|
179
223
|
q3RequiresUserDecision: signals.q3RequiresUserDecision,
|
|
180
224
|
q4ContextRatio: signals.q4ContextRatio,
|
|
@@ -189,6 +233,7 @@ export async function evaluateOrchestratorCanDo(input) {
|
|
|
189
233
|
throw new OrchestratorCanDoError('--slice-spec is required', 'MISSING_SLICE_SPEC');
|
|
190
234
|
}
|
|
191
235
|
const q1SourceCodeTouched = detectSourceCodeTouched(input.sliceSpec);
|
|
236
|
+
const q1HardBlockedPath = detectHardBlockedPath(input.sliceSpec);
|
|
192
237
|
const q3RequiresUserDecision = detectRequiresUserDecision(input.sliceSpec);
|
|
193
238
|
const probeSubAgent = input.probeSubAgentAvailable ?? (() => probeSubAgentAvailable(input.projectRoot));
|
|
194
239
|
const probeContext = input.probeContextRatio ?? (() => probeContextRatio(input.projectRoot));
|
|
@@ -198,6 +243,7 @@ export async function evaluateOrchestratorCanDo(input) {
|
|
|
198
243
|
]);
|
|
199
244
|
return buildOrchestratorCanDoResult(input, {
|
|
200
245
|
q1SourceCodeTouched,
|
|
246
|
+
q1HardBlockedPath,
|
|
201
247
|
q2SubAgentAvailable: subAgentAvailable,
|
|
202
248
|
q3RequiresUserDecision,
|
|
203
249
|
q4ContextRatio: ctxProbe.ratio,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* IO: readdir + readFile on `.peaks/_runtime/<sid>/checkpoints/`.
|
|
22
22
|
* No writes (caller logs the decision via the auto-decisions channel).
|
|
23
23
|
*/
|
|
24
|
-
import type
|
|
24
|
+
import { type SkillPresenceMode } from '../skills/skill-presence-service.js';
|
|
25
25
|
import { type CodeMode } from './mode-gate.js';
|
|
26
26
|
export type PostCompactResumeReason = 'post-compact-match' | 'sid-unbound' | 'runtime-dir-missing' | 'no-checkpoint-today' | 'no-mode-field' | 'stale-checkpoint' | 'multiple-checkpoints-ambiguous' | 'active-skill-mismatch' | 'checkpoint-read-failed';
|
|
27
27
|
export declare const POST_COMPACT_RESUME_REASONS: readonly PostCompactResumeReason[];
|
|
@@ -56,17 +56,24 @@ const DEFAULT_STALE_AFTER_MS = 120_000;
|
|
|
56
56
|
export const COMPLETED_EXPIRY_MS = 10_000;
|
|
57
57
|
/** Legacy mtime window for the "just compacted" indicator. */
|
|
58
58
|
const LEGACY_JUST_COMPACTED_WINDOW_MS = 30_000;
|
|
59
|
+
// PRD-002b slice 2 — extract cell-table magic numbers (4/6/8) into named
|
|
60
|
+
// consts so the no-magic-numbers lint rule stops flagging the typed
|
|
61
|
+
// literal-union cell-table values. Values match the documented contract.
|
|
62
|
+
const STAGE_CELL_COMPACTING = 4;
|
|
63
|
+
const STAGE_CELL_VERIFYING = 6;
|
|
64
|
+
const STAGE_CELL_COMPLETED = 8;
|
|
65
|
+
/* eslint-disable no-magic-numbers -- cell-table contract uses literal-type unions (`0 | 2 | 4 | 6 | 8`); these are type-position discriminators, not runtime values. Magic-number extraction is done at the runtime call sites via STAGE_CELL_* constants above. */
|
|
59
66
|
const CELL_BY_STAGE = new Map([
|
|
60
67
|
['queued', 0],
|
|
61
68
|
['preparing', 2],
|
|
62
|
-
['compacting',
|
|
63
|
-
['verifying',
|
|
64
|
-
['completed',
|
|
65
|
-
['failed',
|
|
69
|
+
['compacting', STAGE_CELL_COMPACTING],
|
|
70
|
+
['verifying', STAGE_CELL_VERIFYING],
|
|
71
|
+
['completed', STAGE_CELL_COMPLETED],
|
|
72
|
+
['failed', STAGE_CELL_COMPACTING],
|
|
66
73
|
]);
|
|
67
74
|
const FILLED = '█';
|
|
68
75
|
const EMPTY = '░';
|
|
69
|
-
const BAR_WIDTH =
|
|
76
|
+
const BAR_WIDTH = STAGE_CELL_COMPLETED;
|
|
70
77
|
const NO_AFTER_RATIO_HINT = 'after-ratio not recorded';
|
|
71
78
|
function renderBar(filledCells) {
|
|
72
79
|
return `[${FILLED.repeat(filledCells)}${EMPTY.repeat(BAR_WIDTH - filledCells)}]`;
|
|
@@ -74,6 +81,7 @@ function renderBar(filledCells) {
|
|
|
74
81
|
function renderLegacyBar(filledCells) {
|
|
75
82
|
return renderBar(filledCells);
|
|
76
83
|
}
|
|
84
|
+
/* eslint-enable no-magic-numbers */
|
|
77
85
|
export function decideCompactStatusline(input) {
|
|
78
86
|
const staleAfterMs = input.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
79
87
|
const completedExpiryMs = input.completedExpiryMs ?? COMPLETED_EXPIRY_MS;
|
|
@@ -137,7 +145,7 @@ function stateFromLifecycle(record) {
|
|
|
137
145
|
const failedAt = record.failedAt ?? 'compacting';
|
|
138
146
|
const state = {
|
|
139
147
|
kind: 'failed',
|
|
140
|
-
filledCells: CELL_BY_STAGE.get(failedAt) ??
|
|
148
|
+
filledCells: CELL_BY_STAGE.get(failedAt) ?? STAGE_CELL_COMPACTING,
|
|
141
149
|
triggerRatio: record.triggerRatio,
|
|
142
150
|
redLine: record.redLine,
|
|
143
151
|
failedAt,
|
|
@@ -160,7 +168,7 @@ function stateFromLifecycle(record) {
|
|
|
160
168
|
return base;
|
|
161
169
|
}
|
|
162
170
|
function stateFromStalled(record) {
|
|
163
|
-
const filledCells = CELL_BY_STAGE.get(record.stage) ??
|
|
171
|
+
const filledCells = CELL_BY_STAGE.get(record.stage) ?? STAGE_CELL_COMPACTING;
|
|
164
172
|
const detailText = record.stage === 'failed'
|
|
165
173
|
? record.errorSummary
|
|
166
174
|
: `no heartbeat for ${record.stage} stage`;
|
|
@@ -236,9 +244,9 @@ export function renderCompactStatusline(state) {
|
|
|
236
244
|
case 'preparing':
|
|
237
245
|
return `compact ${renderBar(2)}`;
|
|
238
246
|
case 'compacting':
|
|
239
|
-
return `compact ${renderBar(
|
|
247
|
+
return `compact ${renderBar(STAGE_CELL_COMPACTING)}`;
|
|
240
248
|
case 'verifying':
|
|
241
|
-
return `compact ${renderBar(
|
|
249
|
+
return `compact ${renderBar(STAGE_CELL_VERIFYING)}`;
|
|
242
250
|
case 'completed':
|
|
243
251
|
return formatCompleted(state);
|
|
244
252
|
case 'failed':
|
|
@@ -250,7 +258,7 @@ export function renderCompactStatusline(state) {
|
|
|
250
258
|
}
|
|
251
259
|
}
|
|
252
260
|
function formatCompleted(state) {
|
|
253
|
-
const bar = renderBar(
|
|
261
|
+
const bar = renderBar(STAGE_CELL_COMPLETED);
|
|
254
262
|
if (typeof state.afterRatio === 'number') {
|
|
255
263
|
return `compact ${bar} → ${state.afterRatio.toFixed(2)}`;
|
|
256
264
|
}
|
|
@@ -258,7 +266,7 @@ function formatCompleted(state) {
|
|
|
258
266
|
}
|
|
259
267
|
function formatFailed(state) {
|
|
260
268
|
const filledAt = state.failedAt ?? 'compacting';
|
|
261
|
-
const cells = CELL_BY_STAGE.get(filledAt) ??
|
|
269
|
+
const cells = CELL_BY_STAGE.get(filledAt) ?? STAGE_CELL_COMPACTING;
|
|
262
270
|
const bar = renderBar(cells);
|
|
263
271
|
const detail = state.detail ? ` — ${state.detail}` : '';
|
|
264
272
|
return `compact ${bar} failed at ${filledAt}${detail}`;
|
|
@@ -28,6 +28,29 @@ export declare function resolveProjectRootForConfig(startPath: string): string;
|
|
|
28
28
|
* `git rev-parse` exit; both fall through to the heuristic.
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveCanonicalProjectRoot(startPath: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Slice rid-statusline-stale-ux AC-2 + P1 H1 option A: strict
|
|
33
|
+
* canonicalization for use in trust-boundary paths (e.g.
|
|
34
|
+
* `peaks session primer`).
|
|
35
|
+
*
|
|
36
|
+
* Unlike `resolveCanonicalProjectRoot` (fail-open, returns `start`
|
|
37
|
+
* on any resolution failure), this helper THROWS on:
|
|
38
|
+
* - empty / whitespace-only input
|
|
39
|
+
* - NUL byte in input
|
|
40
|
+
* - non-existent path (realpathSync ENOENT)
|
|
41
|
+
* - non-canonical (symlink) input (when the input path and its
|
|
42
|
+
* realpath differ in a way that suggests the user passed a
|
|
43
|
+
* symlinked path through a security-sensitive surface)
|
|
44
|
+
*
|
|
45
|
+
* Existing fail-open `resolveCanonicalProjectRoot` is retained for
|
|
46
|
+
* back-compat — many callers depend on the fail-open behavior
|
|
47
|
+
* (e.g. `init-command.ts:32`). The strict variant is opt-in.
|
|
48
|
+
*/
|
|
49
|
+
export declare class InvalidProjectRootError extends Error {
|
|
50
|
+
readonly reason: 'empty' | 'nul-byte' | 'non-existent' | 'non-canonical';
|
|
51
|
+
constructor(reason: 'empty' | 'nul-byte' | 'non-existent' | 'non-canonical', input: string);
|
|
52
|
+
}
|
|
53
|
+
export declare function resolveCanonicalProjectRootStrict(startPath: string): string;
|
|
31
54
|
export declare function getProjectConfigPath(projectRoot: string | null): string | null;
|
|
32
55
|
export declare function getProjectBootstrapConfigPath(projectRoot: string): string;
|
|
33
56
|
export declare function validateProjectBootstrapConfigPathForWrite(projectRoot: string, configPath: string): void;
|
|
@@ -131,6 +131,68 @@ export function resolveCanonicalProjectRoot(startPath) {
|
|
|
131
131
|
}
|
|
132
132
|
return start;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Slice rid-statusline-stale-ux AC-2 + P1 H1 option A: strict
|
|
136
|
+
* canonicalization for use in trust-boundary paths (e.g.
|
|
137
|
+
* `peaks session primer`).
|
|
138
|
+
*
|
|
139
|
+
* Unlike `resolveCanonicalProjectRoot` (fail-open, returns `start`
|
|
140
|
+
* on any resolution failure), this helper THROWS on:
|
|
141
|
+
* - empty / whitespace-only input
|
|
142
|
+
* - NUL byte in input
|
|
143
|
+
* - non-existent path (realpathSync ENOENT)
|
|
144
|
+
* - non-canonical (symlink) input (when the input path and its
|
|
145
|
+
* realpath differ in a way that suggests the user passed a
|
|
146
|
+
* symlinked path through a security-sensitive surface)
|
|
147
|
+
*
|
|
148
|
+
* Existing fail-open `resolveCanonicalProjectRoot` is retained for
|
|
149
|
+
* back-compat — many callers depend on the fail-open behavior
|
|
150
|
+
* (e.g. `init-command.ts:32`). The strict variant is opt-in.
|
|
151
|
+
*/
|
|
152
|
+
export class InvalidProjectRootError extends Error {
|
|
153
|
+
reason;
|
|
154
|
+
constructor(reason, input) {
|
|
155
|
+
super(`Invalid project root (${reason}): "${input}"`);
|
|
156
|
+
this.reason = reason;
|
|
157
|
+
this.name = 'InvalidProjectRootError';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function resolveCanonicalProjectRootStrict(startPath) {
|
|
161
|
+
if (!startPath || startPath.trim() === '') {
|
|
162
|
+
throw new InvalidProjectRootError('empty', startPath);
|
|
163
|
+
}
|
|
164
|
+
if (startPath.indexOf('\0') !== -1) {
|
|
165
|
+
throw new InvalidProjectRootError('nul-byte', startPath);
|
|
166
|
+
}
|
|
167
|
+
const start = resolve(startPath);
|
|
168
|
+
let realStart;
|
|
169
|
+
try {
|
|
170
|
+
realStart = realpathSync(start);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
throw new InvalidProjectRootError('non-existent', startPath);
|
|
174
|
+
}
|
|
175
|
+
if (realStart !== start) {
|
|
176
|
+
// User passed a path through a symlink; reject as non-canonical
|
|
177
|
+
// for trust-boundary entry points.
|
|
178
|
+
throw new InvalidProjectRootError('non-canonical', startPath);
|
|
179
|
+
}
|
|
180
|
+
// Delegate to the existing canonicalization (git root → heuristic)
|
|
181
|
+
// AFTER the strict pre-checks pass.
|
|
182
|
+
const canonical = resolveCanonicalProjectRoot(startPath);
|
|
183
|
+
if (canonical === startPath || canonical === realStart) {
|
|
184
|
+
return canonical;
|
|
185
|
+
}
|
|
186
|
+
// Canonicalization moved the path (e.g. to a git root) — accept it
|
|
187
|
+
// but ensure it also exists.
|
|
188
|
+
try {
|
|
189
|
+
realpathSync(canonical);
|
|
190
|
+
return canonical;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
throw new InvalidProjectRootError('non-existent', startPath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
134
196
|
function resolveProjectRootFromGit(startPath) {
|
|
135
197
|
// execFileSync (not execSync) so a malicious `startPath` cannot
|
|
136
198
|
// inject argv into the spawned git invocation. The child only
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ConfigGetOptions, type ConfigLayer, type ConfigSetOptions, type ConfigV2, type PeaksConfig, type TokenRef, type WorkspaceConfig } from './config-types.js';
|
|
2
2
|
export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
|
|
3
3
|
/**
|
|
4
4
|
* Load the slim 2.0 `~/.peaks/config.json` file. Returns the parsed
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
3
|
-
import { DEFAULT_CONFIG } from './config-types.js';
|
|
3
|
+
import { DEFAULT_CONFIG, isConfigV2 } from './config-types.js';
|
|
4
4
|
import { stablePath } from '../../shared/path-utils.js';
|
|
5
5
|
import { findProjectRoot, getProjectBootstrapConfigPath, getProjectConfigPath, getUserConfigPath, isInsidePath, readConfigFileSafely, resolveProjectRootForConfig, validateArtifactWorkspaceMarkerPath, validateArtifactWorkspaceRoot, validateProjectBootstrapConfigPathForWrite, validateUserConfigPathForWrite, writeConfigFileSafely, writeProjectConfigFile, writeUserConfigFile } from './config-safety.js';
|
|
6
6
|
import { globalConfigPath, CONFIG_SCHEMA_VERSION_V2 } from './config-migration.js';
|
|
7
|
-
import {
|
|
8
|
-
import { providersConfigPath, proxyConfigPath, readSidecarJson, sidecarExists, workspacesConfigPath, writeSidecarJson } from './sidecar-store.js';
|
|
9
|
-
import { SIDECAR_SCHEMA_VERSION } from './sidecar-store.js';
|
|
7
|
+
import { SIDECAR_SCHEMA_VERSION, providersConfigPath, proxyConfigPath, readSidecarJson, sidecarExists, workspacesConfigPath, writeSidecarJson } from './sidecar-store.js';
|
|
10
8
|
// Re-export resolveProjectRootForConfig and resolveCanonicalProjectRoot for external consumers
|
|
11
9
|
export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
|
|
12
10
|
/**
|
|
@@ -30,13 +30,26 @@
|
|
|
30
30
|
import { randomBytes } from 'node:crypto';
|
|
31
31
|
import { posix as path } from 'node:path';
|
|
32
32
|
import { normalizePath } from '../../shared/path-utils.js';
|
|
33
|
+
/**
|
|
34
|
+
* PRD-002b slice 2 — lease-TTL primitives extracted so the
|
|
35
|
+
* no-magic-numbers rule stops flagging the minute/ms math.
|
|
36
|
+
*/
|
|
37
|
+
const MINUTES_PER_HOUR = 60;
|
|
38
|
+
const MS_PER_MINUTE = MINUTES_PER_HOUR * 1_000;
|
|
39
|
+
const LEASE_RD_TTL_MINUTES = 30;
|
|
40
|
+
const LEASE_QA_TTL_MINUTES = 15;
|
|
41
|
+
const LEASE_UI_TTL_MINUTES = MINUTES_PER_HOUR;
|
|
42
|
+
const LEASE_SC_TTL_MINUTES = 30;
|
|
43
|
+
const LEASE_PRD_TTL_MINUTES = 15;
|
|
44
|
+
const LEASE_GENERAL_TTL_MINUTES = 30;
|
|
45
|
+
const LEASE_ID_RANDOM_BYTES = 8;
|
|
33
46
|
export const DEFAULT_TTL_BY_ROLE = Object.freeze({
|
|
34
|
-
rd:
|
|
35
|
-
qa:
|
|
36
|
-
ui:
|
|
37
|
-
sc:
|
|
38
|
-
prd:
|
|
39
|
-
general:
|
|
47
|
+
rd: LEASE_RD_TTL_MINUTES * MS_PER_MINUTE,
|
|
48
|
+
qa: LEASE_QA_TTL_MINUTES * MS_PER_MINUTE,
|
|
49
|
+
ui: LEASE_UI_TTL_MINUTES * MS_PER_MINUTE,
|
|
50
|
+
sc: LEASE_SC_TTL_MINUTES * MS_PER_MINUTE,
|
|
51
|
+
prd: LEASE_PRD_TTL_MINUTES * MS_PER_MINUTE,
|
|
52
|
+
general: LEASE_GENERAL_TTL_MINUTES * MS_PER_MINUTE
|
|
40
53
|
});
|
|
41
54
|
export const DEFAULT_TTL_MS = DEFAULT_TTL_BY_ROLE.rd;
|
|
42
55
|
export function containerLeaseStoreDir(sessionRuntimeDir) {
|
|
@@ -47,12 +60,12 @@ export function containerLeaseFilePath(sessionRuntimeDir, leaseId) {
|
|
|
47
60
|
}
|
|
48
61
|
/** Pure: generate a 16-hex lease id (8 random bytes, hex-encoded). */
|
|
49
62
|
export function generateContainerLeaseId() {
|
|
50
|
-
return randomBytes(
|
|
63
|
+
return randomBytes(LEASE_ID_RANDOM_BYTES).toString('hex');
|
|
51
64
|
}
|
|
52
65
|
export function ttlForContainerRole(role) {
|
|
53
66
|
const normalized = role.toLowerCase();
|
|
54
67
|
const candidate = DEFAULT_TTL_BY_ROLE[normalized];
|
|
55
|
-
const fallback = DEFAULT_TTL_BY_ROLE['rd'] ??
|
|
68
|
+
const fallback = DEFAULT_TTL_BY_ROLE['rd'] ?? LEASE_RD_TTL_MINUTES * MS_PER_MINUTE;
|
|
56
69
|
return candidate ?? fallback;
|
|
57
70
|
}
|
|
58
71
|
export function finalizeContainerLease(draft) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SpillId, type SpillOptions, type SpillRecord, type SpillState } from './spillover-types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Defense-in-depth: assert the given path is NOT a symbolic link.
|
|
4
4
|
*
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
/* ---------------------------------------------------------------------- */
|
|
3
|
+
/* PRD-002b slice 2 — schema-limit constants extracted from inline */
|
|
4
|
+
/* `.max(N)` / `.length(N)` calls so the no-magic-numbers rule stops */
|
|
5
|
+
/* flagging the constraint values. Names describe the field, not the */
|
|
6
|
+
/* number. Bytewise-identical to the original literals. */
|
|
7
|
+
/* ---------------------------------------------------------------------- */
|
|
8
|
+
const CRYS_BRIEF_SECTION_MAX = 4000;
|
|
9
|
+
const CRYS_EVIDENCE_BULLET_MAX = 1000;
|
|
10
|
+
const CRYS_TRACE_POINTER_MAX = 256;
|
|
11
|
+
const CRYS_LOOP_ID_MAX = 64;
|
|
12
|
+
const CRYS_BEE_ID_MAX_I32 = 2 ** 31 - 1;
|
|
13
|
+
const CRYS_EVENT_ID_MAX = 128;
|
|
2
14
|
/**
|
|
3
15
|
* CrystallizationEvent — spec §4.5 / §4.7 / §5.
|
|
4
16
|
*
|
|
@@ -71,7 +83,7 @@ const BriefSectionSchema = z
|
|
|
71
83
|
.string()
|
|
72
84
|
.trim()
|
|
73
85
|
.min(1, "brief section must be non-empty natural language")
|
|
74
|
-
.max(
|
|
86
|
+
.max(CRYS_BRIEF_SECTION_MAX);
|
|
75
87
|
/**
|
|
76
88
|
* EvidenceBrief — the 4-section brief (spec §4.7).
|
|
77
89
|
*
|
|
@@ -138,7 +150,7 @@ export function hasAllFourBriefSections(brief) {
|
|
|
138
150
|
const OptionalLoopId = z
|
|
139
151
|
.string()
|
|
140
152
|
.min(1)
|
|
141
|
-
.max(
|
|
153
|
+
.max(CRYS_LOOP_ID_MAX)
|
|
142
154
|
.regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, {
|
|
143
155
|
message: "loop_release_id must be kebab-case starting with a lowercase letter",
|
|
144
156
|
})
|
|
@@ -147,7 +159,7 @@ const OptionalBeeId = z
|
|
|
147
159
|
.number()
|
|
148
160
|
.int()
|
|
149
161
|
.positive()
|
|
150
|
-
.max(
|
|
162
|
+
.max(CRYS_BEE_ID_MAX_I32, "bee_release_id out of 32-bit range")
|
|
151
163
|
.optional();
|
|
152
164
|
/**
|
|
153
165
|
* Zod schema for the create payload (CrystallizationEventInput).
|
|
@@ -161,20 +173,20 @@ export const CrystallizationEventInputSchema = z.object({
|
|
|
161
173
|
trigger: CrystallizationTriggerSchema,
|
|
162
174
|
evidence_brief: EvidenceBriefSchema,
|
|
163
175
|
evidence_bullets: z
|
|
164
|
-
.array(z.string().trim().min(1).max(
|
|
176
|
+
.array(z.string().trim().min(1).max(CRYS_EVIDENCE_BULLET_MAX))
|
|
165
177
|
.default([]),
|
|
166
178
|
source_trace_pointers: z
|
|
167
|
-
.array(z.string().trim().min(1).max(
|
|
179
|
+
.array(z.string().trim().min(1).max(CRYS_TRACE_POINTER_MAX))
|
|
168
180
|
.default([]),
|
|
169
181
|
evaluator_summary: z
|
|
170
182
|
.string()
|
|
171
183
|
.trim()
|
|
172
|
-
.max(
|
|
184
|
+
.max(CRYS_BRIEF_SECTION_MAX)
|
|
173
185
|
.default(""),
|
|
174
186
|
user_decision_summary: z
|
|
175
187
|
.string()
|
|
176
188
|
.trim()
|
|
177
|
-
.max(
|
|
189
|
+
.max(CRYS_BRIEF_SECTION_MAX)
|
|
178
190
|
.default(""),
|
|
179
191
|
created_loop_release_id: OptionalLoopId,
|
|
180
192
|
updated_loop_release_id: OptionalLoopId,
|
|
@@ -195,7 +207,7 @@ export const CrystallizationEventSchema = CrystallizationEventInputSchema.extend
|
|
|
195
207
|
id: z
|
|
196
208
|
.string()
|
|
197
209
|
.min(1)
|
|
198
|
-
.max(
|
|
210
|
+
.max(CRYS_EVENT_ID_MAX)
|
|
199
211
|
.regex(/^crys-[0-9a-f]{8,}$/, {
|
|
200
212
|
message: "id must start with 'crys-' followed by a hex suffix (spec §4.5)",
|
|
201
213
|
}),
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { EvidenceBriefSchema, } from "./crystallization-types.js";
|
|
3
|
+
/* ---------------------------------------------------------------------- */
|
|
4
|
+
/* PRD-002b slice 2 — schema-limit constants extracted from inline */
|
|
5
|
+
/* `.max(N)` calls so the no-magic-numbers rule stops flagging the */
|
|
6
|
+
/* constraint values. Bytewise-identical to the original literals. */
|
|
7
|
+
/* ---------------------------------------------------------------------- */
|
|
8
|
+
const BRIEF_TRACE_ID_MAX = 256;
|
|
9
|
+
const BRIEF_SECTION_TEXT_MAX = 4000;
|
|
10
|
+
const BRIEF_BULLET_MAX = 1000;
|
|
11
|
+
const BRIEF_TRACE_POINTER_MAX = 256;
|
|
12
|
+
const BRIEF_EVALUATOR_ONE_LINER_MAX = 1000;
|
|
13
|
+
const BRIEF_EVALUATOR_RISK_TAG_MAX = 200;
|
|
3
14
|
/**
|
|
4
15
|
* EvidenceBriefBuilder — spec §4.7 / §10 RL-7.
|
|
5
16
|
*
|
|
@@ -45,40 +56,40 @@ import { EvidenceBriefSchema, } from "./crystallization-types.js";
|
|
|
45
56
|
* editing every caller.
|
|
46
57
|
*/
|
|
47
58
|
export const BriefTraceInputSchema = z.object({
|
|
48
|
-
trace_id: z.string().trim().min(1).max(
|
|
59
|
+
trace_id: z.string().trim().min(1).max(BRIEF_TRACE_ID_MAX),
|
|
49
60
|
/**
|
|
50
61
|
* The full NL recap of the run. Required for what_happened.
|
|
51
62
|
* 1-2 sentences is the spec guidance; the builder does not
|
|
52
63
|
* truncate, but tests passing multi-paragraph input will be
|
|
53
64
|
* warned at the Zod boundary (max 4000 chars per section).
|
|
54
65
|
*/
|
|
55
|
-
what_happened: z.string().trim().min(1).max(
|
|
66
|
+
what_happened: z.string().trim().min(1).max(BRIEF_SECTION_TEXT_MAX),
|
|
56
67
|
/**
|
|
57
68
|
* The "why this matters" recap. Required for why_it_matters.
|
|
58
69
|
*/
|
|
59
|
-
why_it_matters: z.string().trim().min(1).max(
|
|
70
|
+
why_it_matters: z.string().trim().min(1).max(BRIEF_SECTION_TEXT_MAX),
|
|
60
71
|
/**
|
|
61
72
|
* Failure-mode and preference-extraction recap. Required for
|
|
62
73
|
* what_learned.
|
|
63
74
|
*/
|
|
64
|
-
what_learned: z.string().trim().min(1).max(
|
|
75
|
+
what_learned: z.string().trim().min(1).max(BRIEF_SECTION_TEXT_MAX),
|
|
65
76
|
/**
|
|
66
77
|
* The recommended action. Required for what_action (1
|
|
67
78
|
* sentence per spec).
|
|
68
79
|
*/
|
|
69
|
-
what_action: z.string().trim().min(1).max(
|
|
80
|
+
what_action: z.string().trim().min(1).max(BRIEF_SECTION_TEXT_MAX),
|
|
70
81
|
/**
|
|
71
82
|
* Optional structured bullets (e.g. "4 phases, 3 gates passed,
|
|
72
83
|
* 1 evaluator verdict"). Counts are allowed at this layer; the
|
|
73
84
|
* brief section is the NL primary (RL-7).
|
|
74
85
|
*/
|
|
75
|
-
bullets: z.array(z.string().trim().min(1).max(
|
|
86
|
+
bullets: z.array(z.string().trim().min(1).max(BRIEF_BULLET_MAX)).default([]),
|
|
76
87
|
/**
|
|
77
88
|
* Source trace ids backing the brief (the column on
|
|
78
89
|
* crystallization_event spec §4.5).
|
|
79
90
|
*/
|
|
80
91
|
source_trace_pointers: z
|
|
81
|
-
.array(z.string().trim().min(1).max(
|
|
92
|
+
.array(z.string().trim().min(1).max(BRIEF_TRACE_POINTER_MAX))
|
|
82
93
|
.default([]),
|
|
83
94
|
});
|
|
84
95
|
/**
|
|
@@ -88,17 +99,17 @@ export const BriefTraceInputSchema = z.object({
|
|
|
88
99
|
* flows into the recommendation envelope.
|
|
89
100
|
*/
|
|
90
101
|
export const EvaluatorSummarySchema = z.object({
|
|
91
|
-
one_liner: z.string().trim().max(
|
|
92
|
-
risk_tags: z.array(z.string().trim().min(1).max(
|
|
102
|
+
one_liner: z.string().trim().max(BRIEF_EVALUATOR_ONE_LINER_MAX).default(""),
|
|
103
|
+
risk_tags: z.array(z.string().trim().min(1).max(BRIEF_EVALUATOR_RISK_TAG_MAX)).default([]),
|
|
93
104
|
});
|
|
94
105
|
/* ---------------------------------------------------------------------- */
|
|
95
106
|
/* Recommendation envelope — the CLI renders this. */
|
|
96
107
|
/* ---------------------------------------------------------------------- */
|
|
97
108
|
export const RecommendationPayloadSchema = z.object({
|
|
98
109
|
brief: EvidenceBriefSchema,
|
|
99
|
-
bullets: z.array(z.string().trim().min(1).max(
|
|
110
|
+
bullets: z.array(z.string().trim().min(1).max(BRIEF_BULLET_MAX)).default([]),
|
|
100
111
|
source_trace_pointers: z
|
|
101
|
-
.array(z.string().trim().min(1).max(
|
|
112
|
+
.array(z.string().trim().min(1).max(BRIEF_TRACE_POINTER_MAX))
|
|
102
113
|
.default([]),
|
|
103
114
|
evaluator_summary: EvaluatorSummarySchema.default({
|
|
104
115
|
one_liner: "",
|