peaks-loop 4.0.8 → 4.0.10
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/_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/container-commands.js +2 -1
- package/dist/cli/commands/core/skill-command.js +32 -5
- package/dist/cli/commands/openspec-commands.js +2 -1
- package/dist/cli/commands/statusline-commands.js +1 -1
- package/dist/services/audit/enforcers/active-skill-resolver.d.ts +11 -0
- package/dist/services/audit/enforcers/active-skill-resolver.js +50 -39
- 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/container/container-lease.js +2 -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/impact/impact-scan-service.js +4 -3
- package/dist/services/job/job-progress-store.d.ts +2 -2
- package/dist/services/job/job-types.d.ts +26 -26
- package/dist/services/migrate-skill-name/migrate.js +2 -1
- package/dist/services/observability/observability-service.d.ts +2 -2
- package/dist/services/openspec/artifact-boundary.js +3 -2
- package/dist/services/openspec/coverage-evidence-reader.js +9 -8
- package/dist/services/prd/handoff-auto-regen.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -1
- package/dist/services/sediment/json-schema.d.ts +4 -4
- package/dist/services/session/session-binding-bridge.js +17 -19
- package/dist/services/session/session-manager.js +36 -12
- 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/skill-statusline-renderer.d.ts +46 -56
- package/dist/services/skills/skill-statusline-renderer.js +335 -126
- package/dist/services/skills/skill-statusline-service.d.ts +6 -0
- package/dist/services/skills/skill-statusline-service.js +105 -7
- package/dist/services/vm/vm-lease.js +2 -1
- package/dist/services/workflow/workflow-autonomous-resume-helpers.js +3 -2
- package/dist/services/workspace/workspace-service.js +2 -1
- package/dist/services/worktree/worktree-lease.js +2 -1
- package/dist/shared/path-safety.js +3 -5
- package/dist/shared/path-utils.d.ts +48 -0
- package/dist/shared/path-utils.js +65 -1
- package/package.json +5 -4
|
@@ -9,6 +9,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkS
|
|
|
9
9
|
import { mkdir as mkdirAsync } from 'node:fs/promises';
|
|
10
10
|
import { dirname, join, resolve } from 'node:path';
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
|
+
import { projectRootsMatch, stableRealPath } from '../../shared/path-utils.js';
|
|
12
13
|
import { ensureSession } from './session-binding-bridge.js';
|
|
13
14
|
// As of slice 2026-06-05-peaks-runtime-layer the project-level session
|
|
14
15
|
// binding lives under `.peaks/_runtime/session.json`. The legacy
|
|
@@ -89,17 +90,22 @@ function getSessionFilePath(projectRoot) {
|
|
|
89
90
|
* Read existing session info from disk.
|
|
90
91
|
* Returns null if no session file exists or if it's invalid.
|
|
91
92
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
93
|
+
* The `projectRoot` comparison is canonicalized (separator, symlink,
|
|
94
|
+
* and — on Windows only — case) via `projectRootsMatch`. Before
|
|
95
|
+
* slice `2026-08-04-rid-001-path-canonicalize` this was a strict
|
|
96
|
+
* `===`, which returned null whenever the stored form differed
|
|
97
|
+
* cosmetically from the caller-passed form. On Windows Git Bash that
|
|
98
|
+
* is the common case: `peaks workspace init` stores
|
|
99
|
+
* `C:\Users\...\peaks-loop` while the caller passes
|
|
100
|
+
* `C:/Users/.../peaks-loop`, so every `getSessionId` returned null and
|
|
101
|
+
* `presence:set` failed closed with `PEAKS_SESSION_NOT_BOUND` —
|
|
102
|
+
* surfacing to the user as a permanent `peaks empty` statusline.
|
|
98
103
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
104
|
+
* Canonicalization is a strict widening: paths that denote the same
|
|
105
|
+
* physical directory now match, and paths that denote different
|
|
106
|
+
* directories still do not (pinned by the Case 4 regression test in
|
|
107
|
+
* `tests/unit/session/session-manager-path-canonicalize.test.ts`), so
|
|
108
|
+
* the "no session bound" code path other modules depend on is intact.
|
|
103
109
|
*/
|
|
104
110
|
function readSessionFile(projectRoot) {
|
|
105
111
|
const sessionFile = getSessionFilePath(projectRoot);
|
|
@@ -112,7 +118,7 @@ function readSessionFile(projectRoot) {
|
|
|
112
118
|
return null;
|
|
113
119
|
try {
|
|
114
120
|
const data = JSON.parse(readFileSync(pathToRead, 'utf8'));
|
|
115
|
-
if (data.sessionId && data.projectRoot === projectRoot) {
|
|
121
|
+
if (data.sessionId && typeof data.projectRoot === 'string' && projectRootsMatch(data.projectRoot, projectRoot)) {
|
|
116
122
|
return data;
|
|
117
123
|
}
|
|
118
124
|
return null;
|
|
@@ -157,6 +163,13 @@ function readSessionFileCanonical(projectRoot) {
|
|
|
157
163
|
* `.peaks/_runtime/session.json`. The `.peaks/_runtime/` directory is
|
|
158
164
|
* created on demand. The legacy `.peaks/.session.json` is NOT written by
|
|
159
165
|
* this slice; it is only read for back-compat.
|
|
166
|
+
*
|
|
167
|
+
* The persisted `projectRoot` is passed through `stableRealPath` so the
|
|
168
|
+
* stored form is symlink-resolved and stable across callers. We store
|
|
169
|
+
* the REAL path, not the `projectRootCompareKey` — the key is
|
|
170
|
+
* lossy (lower-cased on Windows) and is only ever a comparison
|
|
171
|
+
* artifact. Reads tolerate either form via `projectRootsMatch`, so
|
|
172
|
+
* bindings written by older versions keep resolving.
|
|
160
173
|
*/
|
|
161
174
|
function writeSessionFile(projectRoot, info) {
|
|
162
175
|
const sessionFile = getSessionFilePath(projectRoot);
|
|
@@ -164,7 +177,18 @@ function writeSessionFile(projectRoot, info) {
|
|
|
164
177
|
if (!existsSync(dir)) {
|
|
165
178
|
mkdirSync(dir, { recursive: true });
|
|
166
179
|
}
|
|
167
|
-
|
|
180
|
+
let canonicalProjectRoot;
|
|
181
|
+
try {
|
|
182
|
+
canonicalProjectRoot = stableRealPath(info.projectRoot);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Never block a write on canonicalization: if the path cannot be
|
|
186
|
+
// realpath'd, persist the caller's form unchanged. Reads canonicalize
|
|
187
|
+
// both sides anyway, so a non-canonical stored value still matches.
|
|
188
|
+
canonicalProjectRoot = info.projectRoot;
|
|
189
|
+
}
|
|
190
|
+
const canonicalInfo = { ...info, projectRoot: canonicalProjectRoot };
|
|
191
|
+
writeFileSync(sessionFile, JSON.stringify(canonicalInfo, null, 2), 'utf8');
|
|
168
192
|
}
|
|
169
193
|
/**
|
|
170
194
|
* Drop the project-level session binding at the canonical
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* prepend to its own prompt. The output is LLM-friendly: stable
|
|
7
7
|
* headings, monospace paths, and bullet lists — easy for the model to
|
|
8
8
|
* scan and reason about.
|
|
9
|
+
*
|
|
10
|
+
* Resume locates the deepestGate (the latest completed gate in the
|
|
11
|
+
* workflow) so the skill can pick up the next step without re-asking
|
|
12
|
+
* the user for state already captured at that gate.
|
|
9
13
|
*/
|
|
10
14
|
import { type CheckpointSnapshot } from './session-checkpoint-service.js';
|
|
11
15
|
export interface ResumeOptions {
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* prepend to its own prompt. The output is LLM-friendly: stable
|
|
7
7
|
* headings, monospace paths, and bullet lists — easy for the model to
|
|
8
8
|
* scan and reason about.
|
|
9
|
+
*
|
|
10
|
+
* Resume locates the deepestGate (the latest completed gate in the
|
|
11
|
+
* workflow) so the skill can pick up the next step without re-asking
|
|
12
|
+
* the user for state already captured at that gate.
|
|
9
13
|
*/
|
|
10
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
11
15
|
import { basename, sep } from 'node:path';
|
|
@@ -14,66 +14,44 @@ export type StatusLineCapability = 'ansi-unicode' | 'unicode' | 'ascii';
|
|
|
14
14
|
export interface StatusLineRenderOptions {
|
|
15
15
|
readonly capability: StatusLineCapability;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Resolve the capability tier from environment and TTY state. Pure and
|
|
19
|
-
* deterministic — no I/O beyond reading the caller-supplied `env` and
|
|
20
|
-
* `isTTY` flag. The CLI delegates to this so the read-only status line
|
|
21
|
-
* does not need to know about ANSI/NO_COLOR semantics.
|
|
22
|
-
*
|
|
23
|
-
* The optional `forced` argument is a TEST SEAM (consumed by direct unit
|
|
24
|
-
* tests, not by the CLI). It lets tests pin a tier without constructing a
|
|
25
|
-
* TTY/NO_COLOR fixture. The CLI does NOT expose any flag that maps to
|
|
26
|
-
* `forced` — the env-driven path is the single first-version runtime
|
|
27
|
-
* source of truth.
|
|
28
|
-
*
|
|
29
|
-
* Adapter-internal env override (NOT a user-facing CLI flag):
|
|
30
|
-
* `PEAKS_STATUSLINE_ASCII` — when set to `1` / `true` / `yes`, the
|
|
31
|
-
* renderer drops to the ASCII palette (no Unicode-extra glyphs, no ANSI).
|
|
32
|
-
* This is an adapter-internal mechanism for the trust boundary described
|
|
33
|
-
* by the two-forms-only / human-NL-choice-only tenets: the user never
|
|
34
|
-
* types a CLI verb to flip palettes, but the adapter (and only the
|
|
35
|
-
* adapter) can set the env var on the user's behalf when its consumer
|
|
36
|
-
* (e.g. a tiny terminal without UTF-8) needs ASCII. We do NOT expose
|
|
37
|
-
* this via a CLI flag.
|
|
38
|
-
*
|
|
39
|
-
* Order of resolution (highest priority first):
|
|
40
|
-
*
|
|
41
|
-
* 1. `forced` argument (test seam only)
|
|
42
|
-
* 2. `PEAKS_STATUSLINE_ASCII` set → `ascii` (adapter-internal override)
|
|
43
|
-
* 3. `isTTY === true` → `ansi-unicode`
|
|
44
|
-
* 4. otherwise → `unicode` (still ANSI-colored; only `ascii` is color-free)
|
|
45
|
-
*
|
|
46
|
-
* Why this ordering: the IDE statusline invokes the renderer through a
|
|
47
|
-
* hook that may not always be detected as a TTY. The `unicode` tier
|
|
48
|
-
* now emits the cyan brand accent + semantic colors so the line reads
|
|
49
|
-
* as a branded product surface whether or not the consumer is a
|
|
50
|
-
* terminal. `NO_COLOR` is honored as an ambient convention by the
|
|
51
|
-
* IDE adapter (which can set `PEAKS_STATUSLINE_ASCII=1` to opt out
|
|
52
|
-
* fully), so a separate tier for NO_COLOR is no longer needed.
|
|
53
|
-
*
|
|
54
|
-
* `ascii` is the strict no-color, no-Unicode-extra tier; it is the
|
|
55
|
-
* single safe option for plain log / file consumers.
|
|
56
|
-
*
|
|
57
|
-
* Why `PEAKS_STATUSLINE_ASCII` outranks `NO_COLOR`:
|
|
58
|
-
* The two env vars are NOT redundant. `NO_COLOR` (https://no-color.org)
|
|
59
|
-
* is the cross-industry signal that NO escape sequences should be emitted
|
|
60
|
-
* — its only effect is "ANSI off". The Unicode-extra glyphs (●, █, ░, etc.)
|
|
61
|
-
* are NOT ANSI escape sequences; they are UTF-8 characters and `NO_COLOR`
|
|
62
|
-
* does not address them. `PEAKS_STATUSLINE_ASCII` is the adapter-internal
|
|
63
|
-
* "signal source is a tiny terminal without UTF-8" override and drops the
|
|
64
|
-
* glyphs to ASCII shape as well. Both are correct signals about
|
|
65
|
-
* different concerns; the ASCII override wins because it is strictly
|
|
66
|
-
* narrower (a tiny terminal needs both no-ANSI AND no-Unicode-extra).
|
|
67
|
-
*
|
|
68
|
-
* The deliberate ordering keeps the rendered text ANSI-free when the consumer
|
|
69
|
-
* is a logger, a file, or any non-interactive sink, and only enables ANSI
|
|
70
|
-
* under an explicit supported condition (TTY + no env veto).
|
|
71
|
-
*/
|
|
72
17
|
export declare function resolveStatusLineCapability(input: {
|
|
73
18
|
readonly env: NodeJS.ProcessEnv;
|
|
74
19
|
readonly isTTY: boolean;
|
|
75
20
|
readonly forced?: StatusLineCapability;
|
|
76
21
|
}): StatusLineCapability;
|
|
22
|
+
export declare function isNoColorEnv(env: NodeJS.ProcessEnv): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Visible-character width of an ANSI-bearing string. Skips every
|
|
25
|
+
* `\x1b[...m` escape so the count reflects what the terminal paints,
|
|
26
|
+
* not the byte length on the wire.
|
|
27
|
+
*/
|
|
28
|
+
export declare function visibleCharWidth(s: string): number;
|
|
29
|
+
interface AnsiToken {
|
|
30
|
+
readonly kind: 'text' | 'esc';
|
|
31
|
+
readonly value: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Split an ANSI string into a flat token stream. Escape sequences are
|
|
35
|
+
* grouped as `\x1b[...m` (single token, kind='esc'); everything else
|
|
36
|
+
* is grouped as a single 'text' run. Used by {@link applyMarquee} to
|
|
37
|
+
* locate visible-cell ranges inside an SGR-bearing string.
|
|
38
|
+
*/
|
|
39
|
+
export declare function tokenizeAnsi(s: string): readonly AnsiToken[];
|
|
40
|
+
/**
|
|
41
|
+
* Apply the marquee highlight band to an ANSI-bearing string. Pure.
|
|
42
|
+
* - `bandStart` / `bandEnd` are visible-cell indices (0-based).
|
|
43
|
+
* - Cells in [bandStart, bandEnd] receive a `\x1b[1;38;2;224;224;224m`
|
|
44
|
+
* prefix; cells after the band receive the closing `\x1b[0m` reset
|
|
45
|
+
* so the rest of the line keeps its original SGR.
|
|
46
|
+
* - Empty strings and zero-width bands return the input unchanged.
|
|
47
|
+
*/
|
|
48
|
+
export declare function applyMarqueeHighlight(s: string, bandStart: number, bandEnd: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* Compute the marquee band for a string at time `nowMs` and re-emit
|
|
51
|
+
* the string with the band applied. ASCII tier is a pass-through (no
|
|
52
|
+
* SGR to inject) — the scanner has no visible effect on plain text.
|
|
53
|
+
*/
|
|
54
|
+
export declare function applyMarquee(s: string, nowMs: number, capability: StatusLineCapability): string;
|
|
77
55
|
/**
|
|
78
56
|
* Render the status line. Pure — no I/O, no side effects. The output is
|
|
79
57
|
* a single short line suitable for the bottom-of-terminal status bar.
|
|
@@ -85,5 +63,17 @@ export declare function resolveStatusLineCapability(input: {
|
|
|
85
63
|
* Compact-state precedence: when `model.compact.kind !== 'none'` the
|
|
86
64
|
* compact bar replaces the active/idle/stale content. The C1 baseline
|
|
87
65
|
* line is preserved when the compact state is `none`.
|
|
66
|
+
*
|
|
67
|
+
* The full line is wrapped by {@link applyMarquee} so the brand-purple
|
|
68
|
+
* text + non-focal bar cells share a single colour surface while the
|
|
69
|
+
* scanner band paints its `#E0E0E0` highlight over a moving slice.
|
|
70
|
+
*
|
|
71
|
+
* Idle suppression: the marquee is OFF when `model.state === 'idle'`
|
|
72
|
+
* (and no compact state is active). Idle keeps the slow-blink `○`
|
|
73
|
+
* as the only motion — the user is asking whether the harness is
|
|
74
|
+
* running a skill; the scan band would compete for attention and
|
|
75
|
+
* obscure that question. Active / stale / invalid-presence / compact
|
|
76
|
+
* states all keep the marquee.
|
|
88
77
|
*/
|
|
89
|
-
export declare function renderStatusLine(model: StatusLineModel, options?: StatusLineRenderOptions): string;
|
|
78
|
+
export declare function renderStatusLine(model: StatusLineModel, options?: StatusLineRenderOptions, env?: NodeJS.ProcessEnv): string;
|
|
79
|
+
export {};
|