peaks-loop 4.0.11 → 4.0.13
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 +47 -0
- package/dist/cli/commands/code-commands.js +2 -0
- package/dist/cli/commands/code-orchestrator-can-do.d.ts +17 -0
- package/dist/cli/commands/code-orchestrator-can-do.js +75 -0
- package/dist/cli/commands/core/doctor-command.js +26 -3
- package/dist/cli/commands/workspace/reconcile-command.js +5 -2
- package/dist/services/code/orchestrator-can-do.d.ts +114 -0
- package/dist/services/code/orchestrator-can-do.js +205 -0
- package/dist/services/doctor/doctor-service/checks/multi-binary-drift.d.ts +61 -0
- package/dist/services/doctor/doctor-service/checks/multi-binary-drift.js +268 -0
- package/dist/services/doctor/doctor-service/checks/skill-presence.js +1 -1
- package/dist/services/doctor/doctor-service/checks/workspace-layout.d.ts +5 -2
- package/dist/services/doctor/doctor-service/checks/workspace-layout.js +70 -9
- package/dist/services/doctor/doctor-service/plugin-registry.js +2 -0
- package/dist/services/doctor/doctor-service/report/final-summary.d.ts +13 -5
- package/dist/services/doctor/doctor-service/report/final-summary.js +19 -9
- package/dist/services/doctor/doctor-service/types.d.ts +64 -0
- package/dist/services/doctor/doctor-service.d.ts +2 -1
- package/dist/services/doctor/doctor-service.js +1 -0
- package/dist/services/doctor/index.d.ts +1 -1
- package/dist/services/doctor/index.js +1 -1
- package/dist/services/hooks/presence-marker-detector.d.ts +7 -5
- package/dist/services/hooks/presence-marker-detector.js +45 -85
- package/dist/services/migration/v2-10-to-v2-11-service.js +1 -3
- package/dist/services/sc/sc-service.d.ts +11 -7
- package/dist/services/sc/sc-service.js +29 -26
- package/dist/services/session/binding-store.d.ts +6 -6
- package/dist/services/skills/hooks-settings-service.d.ts +1 -1
- package/dist/services/skills/hooks-settings-service.js +1 -1
- package/dist/services/skills/skill-presence-service.d.ts +0 -1
- package/dist/services/skills/skill-presence-service.js +112 -102
- package/dist/services/skills/skill-statusline-renderer.d.ts +3 -1
- package/dist/services/skills/skill-statusline-renderer.js +37 -1
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/skills/skill-statusline-service.js +109 -53
- package/dist/services/skills/skill-statusline-sid-suffix.d.ts +55 -0
- package/dist/services/skills/skill-statusline-sid-suffix.js +65 -0
- package/package.json +4 -4
- package/skills/peaks-code/SKILL.md +6 -0
- package/skills/peaks-code/references/session-overload-signal-index.md +60 -0
|
@@ -2,7 +2,15 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from '
|
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { findProjectRoot } from '../config/config-safety.js';
|
|
4
4
|
import { ensureMemoryBootstrap } from '../memory/project-memory-service.js';
|
|
5
|
-
import { getSessionMeta } from '../session/session-manager.js';
|
|
5
|
+
import { getSessionId, getSessionMeta } from '../session/session-manager.js';
|
|
6
|
+
// Slice 4.0.8 compat shim: the canonical write path lives in
|
|
7
|
+
// `presence-lease-service.ts`. The legacy shim dynamically imports
|
|
8
|
+
// it inside `setSkillPresence` so the cold path of the legacy
|
|
9
|
+
// `getSkillPresence` / `checkStalePresence` / `clearStalePresenceOnRotation`
|
|
10
|
+
// read paths stays cheap. The static imports below are reserved for
|
|
11
|
+
// the migration window and are referenced through the dynamic
|
|
12
|
+
// `leaseMod.*` accessor at runtime.
|
|
13
|
+
import { listPresenceLeases } from './presence-lease-service.js';
|
|
6
14
|
// Re-export the 4.0.8 compat surface so legacy callers
|
|
7
15
|
// (`presence-service` consumers in `code-mode-gate-commands.ts`,
|
|
8
16
|
// `mode-enforcement.ts`, `code-job-shape-commands.ts`, etc.) keep
|
|
@@ -37,14 +45,13 @@ function getCurrentOuterSessionId() {
|
|
|
37
45
|
return claude;
|
|
38
46
|
return undefined;
|
|
39
47
|
}
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
const PRESENCE_FILE_LEGACY = '.peaks/.active-skill.json';
|
|
48
|
+
// Slice 4.0.11 statusline-sid-scoped-lease C: the deprecated
|
|
49
|
+
// project-level single-slot presence files
|
|
50
|
+
// `.peaks/_runtime/active-skill.json` and `.peaks/.active-skill.json`
|
|
51
|
+
// are REMOVED from this module. The canonical sid-scoped lease
|
|
52
|
+
// projection (`.peaks/_runtime/<sid>/leases/presence-*.json`) is the
|
|
53
|
+
// only source of truth. The legacy constants `PRESENCE_FILE` and
|
|
54
|
+
// `PRESENCE_FILE_LEGACY` are deleted; readers use `listPresenceLeases`.
|
|
48
55
|
const SESSION_FILE = join('.peaks', '_runtime', 'session.json');
|
|
49
56
|
const SESSION_FILE_LEGACY = '.peaks/.session.json';
|
|
50
57
|
function resolveProjectRoot(override) {
|
|
@@ -52,36 +59,6 @@ function resolveProjectRoot(override) {
|
|
|
52
59
|
return resolve(override);
|
|
53
60
|
return findProjectRoot(process.cwd()) ?? process.cwd();
|
|
54
61
|
}
|
|
55
|
-
function resolvePresencePath(projectRootOverride) {
|
|
56
|
-
return resolve(resolveProjectRoot(projectRootOverride), PRESENCE_FILE);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Back-compat read for the active-skill marker. Prefers the new
|
|
60
|
-
* canonical `.peaks/_runtime/active-skill.json`; falls back to the
|
|
61
|
-
* legacy `.peaks/.active-skill.json` for one minor release.
|
|
62
|
-
*
|
|
63
|
-
* Returns the parsed SkillPresence object, or null when neither
|
|
64
|
-
* file is present / valid. The legacy file is never written by
|
|
65
|
-
* current code — only the new path receives writes.
|
|
66
|
-
*/
|
|
67
|
-
function readSkillPresenceBackCompat(projectRootOverride) {
|
|
68
|
-
const presencePath = resolvePresencePath(projectRootOverride);
|
|
69
|
-
const legacyPath = resolve(resolveProjectRoot(projectRootOverride), PRESENCE_FILE_LEGACY);
|
|
70
|
-
const pathToRead = existsSync(presencePath) ? presencePath : legacyPath;
|
|
71
|
-
if (!existsSync(pathToRead))
|
|
72
|
-
return null;
|
|
73
|
-
try {
|
|
74
|
-
const raw = readFileSync(pathToRead, 'utf8');
|
|
75
|
-
const parsed = JSON.parse(raw);
|
|
76
|
-
if (typeof parsed?.skill !== 'string' || parsed.skill.length === 0) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
return { presence: parsed, path: pathToRead };
|
|
80
|
-
}
|
|
81
|
-
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
62
|
/**
|
|
86
63
|
* Resolve the active peaks session id from
|
|
87
64
|
* `.peaks/_runtime/session.json` (legacy: `.peaks/.session.json`).
|
|
@@ -112,6 +89,43 @@ export function getCurrentSessionId(projectRootOverride) {
|
|
|
112
89
|
return null;
|
|
113
90
|
}
|
|
114
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Slice 4.0.11 statusline-sid-scoped-lease C: read the canonical
|
|
94
|
+
* sid-scoped lease projection and project the freshest in-flight
|
|
95
|
+
* lease into the legacy `SkillPresence` shape that the public
|
|
96
|
+
* `getSkillPresence` / `checkStalePresence` /
|
|
97
|
+
* `clearStalePresenceOnRotation` consumers expect. The projection
|
|
98
|
+
* is lossy (the lease does not carry `outerSessionId`; `setAt`
|
|
99
|
+
* maps to `startedAt`; `lastHeartbeat` is reused as `lastHeartbeat`).
|
|
100
|
+
*
|
|
101
|
+
* Returns `null` when the bound session has no in-flight leases
|
|
102
|
+
* (`running` or `preparing`); terminalized leases are ignored so
|
|
103
|
+
* the read does not resurrect a stale skill on a fresh session.
|
|
104
|
+
*/
|
|
105
|
+
function readSkillPresenceFromLease(projectRootOverride) {
|
|
106
|
+
const projectRoot = resolveProjectRoot(projectRootOverride);
|
|
107
|
+
const sessionId = getSessionId(projectRoot);
|
|
108
|
+
if (sessionId === null)
|
|
109
|
+
return null;
|
|
110
|
+
const leases = listPresenceLeases(projectRoot, sessionId);
|
|
111
|
+
const inFlight = leases.filter((l) => l.status === 'running' || l.status === 'preparing');
|
|
112
|
+
if (inFlight.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
inFlight.sort((a, b) => b.lastHeartbeat.localeCompare(a.lastHeartbeat));
|
|
115
|
+
const lease = inFlight[0];
|
|
116
|
+
if (lease === undefined)
|
|
117
|
+
return null;
|
|
118
|
+
// The lease carries `callerId` (the adapter / harness session id),
|
|
119
|
+
// not the legacy `outerSessionId` field. Project it for back-compat
|
|
120
|
+
// with `clearStalePresenceOnRotation`'s outer-mismatch check.
|
|
121
|
+
return {
|
|
122
|
+
skill: lease.skill,
|
|
123
|
+
sessionId,
|
|
124
|
+
outerSessionId: lease.callerId,
|
|
125
|
+
setAt: lease.startedAt,
|
|
126
|
+
lastHeartbeat: lease.lastHeartbeat
|
|
127
|
+
};
|
|
128
|
+
}
|
|
115
129
|
/**
|
|
116
130
|
* Look up the outer-session-id that was bound to the *current* peaks
|
|
117
131
|
* session, i.e. the one written to the per-session
|
|
@@ -160,23 +174,14 @@ function getBoundOuterSessionId(projectRootOverride) {
|
|
|
160
174
|
* legacy `.peaks/.active-skill.json` for one minor release.
|
|
161
175
|
*/
|
|
162
176
|
function getPreviousOuterSessionId(projectRootOverride) {
|
|
163
|
-
const result =
|
|
177
|
+
const result = readSkillPresenceFromLease(projectRootOverride);
|
|
164
178
|
if (result === null)
|
|
165
179
|
return undefined;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return parsed.outerSessionId;
|
|
169
|
-
}
|
|
170
|
-
// Legacy field name. Honour it on the read side so v1.2.x
|
|
171
|
-
// presence files do not show as a false mismatch.
|
|
172
|
-
if (typeof parsed.claudeSessionId === 'string' && parsed.claudeSessionId.length > 0) {
|
|
173
|
-
return parsed.claudeSessionId;
|
|
180
|
+
if (typeof result.outerSessionId === 'string' && result.outerSessionId.length > 0) {
|
|
181
|
+
return result.outerSessionId;
|
|
174
182
|
}
|
|
175
183
|
return undefined;
|
|
176
184
|
}
|
|
177
|
-
export function exportSkillPresence(projectRootOverride) {
|
|
178
|
-
return resolvePresencePath(projectRootOverride);
|
|
179
|
-
}
|
|
180
185
|
// ============================================================================
|
|
181
186
|
// Slice 020 — caller-keyed active-skill marker (D6).
|
|
182
187
|
// ============================================================================
|
|
@@ -336,12 +341,12 @@ export function setSkillPresence(skill, mode, gate, projectRootOverride) {
|
|
|
336
341
|
catch { /* fall through to legacy write */ }
|
|
337
342
|
})();
|
|
338
343
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
// Slice 4.0.11 statusline-sid-scoped-lease A: the canonical write
|
|
345
|
+
// path is now exclusively `presence-lease-service.setPresenceLease`.
|
|
346
|
+
// The legacy single-slot marker file write was removed in this
|
|
347
|
+
// slice: every `setSkillPresence` call flows through the sid-scoped
|
|
348
|
+
// lease + presence-index machinery above. The legacy
|
|
349
|
+
// `active-skill.json` file is NOT recreated here.
|
|
345
350
|
// Skill-activation side effect: ensure `.peaks/memory/` and a full-shape
|
|
346
351
|
// empty `index.json` exist for the project. This is the user-facing fix
|
|
347
352
|
// for "stock projects never get a memory directory or index". Every peaks
|
|
@@ -354,26 +359,17 @@ export function setSkillPresence(skill, mode, gate, projectRootOverride) {
|
|
|
354
359
|
return presence;
|
|
355
360
|
}
|
|
356
361
|
export function getSkillPresence(projectRootOverride) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
unlinkSync(presencePath);
|
|
366
|
-
}
|
|
367
|
-
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
368
|
-
// best effort
|
|
369
|
-
}
|
|
370
|
-
return null;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return presence;
|
|
362
|
+
// Slice 4.0.11 statusline-sid-scoped-lease C: read from the
|
|
363
|
+
// canonical sid-scoped lease projection. The legacy
|
|
364
|
+
// `unlinkSync(presencePath)` purge on session-mismatch is
|
|
365
|
+
// removed: the lease projection is keyed off the bound session
|
|
366
|
+
// id, so a mismatch cannot happen — `listPresenceLeases`
|
|
367
|
+
// already filters by the session-id resolved from the workspace
|
|
368
|
+
// binding.
|
|
369
|
+
return readSkillPresenceFromLease(projectRootOverride);
|
|
374
370
|
}
|
|
375
371
|
export function checkStalePresence(opts) {
|
|
376
|
-
const result =
|
|
372
|
+
const result = readSkillPresenceFromLease(opts?.projectRootOverride);
|
|
377
373
|
// `opts?.currentOuter === undefined` is the omitted-key case. A
|
|
378
374
|
// falsy string `''` is an explicit "no signal" (used by tests to
|
|
379
375
|
// simulate a CLI run with no harness env vars).
|
|
@@ -389,7 +385,7 @@ export function checkStalePresence(opts) {
|
|
|
389
385
|
recordedOuterSessionId: undefined
|
|
390
386
|
};
|
|
391
387
|
}
|
|
392
|
-
const recorded = result.
|
|
388
|
+
const recorded = result.outerSessionId;
|
|
393
389
|
// Suppress false-positives when NEITHER side recorded an outer
|
|
394
390
|
// session id (legacy project, no harness signal). Two unknowns
|
|
395
391
|
// are not a swap — they are "no signal available yet".
|
|
@@ -399,7 +395,7 @@ export function checkStalePresence(opts) {
|
|
|
399
395
|
return {
|
|
400
396
|
stale: false,
|
|
401
397
|
reason: null,
|
|
402
|
-
presence: result
|
|
398
|
+
presence: result,
|
|
403
399
|
currentOuterSessionId: current,
|
|
404
400
|
recordedOuterSessionId: recorded
|
|
405
401
|
};
|
|
@@ -408,7 +404,7 @@ export function checkStalePresence(opts) {
|
|
|
408
404
|
return {
|
|
409
405
|
stale: false,
|
|
410
406
|
reason: null,
|
|
411
|
-
presence: result
|
|
407
|
+
presence: result,
|
|
412
408
|
currentOuterSessionId: current,
|
|
413
409
|
recordedOuterSessionId: recorded
|
|
414
410
|
};
|
|
@@ -416,7 +412,7 @@ export function checkStalePresence(opts) {
|
|
|
416
412
|
return {
|
|
417
413
|
stale: true,
|
|
418
414
|
reason: 'outer-session-mismatch',
|
|
419
|
-
presence: result
|
|
415
|
+
presence: result,
|
|
420
416
|
currentOuterSessionId: current,
|
|
421
417
|
recordedOuterSessionId: recorded
|
|
422
418
|
};
|
|
@@ -444,11 +440,11 @@ export function checkStalePresence(opts) {
|
|
|
444
440
|
* decide what the "rotated-out" id was.
|
|
445
441
|
*/
|
|
446
442
|
export function clearStalePresenceOnRotation(opts) {
|
|
447
|
-
const result =
|
|
443
|
+
const result = readSkillPresenceFromLease(opts.projectRootOverride);
|
|
448
444
|
if (result === null) {
|
|
449
445
|
return { cleared: false, reason: 'no-presence' };
|
|
450
446
|
}
|
|
451
|
-
const recorded = result.
|
|
447
|
+
const recorded = result.outerSessionId;
|
|
452
448
|
const current = opts.currentOuterSessionId;
|
|
453
449
|
// If the recorded outer id matches the new (current) outer id,
|
|
454
450
|
// this presence is NOT stale — the user just reconnected from
|
|
@@ -483,24 +479,25 @@ export function clearStalePresenceOnRotation(opts) {
|
|
|
483
479
|
};
|
|
484
480
|
}
|
|
485
481
|
export function touchSkillHeartbeat(projectRootOverride) {
|
|
486
|
-
|
|
487
|
-
|
|
482
|
+
// Slice 4.0.11 statusline-sid-scoped-lease C: read from the
|
|
483
|
+
// canonical sid-scoped lease projection. The legacy
|
|
484
|
+
// unlink-on-session-mismatch purge is removed: the lease is
|
|
485
|
+
// keyed off the bound session id, so the mismatch path cannot
|
|
486
|
+
// fire (a lease under session A is never returned when the bound
|
|
487
|
+
// session is B).
|
|
488
|
+
const presence = readSkillPresenceFromLease(projectRootOverride);
|
|
489
|
+
if (presence === null)
|
|
488
490
|
return null;
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
return null;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
491
|
+
// Slice 4.0.11 statusline-sid-scoped-lease A: the legacy write to
|
|
492
|
+
// the single-slot marker file was removed. Heartbeat refresh now
|
|
493
|
+
// lives exclusively in `presence-lease-service.setPresenceLease`
|
|
494
|
+
// (the canonical sid-scoped lease). This function remains as a
|
|
495
|
+
// read-only back-compat shim for the 4-B / 4-C sub-slices; callers
|
|
496
|
+
// still receive the in-memory `presence` (with the new
|
|
497
|
+
// `lastHeartbeat` stamped) but the legacy `active-skill.json` file
|
|
498
|
+
// is no longer touched. The canonical lease / presence-index
|
|
499
|
+
// entries are the source of truth for heartbeat freshness.
|
|
502
500
|
presence.lastHeartbeat = new Date().toISOString();
|
|
503
|
-
writeFileSync(presencePath, JSON.stringify(presence, null, 2), 'utf8');
|
|
504
501
|
return presence;
|
|
505
502
|
}
|
|
506
503
|
export function clearSkillPresence(projectRootOverride) {
|
|
@@ -518,12 +515,25 @@ export function clearSkillPresence(projectRootOverride) {
|
|
|
518
515
|
// `.peaks/.active-skill.json` so a stale marker from a prior CLI
|
|
519
516
|
// version cannot resurrect after a fresh `clear`. The canonical
|
|
520
517
|
// lease + index entries are NOT touched by this shim — they
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
518
|
+
// Slice 4.0.11 statusline-sid-scoped-lease C: the deprecated
|
|
519
|
+
// single-slot presence files (`.peaks/_runtime/active-skill.json`
|
|
520
|
+
// and `.peaks/.active-skill.json`) are removed. The canonical
|
|
521
|
+
// sid-scoped lease projection (under
|
|
522
|
+
// `.peaks/_runtime/<sid>/leases/`) is untouched here — workflow
|
|
523
|
+
// leases route through `terminalizeWorkflow`, NOT this shim.
|
|
524
|
+
//
|
|
525
|
+
// This shim remains as a best-effort stale-file cleanup for
|
|
526
|
+
// projects that still carry the deprecated single-slot file from
|
|
527
|
+
// a pre-4.0.11 CLI version. After one minor release the legacy
|
|
528
|
+
// paths are gone and the loop is a no-op. Returns true when a
|
|
529
|
+
// stale file was actually removed.
|
|
530
|
+
const projectRoot = resolveProjectRoot(projectRootOverride);
|
|
531
|
+
const candidates = [
|
|
532
|
+
resolve(projectRoot, '.peaks', '_runtime', 'active-skill.json'),
|
|
533
|
+
resolve(projectRoot, '.peaks', '.active-skill.json')
|
|
534
|
+
];
|
|
525
535
|
let cleared = false;
|
|
526
|
-
for (const p of
|
|
536
|
+
for (const p of candidates) {
|
|
527
537
|
if (!existsSync(p))
|
|
528
538
|
continue;
|
|
529
539
|
try {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { StatusLineModel } from './skill-statusline-service.js';
|
|
2
|
+
import { computeRootSuffix as computeRootSuffixImpl } from './skill-statusline-sid-suffix.js';
|
|
3
|
+
export { formatShortSid } from './skill-statusline-sid-suffix.js';
|
|
4
|
+
export declare const computeRootSuffix: typeof computeRootSuffixImpl;
|
|
2
5
|
/**
|
|
3
6
|
* Pure formatting layer for the Peaks statusLine. Takes the read-only status
|
|
4
7
|
* model and produces the single line Claude Code paints at the bottom of the
|
|
@@ -76,4 +79,3 @@ export declare function applyMarquee(s: string, nowMs: number, capability: Statu
|
|
|
76
79
|
* states all keep the marquee.
|
|
77
80
|
*/
|
|
78
81
|
export declare function renderStatusLine(model: StatusLineModel, options?: StatusLineRenderOptions, env?: NodeJS.ProcessEnv): string;
|
|
79
|
-
export {};
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
|
+
import { computeRootSuffix as computeRootSuffixImpl, } from './skill-statusline-sid-suffix.js';
|
|
3
|
+
// Re-export so existing test imports
|
|
4
|
+
// (`import { formatShortSid, computeRootSuffix } from '.../skill-statusline-renderer'`)
|
|
5
|
+
// keep working byte-identically after the helper extraction in slice
|
|
6
|
+
// 2026-08-05-statusline-sid-only-marker-and-multi-binary-drift-guard
|
|
7
|
+
// repair cycle.
|
|
8
|
+
export { formatShortSid } from './skill-statusline-sid-suffix.js';
|
|
9
|
+
export const computeRootSuffix = computeRootSuffixImpl;
|
|
2
10
|
/**
|
|
3
11
|
* Internal: the brand prefix is plain ASCII so a tiny terminal without
|
|
4
12
|
* monospaced UTF-8 still sees a recognizable string. The mountain glyphs
|
|
@@ -186,6 +194,14 @@ function rootLabel(projectRoot) {
|
|
|
186
194
|
return '';
|
|
187
195
|
return basename(projectRoot);
|
|
188
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* `formatShortSid` + `computeRootSuffix` were extracted to
|
|
199
|
+
* `./skill-statusline-sid-suffix.ts` in the slice
|
|
200
|
+
* 2026-08-05-statusline-sid-only-marker-and-multi-binary-drift-guard
|
|
201
|
+
* repair cycle so the renderer file stays under the Karpathy
|
|
202
|
+
* 800-line cap. Both helpers are re-exported above for back-compat
|
|
203
|
+
* with existing test imports.
|
|
204
|
+
*/
|
|
189
205
|
/**
|
|
190
206
|
* Build the "middle" of the status line — everything between the brand
|
|
191
207
|
* prefix and the project root label. Kept separate so the token layout
|
|
@@ -568,7 +584,27 @@ export function renderStatusLine(model, options, env) {
|
|
|
568
584
|
const noColor = env !== undefined ? isNoColor(env) : false;
|
|
569
585
|
const palette = paletteFor(capability, noColor);
|
|
570
586
|
const root = rootLabel(model.projectRoot);
|
|
571
|
-
|
|
587
|
+
// short-sid suffix (slices
|
|
588
|
+
// - 2026-08-05-statusline-empty-render-and-short-sid-suffix (active only)
|
|
589
|
+
// - 2026-08-05-statusline-sid-only-marker (idle + stale also)
|
|
590
|
+
// ):
|
|
591
|
+
// the project root cell carries ` [shortSid]` after `peaks-loop` whenever
|
|
592
|
+
// a canonical session id resolves. Per-state matrix:
|
|
593
|
+
//
|
|
594
|
+
// active → append sid when sessionId !== null (AC5/PB1)
|
|
595
|
+
// idle → append sid when sessionId !== null (AC1)
|
|
596
|
+
// stale → append sid when sessionId !== null (AC2)
|
|
597
|
+
// invalid-presence → NEVER append sid (G2 — read-error signal must
|
|
598
|
+
// stay loud; showing sid would mask the warning)
|
|
599
|
+
// (AC4)
|
|
600
|
+
//
|
|
601
|
+
// Compact state with presence is still rendered with the active sid logic
|
|
602
|
+
// (the active branch already covered this in the prior slice). Compact state
|
|
603
|
+
// without presence is the same as the underlying idle / invalid-presence /
|
|
604
|
+
// stale branch; the helper inherits that. Pure ASCII (no narrow space /
|
|
605
|
+
// smart quotes), so Windows PowerShell + Git Bash + zsh render byte-
|
|
606
|
+
// identical (AC7 of the prior slice + AC10 of this slice).
|
|
607
|
+
const rootSuffix = computeRootSuffix(model, root, palette);
|
|
572
608
|
const brand = brandText(capability, noColor);
|
|
573
609
|
// Breathing pulse: key off a single wall-clock read at the start of
|
|
574
610
|
// render so the output is deterministic for a given invocation. The
|
|
@@ -27,6 +27,20 @@ export type StatusLineModel = {
|
|
|
27
27
|
ageMs: number | null;
|
|
28
28
|
compact: CompactStatuslineState;
|
|
29
29
|
activeLeaf: StatusLineActiveLeaf | null;
|
|
30
|
+
/**
|
|
31
|
+
* Slice 2026-08-05-statusline-sid-only-marker: the canonical session
|
|
32
|
+
* id resolved for the project root (`getSessionIdCanonical`), or
|
|
33
|
+
* `null` when no project root is bound or no `.peaks/_runtime/<sid>/`
|
|
34
|
+
* session is on disk. The renderer reads this to append ` [shortSid]`
|
|
35
|
+
* to the project name cell for idle / stale states (G1); invalid-
|
|
36
|
+
* presence still suppresses the suffix (G2).
|
|
37
|
+
*
|
|
38
|
+
* Carry-forward for AC1/AC2/AC3: the value is `null` whenever
|
|
39
|
+
* `projectRoot === null` — the renderer relies on this to skip the
|
|
40
|
+
* suffix without re-running `getSessionIdCanonical` against a non-
|
|
41
|
+
* existent root.
|
|
42
|
+
*/
|
|
43
|
+
sessionId: string | null;
|
|
30
44
|
};
|
|
31
45
|
export declare function parseStatusLineStdin(raw: string): StatusLineStdin | null;
|
|
32
46
|
export declare function buildStatusLineModel(stdin: StatusLineStdin | null, nowMs: number): StatusLineModel;
|