peaks-loop 4.0.33 → 4.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/cli/commands/code-commands.js +2 -0
- package/dist/cli/commands/code-mode-gate-commands.js +3 -3
- package/dist/cli/commands/code-mode-status-command.d.ts +17 -0
- package/dist/cli/commands/code-mode-status-command.js +51 -0
- package/dist/cli/commands/code-run-command.js +8 -1
- package/dist/cli/commands/code-runtime-commands.js +13 -5
- package/dist/cli/commands/core/memory-command.js +61 -3
- package/dist/cli/commands/core/skill-command.js +1 -1
- package/dist/cli/commands/dispatch-commands.js +4 -2
- package/dist/cli/commands/memory-commands.d.ts +35 -0
- package/dist/cli/commands/memory-commands.js +119 -10
- package/dist/cli/commands/request-commands.js +3 -7
- package/dist/cli/commands/session-24h-mode.js +9 -1
- package/dist/services/24h-mode/auto-engage.d.ts +39 -0
- package/dist/services/24h-mode/auto-engage.js +46 -0
- package/dist/services/24h-mode/decider.d.ts +11 -5
- package/dist/services/24h-mode/decider.js +13 -13
- package/dist/services/audit/enforcers/active-skill-resolver.d.ts +3 -1
- package/dist/services/audit/enforcers/active-skill-resolver.js +3 -5
- package/dist/services/code/auto-compact-lifecycle.d.ts +1 -1
- package/dist/services/code/auto-compact-lifecycle.js +1 -1
- package/dist/services/code/auto-compact-modes.d.ts +1 -1
- package/dist/services/code/auto-compact-modes.js +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +18 -22
- package/dist/services/code/mode-gate.d.ts +6 -2
- package/dist/services/code/mode-gate.js +9 -5
- package/dist/services/code/orchestrator-can-do.js +2 -2
- package/dist/services/config/config-service.d.ts +17 -0
- package/dist/services/config/config-service.js +34 -0
- package/dist/services/context/auto-compact-dispatcher.d.ts +2 -2
- package/dist/services/context/auto-compact-dispatcher.js +4 -4
- package/dist/services/context/auto-compact-reader.js +8 -1
- package/dist/services/context/auto-compact-types.d.ts +13 -1
- package/dist/services/context/build-dispatch-system-prompt.js +2 -2
- package/dist/services/context/memory-index-reader.d.ts +26 -0
- package/dist/services/context/memory-index-reader.js +62 -30
- package/dist/services/context/memory-preflight-config.d.ts +33 -0
- package/dist/services/context/memory-preflight-config.js +32 -2
- package/dist/services/context/memory-preflight-service.d.ts +20 -1
- package/dist/services/context/memory-preflight-service.js +198 -31
- package/dist/services/doctor/doctor-service/checks/l3-memory-health.d.ts +19 -2
- package/dist/services/doctor/doctor-service/checks/l3-memory-health.js +143 -19
- package/dist/services/hooks/auto-compact-hook-install.d.ts +2 -2
- package/dist/services/hooks/auto-compact-hook-install.js +2 -2
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +51 -0
- package/dist/services/ide/adapters/claude-code-adapter.js +84 -14
- package/dist/services/ide/ide-types.d.ts +10 -1
- package/dist/services/job/job-types.d.ts +3 -3
- package/dist/services/memory/memory-ingest-service.d.ts +79 -0
- package/dist/services/memory/memory-ingest-service.js +225 -0
- package/dist/services/memory/memory-rotate-service.d.ts +88 -0
- package/dist/services/memory/memory-rotate-service.js +373 -0
- package/dist/services/memory/project-memory-service/index/ranking.d.ts +9 -1
- package/dist/services/memory/project-memory-service/index/ranking.js +25 -13
- package/dist/services/memory/project-memory-service/index/reindex.d.ts +75 -0
- package/dist/services/memory/project-memory-service/index/reindex.js +207 -0
- package/dist/services/memory/project-memory-service/index/search.js +14 -24
- package/dist/services/memory/project-memory-service/index.d.ts +7 -3
- package/dist/services/memory/project-memory-service/index.js +6 -2
- package/dist/services/memory/project-memory-service/parsers/frontmatter.d.ts +75 -3
- package/dist/services/memory/project-memory-service/parsers/frontmatter.js +113 -24
- package/dist/services/memory/project-memory-service/types.d.ts +31 -1
- package/dist/services/memory/project-memory-service/types.js +76 -1
- package/dist/services/mode/mode-enforcement.d.ts +4 -1
- package/dist/services/mode/mode-enforcement.js +26 -28
- package/dist/services/mode/mode-status-service.d.ts +81 -0
- package/dist/services/mode/mode-status-service.js +108 -0
- package/dist/services/preferences/preferences-types.d.ts +14 -0
- package/dist/services/preferences/preferences-types.js +8 -0
- package/dist/services/share/run-state-contract.d.ts +1 -1
- package/dist/services/skills/presence-lease-service.d.ts +21 -0
- package/dist/services/skills/presence-lease-service.js +26 -0
- package/dist/services/skills/skill-presence-service.d.ts +10 -1
- package/dist/services/skills/skill-presence-service.js +32 -4
- package/dist/services/skills/skill-statusline-service.js +6 -3
- package/package.json +5 -5
- package/skills/peaks-code/SKILL.md +10 -10
- package/skills/peaks-code/references/mode-selection.md +7 -3
- package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
- package/skills/peaks-code/references/runbook.md +16 -5
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -2
- package/skills/peaks-code/references/standards-preflight.md +1 -1
- package/skills/peaks-code/references/startup-sequence.md +1 -1
- package/skills/peaks-code/references/step-0-8-gate.md +1 -1
- package/skills/peaks-code/references/step-11-memory-sediment.md +35 -0
- package/skills/peaks-code/references/swarm-dispatch-contract.md +2 -2
- package/skills/peaks-code/references/workflow-gates-and-types.md +1 -1
|
@@ -12,22 +12,18 @@
|
|
|
12
12
|
// are stored as standard YAML frontmatter (name / description /
|
|
13
13
|
// metadata.type / metadata.sourceArtifact) followed by the body.
|
|
14
14
|
//
|
|
15
|
-
// Both parsers share the
|
|
15
|
+
// Both parsers share the `VALID_MEMORY_KINDS` allow-list (derived from the
|
|
16
|
+
// canonical `PROJECT_MEMORY_KINDS` tuple in `../types.ts`) and the
|
|
16
17
|
// `slugify` helper used to derive filenames from titles.
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'lesson'
|
|
27
|
-
]);
|
|
28
|
-
/** Exported for guard tests + tooling that needs to enumerate the valid
|
|
29
|
-
* set without duplicating the literal. Single source of truth. */
|
|
30
|
-
export const VALID_PROJECT_MEMORY_KINDS = Array.from(VALID_MEMORY_KINDS);
|
|
19
|
+
import { basename } from 'node:path';
|
|
20
|
+
import { PROJECT_MEMORY_KINDS } from '../types.js';
|
|
21
|
+
/** Accepted-kind set, derived from the canonical `PROJECT_MEMORY_KINDS`
|
|
22
|
+
* tuple so the parser cannot drift from the union type / tier map. */
|
|
23
|
+
export const VALID_MEMORY_KINDS = new Set(PROJECT_MEMORY_KINDS);
|
|
24
|
+
/** Exported for guard tests + tooling that needs to enumerate the accepted
|
|
25
|
+
* set (CLI help text, `--kind` validation) without duplicating the literal. */
|
|
26
|
+
export const VALID_PROJECT_MEMORY_KINDS = PROJECT_MEMORY_KINDS;
|
|
31
27
|
export function slugify(title) {
|
|
32
28
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
33
29
|
return slug.length > 0 ? slug : 'project-memory';
|
|
@@ -69,36 +65,129 @@ export function renderMemoryFile(memory) {
|
|
|
69
65
|
''
|
|
70
66
|
].join('\n');
|
|
71
67
|
}
|
|
72
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the peaks memory kind from a stored memory file's frontmatter.
|
|
70
|
+
*
|
|
71
|
+
* Resolution order (first *valid* kind wins):
|
|
72
|
+
* 1. nested `metadata.type` — the canonical peaks contract
|
|
73
|
+
* 2. top-level `kind:` — the legacy alias (was silently dropped before)
|
|
74
|
+
* 3. top-level `type:` — tolerated by the pre-existing trim-based reader
|
|
75
|
+
* 4. `none` — reported as unclassified; never invented
|
|
76
|
+
*
|
|
77
|
+
* Slice 2026-09-09-memory-system-overhaul (B): before this helper, files
|
|
78
|
+
* using a top-level `kind:` were silently dropped by the reader (defect
|
|
79
|
+
* #2). Falling through to `kind:` / `type:` is a strict superset of the
|
|
80
|
+
* old behaviour — no previously-indexed file changes kind.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveMemoryKind(content) {
|
|
83
|
+
const parsed = parseMemoryFrontmatter(content);
|
|
84
|
+
return parsed.kind;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Single parse surface for stored memory frontmatter. Both
|
|
88
|
+
* `parseStoredMemoryFile` (read path) and the reindex / ingest / doctor
|
|
89
|
+
* classifiers consume this so there is exactly one kind-resolution rule
|
|
90
|
+
* in the codebase.
|
|
91
|
+
*/
|
|
92
|
+
export function parseMemoryFrontmatter(content) {
|
|
73
93
|
const normalized = content.replace(/\r\n/g, '\n');
|
|
74
|
-
if (!normalized.startsWith('---\n'))
|
|
75
|
-
return null;
|
|
94
|
+
if (!normalized.startsWith('---\n')) {
|
|
95
|
+
return { hasFrontmatter: false, kind: { kind: null, source: 'none', rawKind: null }, frontmatter: '', body: normalized.trim() };
|
|
96
|
+
}
|
|
76
97
|
const endIndex = normalized.indexOf('\n---\n', 4);
|
|
77
|
-
if (endIndex < 0)
|
|
78
|
-
return null;
|
|
98
|
+
if (endIndex < 0) {
|
|
99
|
+
return { hasFrontmatter: false, kind: { kind: null, source: 'none', rawKind: null }, frontmatter: '', body: normalized.trim() };
|
|
100
|
+
}
|
|
79
101
|
const frontmatter = normalized.slice(4, endIndex);
|
|
80
102
|
const body = normalized.slice(endIndex + '\n---\n'.length).trim();
|
|
81
103
|
let name;
|
|
104
|
+
let titleField;
|
|
82
105
|
let description;
|
|
83
|
-
let kind;
|
|
84
106
|
let sourceArtifact;
|
|
107
|
+
let nestedType;
|
|
108
|
+
let topType;
|
|
109
|
+
let kindField;
|
|
110
|
+
let inMetadata = false;
|
|
85
111
|
for (const rawLine of frontmatter.split('\n')) {
|
|
112
|
+
const indented = /^\s/.test(rawLine);
|
|
86
113
|
const line = rawLine.trim();
|
|
114
|
+
if (!indented) {
|
|
115
|
+
inMetadata = line === 'metadata:';
|
|
116
|
+
}
|
|
87
117
|
if (line.startsWith('name:'))
|
|
88
118
|
name = line.slice('name:'.length).trim();
|
|
119
|
+
else if (line.startsWith('title:')) {
|
|
120
|
+
if (!indented)
|
|
121
|
+
titleField = line.slice('title:'.length).trim();
|
|
122
|
+
}
|
|
89
123
|
else if (line.startsWith('description:'))
|
|
90
124
|
description = line.slice('description:'.length).trim();
|
|
91
|
-
else if (line.startsWith('type:'))
|
|
92
|
-
|
|
125
|
+
else if (line.startsWith('type:')) {
|
|
126
|
+
const value = line.slice('type:'.length).trim();
|
|
127
|
+
if (indented || inMetadata)
|
|
128
|
+
nestedType ??= value;
|
|
129
|
+
else
|
|
130
|
+
topType ??= value;
|
|
131
|
+
}
|
|
132
|
+
else if (line.startsWith('kind:'))
|
|
133
|
+
kindField ??= line.slice('kind:'.length).trim();
|
|
93
134
|
else if (line.startsWith('sourceArtifact:'))
|
|
94
135
|
sourceArtifact = line.slice('sourceArtifact:'.length).trim();
|
|
95
136
|
}
|
|
96
|
-
|
|
137
|
+
const candidates = [
|
|
138
|
+
['metadata.type', nestedType],
|
|
139
|
+
['kind', kindField],
|
|
140
|
+
['type', topType]
|
|
141
|
+
];
|
|
142
|
+
let kind = { kind: null, source: 'none', rawKind: null };
|
|
143
|
+
for (const [source, raw] of candidates) {
|
|
144
|
+
if (raw === undefined || raw === '')
|
|
145
|
+
continue;
|
|
146
|
+
if (kind.rawKind === null)
|
|
147
|
+
kind = { kind: null, source: 'none', rawKind: raw };
|
|
148
|
+
if (VALID_MEMORY_KINDS.has(raw)) {
|
|
149
|
+
kind = { kind: raw, source, rawKind: raw };
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { hasFrontmatter: true, frontmatter, ...(name !== undefined ? { name } : {}), ...(titleField !== undefined ? { title: titleField } : {}), ...(description !== undefined ? { description } : {}), ...(sourceArtifact !== undefined ? { sourceArtifact } : {}), kind, body };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Deterministic name fallback chain for the read path:
|
|
157
|
+
*
|
|
158
|
+
* 1. `name:` — the canonical field written by `renderMemoryFile`
|
|
159
|
+
* 2. `title:` — hand-written / legacy files (the 5 on-disk files
|
|
160
|
+
* this slice fixes carried only `title:` + `kind:`)
|
|
161
|
+
* 3. filename stem — last resort, so a well-formed memory with a valid
|
|
162
|
+
* kind is never dropped just for missing a name
|
|
163
|
+
*
|
|
164
|
+
* Empty values are skipped rather than accepted: a `name:` of `''` still
|
|
165
|
+
* falls through, and a file whose stem is also empty resolves to null so the
|
|
166
|
+
* caller's validation is preserved (never invents a name).
|
|
167
|
+
*/
|
|
168
|
+
export function resolveMemoryName(parsed, filePath) {
|
|
169
|
+
if (parsed.name !== undefined && parsed.name.length > 0)
|
|
170
|
+
return { name: parsed.name, source: 'name' };
|
|
171
|
+
if (parsed.title !== undefined && parsed.title.length > 0)
|
|
172
|
+
return { name: parsed.title, source: 'title' };
|
|
173
|
+
const stem = basename(filePath, '.md');
|
|
174
|
+
if (stem.length > 0)
|
|
175
|
+
return { name: stem, source: 'stem' };
|
|
176
|
+
return { name: null, source: 'none' };
|
|
177
|
+
}
|
|
178
|
+
export function parseStoredMemoryFile(content, filePath) {
|
|
179
|
+
const parsed = parseMemoryFrontmatter(content);
|
|
180
|
+
if (!parsed.hasFrontmatter)
|
|
181
|
+
return null;
|
|
182
|
+
const { description, sourceArtifact, body } = parsed;
|
|
183
|
+
const kind = parsed.kind.kind;
|
|
184
|
+
const { name } = resolveMemoryName(parsed, filePath);
|
|
185
|
+
if (name === null || kind === null || body.length === 0)
|
|
97
186
|
return null;
|
|
98
187
|
return {
|
|
99
188
|
name,
|
|
100
189
|
title: description ?? name,
|
|
101
|
-
kind
|
|
190
|
+
kind,
|
|
102
191
|
sourceArtifact: sourceArtifact && sourceArtifact !== 'undefined' ? sourceArtifact : null,
|
|
103
192
|
body,
|
|
104
193
|
filePath
|
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Canonical memory-kind vocabulary — the single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* The union type, the accepted-kind set in the frontmatter parser, the
|
|
5
|
+
* hot/warm tier map below, `KIND_ORDER` in the reindex view, and the CLI
|
|
6
|
+
* `--kind` help text all derive from this tuple. Adding a kind here is the
|
|
7
|
+
* only edit required; TypeScript then forces `MEMORY_KIND_TIER` to cover it.
|
|
8
|
+
*
|
|
9
|
+
* Slice 2026-09-10-memory-vocab-and-rotate (E): the original 8 kinds are
|
|
10
|
+
* unchanged. The 13 appended kinds were observed on disk with real values
|
|
11
|
+
* that the index schema rejected (`peaks memory reindex` reported them as
|
|
12
|
+
* `unrecognized kind value`). Accepting them moves those files into the
|
|
13
|
+
* index; files with no kind field at all stay reported as unclassified.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PROJECT_MEMORY_KINDS: readonly ["project", "rule", "decision", "reference", "feedback", "convention", "module", "lesson", "bug", "investigation", "technical-pattern", "project-rule", "design", "handoff", "session-handoff", "project-todo", "publish-closure", "project-closure", "slice-closure", "slice-pilot-findings", "sediment"];
|
|
16
|
+
export type ProjectMemoryKind = (typeof PROJECT_MEMORY_KINDS)[number];
|
|
17
|
+
/** Hot kinds keep their body in the always-available index; warm kinds are
|
|
18
|
+
* indexed with the same entry shape but read on demand. */
|
|
19
|
+
export type MemoryKindTier = 'hot' | 'warm';
|
|
20
|
+
/**
|
|
21
|
+
* Hot/warm tier per kind. Insertion order IS the deterministic section order
|
|
22
|
+
* used by the generated `MEMORY.md` (`KIND_ORDER` in `index/reindex.ts`),
|
|
23
|
+
* so this object's key order is load-bearing — append new kinds at the end
|
|
24
|
+
* of their tier block rather than reshuffling existing entries.
|
|
25
|
+
*
|
|
26
|
+
* The `Record<ProjectMemoryKind, MemoryKindTier>` annotation makes a missing
|
|
27
|
+
* tier a compile error whenever `PROJECT_MEMORY_KINDS` grows.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MEMORY_KIND_TIER: Record<ProjectMemoryKind, MemoryKindTier>;
|
|
30
|
+
export declare const HOT_MEMORY_KINDS: readonly ProjectMemoryKind[];
|
|
31
|
+
export declare const WARM_MEMORY_KINDS: readonly ProjectMemoryKind[];
|
|
2
32
|
export type ExtractedProjectMemory = {
|
|
3
33
|
title: string;
|
|
4
34
|
kind: ProjectMemoryKind;
|
|
@@ -6,4 +6,79 @@
|
|
|
6
6
|
// from here, and the top-level `index.ts` re-exports them for back-compat
|
|
7
7
|
// with downstream callers (CLI commands, audit writers, presence service).
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Canonical memory-kind vocabulary — the single source of truth.
|
|
11
|
+
*
|
|
12
|
+
* The union type, the accepted-kind set in the frontmatter parser, the
|
|
13
|
+
* hot/warm tier map below, `KIND_ORDER` in the reindex view, and the CLI
|
|
14
|
+
* `--kind` help text all derive from this tuple. Adding a kind here is the
|
|
15
|
+
* only edit required; TypeScript then forces `MEMORY_KIND_TIER` to cover it.
|
|
16
|
+
*
|
|
17
|
+
* Slice 2026-09-10-memory-vocab-and-rotate (E): the original 8 kinds are
|
|
18
|
+
* unchanged. The 13 appended kinds were observed on disk with real values
|
|
19
|
+
* that the index schema rejected (`peaks memory reindex` reported them as
|
|
20
|
+
* `unrecognized kind value`). Accepting them moves those files into the
|
|
21
|
+
* index; files with no kind field at all stay reported as unclassified.
|
|
22
|
+
*/
|
|
23
|
+
export const PROJECT_MEMORY_KINDS = [
|
|
24
|
+
// Original 8 (pre-2026-09-10).
|
|
25
|
+
'project',
|
|
26
|
+
'rule',
|
|
27
|
+
'decision',
|
|
28
|
+
'reference',
|
|
29
|
+
'feedback',
|
|
30
|
+
'convention',
|
|
31
|
+
'module',
|
|
32
|
+
'lesson',
|
|
33
|
+
// Slice E expansion — observed on-disk values, hot tier.
|
|
34
|
+
'bug',
|
|
35
|
+
'investigation',
|
|
36
|
+
'technical-pattern',
|
|
37
|
+
'project-rule',
|
|
38
|
+
// Slice E expansion — observed on-disk values, warm tier.
|
|
39
|
+
'design',
|
|
40
|
+
'handoff',
|
|
41
|
+
'session-handoff',
|
|
42
|
+
'project-todo',
|
|
43
|
+
'publish-closure',
|
|
44
|
+
'project-closure',
|
|
45
|
+
'slice-closure',
|
|
46
|
+
'slice-pilot-findings',
|
|
47
|
+
'sediment'
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Hot/warm tier per kind. Insertion order IS the deterministic section order
|
|
51
|
+
* used by the generated `MEMORY.md` (`KIND_ORDER` in `index/reindex.ts`),
|
|
52
|
+
* so this object's key order is load-bearing — append new kinds at the end
|
|
53
|
+
* of their tier block rather than reshuffling existing entries.
|
|
54
|
+
*
|
|
55
|
+
* The `Record<ProjectMemoryKind, MemoryKindTier>` annotation makes a missing
|
|
56
|
+
* tier a compile error whenever `PROJECT_MEMORY_KINDS` grows.
|
|
57
|
+
*/
|
|
58
|
+
export const MEMORY_KIND_TIER = {
|
|
59
|
+
// hot — full body kept in the index
|
|
60
|
+
feedback: 'hot',
|
|
61
|
+
decision: 'hot',
|
|
62
|
+
rule: 'hot',
|
|
63
|
+
convention: 'hot',
|
|
64
|
+
module: 'hot',
|
|
65
|
+
lesson: 'hot',
|
|
66
|
+
bug: 'hot',
|
|
67
|
+
investigation: 'hot',
|
|
68
|
+
'technical-pattern': 'hot',
|
|
69
|
+
'project-rule': 'hot',
|
|
70
|
+
// warm — same entry shape, read on demand
|
|
71
|
+
project: 'warm',
|
|
72
|
+
reference: 'warm',
|
|
73
|
+
design: 'warm',
|
|
74
|
+
handoff: 'warm',
|
|
75
|
+
'session-handoff': 'warm',
|
|
76
|
+
'project-todo': 'warm',
|
|
77
|
+
'publish-closure': 'warm',
|
|
78
|
+
'project-closure': 'warm',
|
|
79
|
+
'slice-closure': 'warm',
|
|
80
|
+
'slice-pilot-findings': 'warm',
|
|
81
|
+
sediment: 'warm'
|
|
82
|
+
};
|
|
83
|
+
export const HOT_MEMORY_KINDS = PROJECT_MEMORY_KINDS.filter((kind) => MEMORY_KIND_TIER[kind] === 'hot');
|
|
84
|
+
export const WARM_MEMORY_KINDS = PROJECT_MEMORY_KINDS.filter((kind) => MEMORY_KIND_TIER[kind] === 'warm');
|
|
@@ -8,7 +8,10 @@ export type ConfirmationOptions = {
|
|
|
8
8
|
forceConfirm?: boolean | undefined;
|
|
9
9
|
};
|
|
10
10
|
export declare class ConfirmationRequiredError extends Error {
|
|
11
|
-
|
|
11
|
+
readonly transitionKey: TransitionKey;
|
|
12
|
+
readonly mode: SkillPresenceMode;
|
|
13
|
+
readonly nextActions: readonly string[];
|
|
14
|
+
constructor(transitionKey: TransitionKey, mode: SkillPresenceMode);
|
|
12
15
|
}
|
|
13
16
|
export declare function requireUserConfirmation(options: ConfirmationOptions): Promise<void>;
|
|
14
17
|
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as readline from 'node:readline';
|
|
2
1
|
import { getSkillPresence } from '../skills/skill-presence-service.js';
|
|
3
2
|
const ASSISTED_CONFIRM_TRANSITIONS = new Set([
|
|
4
3
|
'prd:confirmed-by-user',
|
|
@@ -6,7 +5,9 @@ const ASSISTED_CONFIRM_TRANSITIONS = new Set([
|
|
|
6
5
|
'qa:verdict-issued'
|
|
7
6
|
]);
|
|
8
7
|
export function requiresConfirmation(mode, transitionKey) {
|
|
9
|
-
|
|
8
|
+
// Slice 2026-09-09-mode-consolidation: `swarm` removed as a mode; the
|
|
9
|
+
// two auto-proceed peers are `full-auto` and `24h`.
|
|
10
|
+
if (mode === 'full-auto' || mode === '24h') {
|
|
10
11
|
return false;
|
|
11
12
|
}
|
|
12
13
|
if (mode === 'strict') {
|
|
@@ -22,12 +23,23 @@ function describeTransition(transitionKey) {
|
|
|
22
23
|
return `Transition ${role.toUpperCase()} → ${state}`;
|
|
23
24
|
}
|
|
24
25
|
export class ConfirmationRequiredError extends Error {
|
|
25
|
-
|
|
26
|
+
transitionKey;
|
|
27
|
+
mode;
|
|
28
|
+
nextActions;
|
|
29
|
+
constructor(transitionKey, mode) {
|
|
26
30
|
const description = describeTransition(transitionKey);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
31
|
+
const nextActions = [
|
|
32
|
+
`Ask the user via AskUserQuestion whether to proceed with ${description} in ${mode} mode.`,
|
|
33
|
+
'If the user approves, re-run the same command with --confirm.'
|
|
34
|
+
];
|
|
35
|
+
super(`Confirmation required for: ${description} (mode: ${mode})\n` +
|
|
36
|
+
`${nextActions[0]}\n` +
|
|
37
|
+
`${nextActions[1]}\n` +
|
|
38
|
+
'No terminal prompt is available: this gate never reads stdin.');
|
|
30
39
|
this.name = 'ConfirmationRequiredError';
|
|
40
|
+
this.transitionKey = transitionKey;
|
|
41
|
+
this.mode = mode;
|
|
42
|
+
this.nextActions = nextActions;
|
|
31
43
|
}
|
|
32
44
|
}
|
|
33
45
|
export async function requireUserConfirmation(options) {
|
|
@@ -40,11 +52,11 @@ export async function requireUserConfirmation(options) {
|
|
|
40
52
|
if (!requiresConfirmation(mode, options.transitionKey)) {
|
|
41
53
|
return;
|
|
42
54
|
}
|
|
43
|
-
// --confirm flag bypasses
|
|
55
|
+
// --confirm flag bypasses the gate
|
|
44
56
|
if (options.confirmed) {
|
|
45
57
|
return;
|
|
46
58
|
}
|
|
47
|
-
// PEAKS_AUTO_CONFIRM=1 only works for full-auto/
|
|
59
|
+
// PEAKS_AUTO_CONFIRM=1 only works for full-auto/24h (already returned above)
|
|
48
60
|
// For assisted/strict, env var is ignored unless --force-confirm is also set
|
|
49
61
|
if (process.env.PEAKS_AUTO_CONFIRM === '1') {
|
|
50
62
|
if (options.forceConfirm) {
|
|
@@ -53,7 +65,7 @@ export async function requireUserConfirmation(options) {
|
|
|
53
65
|
'This bypasses user confirmation. Use with caution.');
|
|
54
66
|
return;
|
|
55
67
|
}
|
|
56
|
-
throw new ConfirmationRequiredError(options.transitionKey);
|
|
68
|
+
throw new ConfirmationRequiredError(options.transitionKey, mode);
|
|
57
69
|
}
|
|
58
70
|
// --force-confirm without env var
|
|
59
71
|
if (options.forceConfirm) {
|
|
@@ -62,23 +74,9 @@ export async function requireUserConfirmation(options) {
|
|
|
62
74
|
'This bypasses user confirmation. Use with caution.');
|
|
63
75
|
return;
|
|
64
76
|
}
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return new Promise((resolve, reject) => {
|
|
71
|
-
const description = describeTransition(options.transitionKey);
|
|
72
|
-
const prompt = `\n[CONFIRM] ${description}\nProceed? (y/N) `;
|
|
73
|
-
rl.question(prompt, (answer) => {
|
|
74
|
-
rl.close();
|
|
75
|
-
const normalized = answer.trim().toLowerCase();
|
|
76
|
-
if (normalized === 'y' || normalized === 'yes') {
|
|
77
|
-
resolve();
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
reject(new ConfirmationRequiredError(options.transitionKey));
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
});
|
|
77
|
+
// No bypass flag: refuse immediately. Never read stdin — in an LLM-driven
|
|
78
|
+
// session there is no TTY, so a prompt would hang forever, and a `y/N`
|
|
79
|
+
// terminal prompt would also violate the Human-NL-Choice-Only rule (the
|
|
80
|
+
// user answers via AskUserQuestion, never by typing into a shell).
|
|
81
|
+
throw new ConfirmationRequiredError(options.transitionKey, mode);
|
|
84
82
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-09-09-mode-consolidation (Slice D): one read for the whole
|
|
3
|
+
* stacked mode picture.
|
|
4
|
+
*
|
|
5
|
+
* The autonomy model has two layers that used to be read by nothing in
|
|
6
|
+
* common:
|
|
7
|
+
*
|
|
8
|
+
* - MODE — `SkillPresenceMode` on the presence lease: the autonomy
|
|
9
|
+
* level (`full-auto | assisted | strict | 24h`).
|
|
10
|
+
* - STATE — `.peaks/_runtime/<sid>/24h-state.json`, the 6-state
|
|
11
|
+
* machine (`IDLE / BRAINSTORM / USER_CONFIRM / 24H_ACTIVE /
|
|
12
|
+
* WAITING_USER / HANDOFF`): the long-run PHASE *inside* the `24h`
|
|
13
|
+
* mode.
|
|
14
|
+
*
|
|
15
|
+
* This module is the single place that reads both, plus the Job-mode
|
|
16
|
+
* decision and the resolved auto-compact profile, so `peaks code mode
|
|
17
|
+
* status` (and any future consumer) does not re-derive the stack.
|
|
18
|
+
*
|
|
19
|
+
* Read-only + fail-soft: every sub-read is wrapped so a missing or
|
|
20
|
+
* malformed artifact degrades to `null` / `false` rather than throwing.
|
|
21
|
+
*/
|
|
22
|
+
import { type SkillPresenceMode } from '../skills/skill-presence-service.js';
|
|
23
|
+
import type { State24hSnapshot } from '../24h-mode/state.js';
|
|
24
|
+
import { type AutoCompactMode } from '../code/auto-compact-modes.js';
|
|
25
|
+
export interface ResolvedPresenceMode {
|
|
26
|
+
readonly mode: SkillPresenceMode | null;
|
|
27
|
+
readonly skill: string | null;
|
|
28
|
+
readonly source: 'canonical' | 'file' | 'env' | 'none';
|
|
29
|
+
}
|
|
30
|
+
export interface ModeStatus {
|
|
31
|
+
/** Autonomy level from the presence lease; `null` when no lease / no mode recorded. */
|
|
32
|
+
readonly mode: SkillPresenceMode | null;
|
|
33
|
+
/** Active skill name from the same lease read (display aid). */
|
|
34
|
+
readonly skill: string | null;
|
|
35
|
+
/** Where the mode came from. */
|
|
36
|
+
readonly modeSource: 'canonical' | 'file' | 'env' | 'none';
|
|
37
|
+
/** `true` when the resolved mode is `24h`. */
|
|
38
|
+
readonly is24h: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The 24h internal state machine snapshot. Populated only when
|
|
41
|
+
* `is24h` is true (the phase is meaningless outside the 24h mode);
|
|
42
|
+
* `null` when the mode is not `24h` or the snapshot is unreadable.
|
|
43
|
+
*/
|
|
44
|
+
readonly h24State: State24hSnapshot | null;
|
|
45
|
+
/** Job-mode flag from `job-shape.json` (`false` when undecided / unreadable). */
|
|
46
|
+
readonly jobMode: boolean;
|
|
47
|
+
/** Resolved auto-compact profile: `partial` iff the mode is `24h`. */
|
|
48
|
+
readonly autoCompactProfile: AutoCompactMode;
|
|
49
|
+
/** Thresholds backing `autoCompactProfile` (display aid). */
|
|
50
|
+
readonly autoCompactThresholds: {
|
|
51
|
+
readonly autoFire: number;
|
|
52
|
+
readonly preCompact: number;
|
|
53
|
+
readonly redLine: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read the active presence mode via the canonical lease projection.
|
|
58
|
+
* Never throws.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolvePresenceMode(projectRoot: string): ResolvedPresenceMode;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the auto-compact profile from the MODE, not from the 24h
|
|
63
|
+
* state machine. `24h` mode → `partial` (earlier thresholds for
|
|
64
|
+
* long-run sessions); every other mode → `standard`.
|
|
65
|
+
*
|
|
66
|
+
* Slice 2026-09-09-mode-consolidation: this replaced the previous
|
|
67
|
+
* `24h-state.state === '24H_ACTIVE'` mapping. The state machine is the
|
|
68
|
+
* long-run *phase*; the mode is the autonomy level the user (or the
|
|
69
|
+
* T3/T4 auto-engage) selected. Keying off the mode means a `24h`
|
|
70
|
+
* session keeps the partial profile even while it is in
|
|
71
|
+
* `WAITING_USER`, which is the correct long-run behaviour.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveAutoCompactProfile(projectRoot: string): AutoCompactMode;
|
|
74
|
+
/**
|
|
75
|
+
* Assemble the full stacked status. `sessionId` defaults to the
|
|
76
|
+
* canonical binding when omitted.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveModeStatus(input: {
|
|
79
|
+
projectRoot: string;
|
|
80
|
+
sessionId?: string | null | undefined;
|
|
81
|
+
}): ModeStatus;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-09-09-mode-consolidation (Slice D): one read for the whole
|
|
3
|
+
* stacked mode picture.
|
|
4
|
+
*
|
|
5
|
+
* The autonomy model has two layers that used to be read by nothing in
|
|
6
|
+
* common:
|
|
7
|
+
*
|
|
8
|
+
* - MODE — `SkillPresenceMode` on the presence lease: the autonomy
|
|
9
|
+
* level (`full-auto | assisted | strict | 24h`).
|
|
10
|
+
* - STATE — `.peaks/_runtime/<sid>/24h-state.json`, the 6-state
|
|
11
|
+
* machine (`IDLE / BRAINSTORM / USER_CONFIRM / 24H_ACTIVE /
|
|
12
|
+
* WAITING_USER / HANDOFF`): the long-run PHASE *inside* the `24h`
|
|
13
|
+
* mode.
|
|
14
|
+
*
|
|
15
|
+
* This module is the single place that reads both, plus the Job-mode
|
|
16
|
+
* decision and the resolved auto-compact profile, so `peaks code mode
|
|
17
|
+
* status` (and any future consumer) does not re-derive the stack.
|
|
18
|
+
*
|
|
19
|
+
* Read-only + fail-soft: every sub-read is wrapped so a missing or
|
|
20
|
+
* malformed artifact degrades to `null` / `false` rather than throwing.
|
|
21
|
+
*/
|
|
22
|
+
import { resolveActiveSkillForCaller } from '../audit/enforcers/active-skill-resolver.js';
|
|
23
|
+
import { getCurrentSessionId } from '../skills/skill-presence-service.js';
|
|
24
|
+
// Import the 24h store directly (NOT the `24h-mode/index.js` barrel) —
|
|
25
|
+
// the barrel re-exports `decider.ts`, which imports this module, and
|
|
26
|
+
// that would form an import cycle.
|
|
27
|
+
import { read24hState } from '../24h-mode/store.js';
|
|
28
|
+
import { readJobShapeDecision } from '../code/job-shape-decision.js';
|
|
29
|
+
import { AUTO_COMPACT_THRESHOLDS } from '../code/auto-compact-modes.js';
|
|
30
|
+
/**
|
|
31
|
+
* Read the active presence mode via the canonical lease projection.
|
|
32
|
+
* Never throws.
|
|
33
|
+
*/
|
|
34
|
+
export function resolvePresenceMode(projectRoot) {
|
|
35
|
+
try {
|
|
36
|
+
const resolved = resolveActiveSkillForCaller(projectRoot);
|
|
37
|
+
const mode = resolved.mode;
|
|
38
|
+
return {
|
|
39
|
+
mode: mode === null ? null : mode,
|
|
40
|
+
skill: resolved.skill,
|
|
41
|
+
source: resolved.source
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { mode: null, skill: null, source: 'none' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the auto-compact profile from the MODE, not from the 24h
|
|
50
|
+
* state machine. `24h` mode → `partial` (earlier thresholds for
|
|
51
|
+
* long-run sessions); every other mode → `standard`.
|
|
52
|
+
*
|
|
53
|
+
* Slice 2026-09-09-mode-consolidation: this replaced the previous
|
|
54
|
+
* `24h-state.state === '24H_ACTIVE'` mapping. The state machine is the
|
|
55
|
+
* long-run *phase*; the mode is the autonomy level the user (or the
|
|
56
|
+
* T3/T4 auto-engage) selected. Keying off the mode means a `24h`
|
|
57
|
+
* session keeps the partial profile even while it is in
|
|
58
|
+
* `WAITING_USER`, which is the correct long-run behaviour.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveAutoCompactProfile(projectRoot) {
|
|
61
|
+
return resolvePresenceMode(projectRoot).mode === '24h' ? 'partial' : 'standard';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Assemble the full stacked status. `sessionId` defaults to the
|
|
65
|
+
* canonical binding when omitted.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveModeStatus(input) {
|
|
68
|
+
const { projectRoot } = input;
|
|
69
|
+
const presence = resolvePresenceMode(projectRoot);
|
|
70
|
+
const is24h = presence.mode === '24h';
|
|
71
|
+
let sessionId = input.sessionId ?? null;
|
|
72
|
+
if (sessionId === null) {
|
|
73
|
+
try {
|
|
74
|
+
sessionId = getCurrentSessionId(projectRoot);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
sessionId = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let h24State = null;
|
|
81
|
+
if (is24h && sessionId !== null) {
|
|
82
|
+
try {
|
|
83
|
+
h24State = read24hState(projectRoot, sessionId);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
h24State = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
let jobMode = false;
|
|
90
|
+
if (sessionId !== null) {
|
|
91
|
+
try {
|
|
92
|
+
jobMode = readJobShapeDecision(projectRoot, sessionId).decision.isJob;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
jobMode = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
mode: presence.mode,
|
|
100
|
+
skill: presence.skill,
|
|
101
|
+
modeSource: presence.source,
|
|
102
|
+
is24h,
|
|
103
|
+
h24State,
|
|
104
|
+
jobMode,
|
|
105
|
+
autoCompactProfile: is24h ? 'partial' : 'standard',
|
|
106
|
+
autoCompactThresholds: AUTO_COMPACT_THRESHOLDS[is24h ? 'partial' : 'standard']
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -127,9 +127,23 @@ export interface ProjectPreferences {
|
|
|
127
127
|
*/
|
|
128
128
|
readonly memoryPreflight?: {
|
|
129
129
|
readonly enabled?: boolean;
|
|
130
|
+
/** Back-compat token cap; converted to bytes as `maxTokens * 4`. */
|
|
130
131
|
readonly maxTokens?: number;
|
|
132
|
+
/** Back-compat hot item cap; `hotItemCap` overrides it when set. */
|
|
131
133
|
readonly listCap?: number;
|
|
132
134
|
readonly contentCacheBytes?: number;
|
|
135
|
+
/** Slice 2026-09-09-memory-retrieval: hard byte cap on the block. */
|
|
136
|
+
readonly maxBytes?: number;
|
|
137
|
+
/** Slice 2026-09-09-memory-retrieval: max hot items (default 10). */
|
|
138
|
+
readonly hotItemCap?: number;
|
|
139
|
+
/** Slice 2026-09-09-memory-retrieval: max warm items (default 4; 0 disables). */
|
|
140
|
+
readonly warmItemCap?: number;
|
|
141
|
+
/** Slice 2026-09-09-memory-retrieval: min task-token hits for warm eligibility. */
|
|
142
|
+
readonly warmMinTokenHits?: number;
|
|
143
|
+
/** Slice 2026-09-09-memory-retrieval: soft selection wall-clock budget (ms). */
|
|
144
|
+
readonly selectionTimeBudgetMs?: number;
|
|
145
|
+
/** Slice 2026-09-09-memory-retrieval: inline memo bodies (default false). */
|
|
146
|
+
readonly includeBodies?: boolean;
|
|
133
147
|
};
|
|
134
148
|
}
|
|
135
149
|
export type FanoutMode = 'fan-out';
|
|
@@ -48,5 +48,13 @@ export const DEFAULT_PREFERENCES = {
|
|
|
48
48
|
maxTokens: 1200,
|
|
49
49
|
listCap: 12,
|
|
50
50
|
contentCacheBytes: 6000,
|
|
51
|
+
// Slice 2026-09-09-memory-retrieval tiered budget (see
|
|
52
|
+
// memory-preflight-config.ts::DEFAULTS).
|
|
53
|
+
maxBytes: 4800,
|
|
54
|
+
hotItemCap: 10,
|
|
55
|
+
warmItemCap: 4,
|
|
56
|
+
warmMinTokenHits: 1,
|
|
57
|
+
selectionTimeBudgetMs: 200,
|
|
58
|
+
includeBodies: false,
|
|
51
59
|
},
|
|
52
60
|
};
|
|
@@ -66,6 +66,27 @@ export interface GcPresenceLeasesInput {
|
|
|
66
66
|
readonly trigger?: 'manual' | 'workspace-init' | 'presence-set';
|
|
67
67
|
}
|
|
68
68
|
export declare function setPresenceLease(input: SetPresenceLeaseInput): SetPresenceLeaseResult;
|
|
69
|
+
/**
|
|
70
|
+
* Slice 2026-09-09-mode-consolidation (Slice B): stamp `mode` onto every
|
|
71
|
+
* in-flight lease of the bound session, in place.
|
|
72
|
+
*
|
|
73
|
+
* Used by the 24h auto-engage path (T3/T4, `peaks code run --24h`,
|
|
74
|
+
* `peaks session 24h-mode transition --state 24H_ACTIVE`) to record the
|
|
75
|
+
* autonomy level without re-creating the lease (a fresh `setPresenceLease`
|
|
76
|
+
* would reset `startedAt` / `status`). `lastHeartbeat` is intentionally
|
|
77
|
+
* NOT touched — stamping a mode is not a liveness signal.
|
|
78
|
+
*
|
|
79
|
+
* Returns the number of leases updated; 0 when there is nothing in flight.
|
|
80
|
+
* Callers are responsible for the "only `24h` may be auto-set" guard.
|
|
81
|
+
*/
|
|
82
|
+
export declare function stampPresenceLeaseMode(input: {
|
|
83
|
+
readonly projectRoot: string;
|
|
84
|
+
readonly sessionId: string;
|
|
85
|
+
readonly mode: string;
|
|
86
|
+
}): {
|
|
87
|
+
updated: number;
|
|
88
|
+
leasePaths: string[];
|
|
89
|
+
};
|
|
69
90
|
export declare function readPresenceLease(input: ReadPresenceLeaseInput): PresenceProjection;
|
|
70
91
|
export declare function markPresenceLost(input: MarkPresenceLostInput): SkillPresenceLease;
|
|
71
92
|
export declare function gcStalePresenceLeases(input: GcPresenceLeasesInput): GcResult;
|