@aemonge-dev/pi-stardeck 0.14.0 → 0.15.0

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.
@@ -57,49 +57,36 @@ The provider uses the installed OpenSpec CLI and reads only each listed change's
57
57
  5. The task artifact is read once per refresh to recover labels such as `3.8` and kinds
58
58
  such as Story, Spike, Lab, or Hack.
59
59
 
60
- The session focus takes precedence over configured `openspec.change`, which takes
61
- precedence over `PI_OPENSPEC_CHANGE`. Configuration and environment values can select a
62
- change for collection/debugging, but they do not create session task focus or expose an
63
- active OpenSpec footer by themselves.
60
+ The focused change no longer lives in session state: the cockpit reads the
61
+ room aim (`openspec/.focus`) directly and prints its fields verbatim.
64
62
 
65
- ## Session focus and terminal title
63
+ active OpenSpec footer by themselves.
66
64
 
67
- A Pi session has zero or one explicit OpenSpec task focus and an independent
68
- ephemeral Session Work state. Display priority is OpenSpec
69
- Task → Session Work → safe direct fallback. OpenSpec focus clears active Session
70
- Work when acquired, preventing hidden or stale work from resurfacing later.
65
+ ## Room aim and terminal title
71
66
 
72
- Session Work is a legacy surface: its writer tool and `/work` command are retired, and
73
- only old sessions' versioned custom entries remain, restored fail-soft on the active
74
- Pi session branch (never a repository status file). OpenSpec focus is stored the same
75
- way and supports this lifecycle:
67
+ The focused change is the Human's room aim — `pios-gate <slug>` writes
68
+ `openspec/.focus`, the cockpit reads it on every refresh and renders its five
69
+ fields verbatim on the right work tile:
76
70
 
77
71
  ```text
78
- /openspec-focus set <change> <task>
79
- /openspec-focus validate
80
- /openspec-focus replace <change> <task>
81
- /openspec-focus clear
72
+ {change} ⟩ {artifact_name} {artifact-idx} › {task} ⟩ {next-step}
82
73
  ```
83
74
 
84
- When no OpenSpec portfolio is available, the matching row-one footer fallback is the
85
- compact `▶ Focus` or `◇ Validate`; an entirely empty
86
- project uses ` no OpenSpec`. The detailed Session Work intent remains in the header
87
- instead of being duplicated in the footer. Active and validation states also replace the
88
- fallback terminal title. Reload, resume, and tree navigation restore the newest valid
89
- branch entry. The agent-callable `openspec_focus` tool shares the command's `status`,
90
- safeguarded `set`, deliberate `replace`, validating `validate`, and persistent `clear`
91
- transitions; the `awaitingValidation` flag marks a focused task as awaiting Human
92
- validation and auto-clears on the next `set`, `replace`, or `clear`. The
93
- retired `work_focus`, `stage`, and `title` instruments are gone: stages
94
- derive from events and Taskflow phases, the focus row falls back to the
95
- native session name (named by `pi-title`) and then to legacy `session-subject`
96
- entries from old sessions, read defensively and fail-soft.
75
+ Read as-is, printed as-is: no prefixes, no relabeling. A missing field drops
76
+ its segment; absence never becomes zero; an aim without a change slug is no
77
+ aim at all. The agent never writes the file — it is the Human's word, written
78
+ by their terminal verbs.
79
+
80
+ Session Work and the `openspec_focus` tool are retired instruments: old
81
+ sessions' versioned custom entries restore fail-soft and carry no writers.
82
+ The focus row falls back to the native session name (named by `pi-title`)
83
+ and then to legacy `session-subject` entries from old sessions, read
84
+ defensively and fail-soft.
97
85
 
98
86
  Global routing belongs to native Ramona policy rather than this status package. This
99
87
  package does not inject parent routing prompts, block parent tools, create Session Work
100
88
  for a global route, or promote a raw request into OpenSpec focus. It continues to own
101
- branch-local OpenSpec and Session Work state, commands, restoration, titles, and footer
102
- publication.
89
+ branch-local Session Work restoration, commands, titles, and footer publication.
103
90
 
104
91
  The header projection uses the rich lifecycle `understanding`, `working`, `waiting`,
105
92
  `assuring`, `learning`, `answering`, `blocked`, `listening`, or `aborted`. It separately
@@ -318,8 +305,7 @@ configuration directory in this installation). This repository keeps the source
318
305
  ```
319
306
 
320
307
  Unknown or invalid fields are ignored and reported by the debug command; they do not
321
- prevent Pi from starting. `openspec.change` is an explicit collection target, not a
322
- session-scoped task focus.
308
+ prevent Pi from starting.
323
309
 
324
310
  ## Commands
325
311
 
@@ -7,18 +7,14 @@ import { promises as fs, watch } from 'node:fs';
7
7
  import { dirname, join, resolve } from 'node:path';
8
8
  import {
9
9
  formatActiveWorkTitle,
10
- type OpenSpecFocus,
11
- parseOpenSpecFocus,
12
- restoreOpenSpecFocus,
13
10
  restoreSessionSubject,
14
- sameOpenSpecFocus,
15
11
  sameSessionSubject,
16
12
  type SessionSubject,
17
13
  } from './src/active-work.ts';
18
14
  import { collectDiagnostics } from './src/diagnostics.ts';
19
15
  import { loadConfig } from './src/config.ts';
20
- import { resolveGateAct } from './src/gate-act.ts';
21
- import { collectOpenSpec, collectOpenSpecOverview } from './src/openspec.ts';
16
+ import { readRoomAim } from './src/room-aim.ts';
17
+ import { collectOpenSpecOverview } from './src/openspec.ts';
22
18
  import { collectOrchestration } from './src/orchestration.ts';
23
19
  import {
24
20
  buildHeaderStatusEvent,
@@ -171,15 +167,6 @@ class GalacticaStatusRuntime {
171
167
  private openSpecFeedback: OpenSpecFeedback | undefined;
172
168
  private openSpecOverview: OpenSpecOverview | undefined;
173
169
  private openSpecProjectDetected: boolean | undefined;
174
- private focus: OpenSpecFocus;
175
- /** Publisher-owned timestamp of the current focus fact — seeded at
176
- * construction, refreshed on every accepted focus change; staleness
177
- * compares it against the change directory's file truth. */
178
- private focusAt: number | undefined;
179
- /** The gate's relayed act line for the focused change — commentary
180
- * beside the declared focus, never a replacement; cleared whenever
181
- * the focus moves so stale gate truth never survives a switch. */
182
- private gateAct: { change: string; line?: string } | undefined;
183
170
  private subject: SessionSubject;
184
171
  private sessionName: string | undefined;
185
172
  private diagnosticsPath: string | undefined;
@@ -208,30 +195,21 @@ class GalacticaStatusRuntime {
208
195
  pi: ExtensionAPI,
209
196
  ctx: SessionContext,
210
197
  publisher: FancyFooterPublisher,
211
- focus: OpenSpecFocus,
212
198
  subject: SessionSubject,
213
199
  sessionName: string | undefined,
214
200
  ) {
215
201
  this.pi = pi;
216
202
  this.ctx = ctx;
217
203
  this.publisher = publisher;
218
- this.focus = focus;
219
204
  this.subject = subject;
220
205
  this.sessionName = sessionName;
221
- this.focusAt = Date.now();
222
- const restoredAwaitingValidation =
223
- focus.mode === 'task' && focus.awaitingValidation;
224
- this.liveLifecycle = restoredAwaitingValidation ? 'waiting' : 'listening';
225
- this.liveActivity = restoredAwaitingValidation
226
- ? { kind: 'awaiting-validation' }
227
- : undefined;
206
+ this.liveLifecycle = 'listening';
228
207
  this.store = new AtomicStatusStore({
229
208
  cwd: ctx.cwd,
230
209
  projectRoot: ctx.cwd,
231
210
  configPath: '',
232
211
  });
233
212
  const seededAt = Date.now();
234
- this.recordEvent({ type: 'focus/openspec', at: seededAt, focus });
235
213
  this.recordEvent({ type: 'focus/subject', at: seededAt, subject });
236
214
  }
237
215
 
@@ -384,7 +362,6 @@ class GalacticaStatusRuntime {
384
362
  const generation = ++this.generation;
385
363
  if (!this.loadedConfig || this.stopped) return;
386
364
  const config = this.loadedConfig.config;
387
- const focus = this.focus;
388
365
  const at = Date.now();
389
366
  let projectRoot = await detectProjectRoot(this.ctx.cwd, this.runner);
390
367
 
@@ -393,6 +370,9 @@ class GalacticaStatusRuntime {
393
370
  openSpecEnabled && (await pathIsDirectory(join(projectRoot, 'openspec')));
394
371
  let openSpecOverview: RefreshOutcome<OpenSpecOverview> = { kind: 'absent' };
395
372
  let openspec: RefreshOutcome<OpenSpecState> = { kind: 'absent' };
373
+ // The per-change collector retired with the focus store: the room aim
374
+ // carries the tile fields verbatim; the overview feeds the rest.
375
+ void openspec;
396
376
 
397
377
  if (hasOpenSpecProject) {
398
378
  openSpecOverview = await this.outcome('openspec', () =>
@@ -403,31 +383,6 @@ class GalacticaStatusRuntime {
403
383
  }
404
384
  }
405
385
 
406
- if (focus.mode === 'none') {
407
- const passiveChange = config.openspec.change ?? process.env.PI_OPENSPEC_CHANGE;
408
- if (passiveChange) {
409
- openspec = await this.outcome('openspec', () =>
410
- collectOpenSpec({
411
- cwd: projectRoot,
412
- runner: this.runner,
413
- configuredChange: passiveChange,
414
- now: at,
415
- }),
416
- );
417
- if (openspec.kind === 'success') projectRoot = openspec.value.projectRoot;
418
- }
419
- } else {
420
- openspec = await this.outcome('openspec', () =>
421
- collectOpenSpec({
422
- cwd: projectRoot,
423
- runner: this.runner,
424
- configuredChange: focus.change,
425
- now: at,
426
- }),
427
- );
428
- if (openspec.kind === 'success') projectRoot = openspec.value.projectRoot;
429
- }
430
-
431
386
  const [diagnostics, orchestration] = await Promise.all([
432
387
  !config.enabled || !config.diagnostics.enabled
433
388
  ? Promise.resolve<RefreshOutcome<DiagnosticsState>>({ kind: 'absent' })
@@ -457,26 +412,9 @@ class GalacticaStatusRuntime {
457
412
  }),
458
413
  ]);
459
414
 
460
- if (
461
- this.stopped ||
462
- generation !== this.generation ||
463
- !sameOpenSpecFocus(focus, this.focus)
464
- ) {
415
+ if (this.stopped || generation !== this.generation) {
465
416
  return;
466
417
  }
467
- // The gate act is focus-owned commentary: resolved only for a task
468
- // focus inside an OpenSpec project, after the commit guard, so a
469
- // superseded refresh never spends a probe on stale truth.
470
- if (focus.mode === 'task' && hasOpenSpecProject) {
471
- this.gateAct = {
472
- change: focus.change,
473
- line: await resolveGateAct({
474
- change: focus.change,
475
- projectRoot,
476
- runner: this.runner,
477
- }),
478
- };
479
- }
480
418
  const previous = this.store.get();
481
419
  this.updateRefreshHealth(
482
420
  'openspec',
@@ -499,19 +437,18 @@ class GalacticaStatusRuntime {
499
437
  } else if (openSpecOverview.kind === 'absent') {
500
438
  this.openSpecOverview = undefined;
501
439
  }
502
- this.openSpecFeedback =
503
- !hasOpenSpecProject || focus.mode === 'task'
504
- ? undefined
505
- : openSpecOverview.kind === 'error'
506
- ? 'unavailable'
507
- : openSpecOverview.kind === 'success' &&
508
- openSpecOverview.value.actionableChanges > 0
509
- ? 'no-focus'
510
- : 'clear';
440
+ this.openSpecFeedback = !hasOpenSpecProject
441
+ ? undefined
442
+ : openSpecOverview.kind === 'error'
443
+ ? 'unavailable'
444
+ : openSpecOverview.kind === 'success' &&
445
+ openSpecOverview.value.actionableChanges > 0
446
+ ? 'no-focus'
447
+ : 'clear';
511
448
  this.store.applyRefresh({
512
449
  projectRoot,
513
450
  at,
514
- openspec,
451
+ openspec: { kind: 'absent' },
515
452
  diagnostics,
516
453
  orchestration,
517
454
  });
@@ -605,21 +542,12 @@ class GalacticaStatusRuntime {
605
542
  private publish(): void {
606
543
  if (this.stopped) return;
607
544
  const snapshot = this.store.get();
608
- // Builders read focus facts through the snapshot layer (recordEvent stores
609
- // the same object references), so output stays byte-identical while the
610
- // ledger becomes the feeding seam.
611
- const focus = this.cockpitFacts.openSpec;
612
545
  const subject = this.cockpitFacts.subject;
613
546
  const title = this.currentTitleState(snapshot);
614
- const focusedOpenSpec =
615
- focus.mode === 'task' && snapshot.openspec?.changeId === focus.change
616
- ? snapshot.openspec
617
- : null;
618
547
  const widgets = buildWidgetSnapshots(
619
- { ...snapshot, openspec: focusedOpenSpec },
548
+ { ...snapshot, openspec: null },
620
549
  {
621
- focusedTaskId: focus.mode === 'task' ? focus.taskId : undefined,
622
- openSpecFeedback: focus.mode === 'none' ? this.openSpecFeedback : undefined,
550
+ openSpecFeedback: this.openSpecFeedback,
623
551
  openSpecOverview: this.openSpecOverview,
624
552
  openSpecProjectDetected: this.openSpecProjectDetected,
625
553
  },
@@ -628,8 +556,7 @@ class GalacticaStatusRuntime {
628
556
  this.pi.events.emit(
629
557
  GALACTICA_HEADER_CHANNEL,
630
558
  buildHeaderStatusEvent(widgets, {
631
- openSpec: focusedOpenSpec,
632
- focusedTaskId: focus.mode === 'task' ? focus.taskId : undefined,
559
+ openSpec: null,
633
560
  subject,
634
561
  orchestration: snapshot.orchestration,
635
562
  lifecycle: this.liveLifecycle,
@@ -653,11 +580,8 @@ class GalacticaStatusRuntime {
653
580
  const facts = this.cockpitFacts;
654
581
  // The left title is Human-owned — session name, subject, directory —
655
582
  // never openspec text (truthful-rendered-state law 2026-11-10); the
656
- // gate act rides the work slot for the right-side tile.
657
- const gateAct =
658
- facts.openSpec.mode === 'task' && this.gateAct?.change === facts.openSpec.change
659
- ? this.gateAct.line
660
- : undefined;
583
+ // right tile prints the room aim verbatim (observe-room-aim law).
584
+ const aim = readRoomAim(snapshot.projectRoot);
661
585
  const subjectTitle = formatActiveWorkTitle({
662
586
  subject: facts.subject,
663
587
  ...(this.sessionName ? { sessionName: this.sessionName } : {}),
@@ -666,11 +590,8 @@ class GalacticaStatusRuntime {
666
590
  });
667
591
  return buildTitleState({
668
592
  subject: { state: 'set', title: subjectTitle },
669
- focus: facts.openSpec,
670
- openspec: snapshot.openspec,
593
+ aim,
671
594
  activity: this.titleActivity.current(),
672
- focusAt: this.focusAt,
673
- ...(gateAct ? { gateAct } : {}),
674
595
  });
675
596
  }
676
597
 
@@ -684,10 +605,6 @@ class GalacticaStatusRuntime {
684
605
  return snapshotFromFacts(this.cockpitFacts, this.cockpitVersion);
685
606
  }
686
607
 
687
- currentFocus(): OpenSpecFocus {
688
- return this.focus;
689
- }
690
-
691
608
  currentSubject(): SessionSubject {
692
609
  return this.subject;
693
610
  }
@@ -704,24 +621,6 @@ class GalacticaStatusRuntime {
704
621
  this.setLiveProjection('understanding', { kind: 'understanding' });
705
622
  }
706
623
 
707
- setFocus(focus: OpenSpecFocus): void {
708
- if (sameOpenSpecFocus(this.focus, focus)) return;
709
- this.focus = focus;
710
- this.focusAt = Date.now();
711
- if (focus.mode !== 'task' || this.gateAct?.change !== focus.change) {
712
- this.gateAct = undefined;
713
- }
714
- this.recordEvent({ type: 'focus/openspec', at: Date.now(), focus });
715
- this.store.clearOpenSpec();
716
- if (focus.mode === 'task' && focus.awaitingValidation) {
717
- this.setLiveProjection('waiting', { kind: 'awaiting-validation' });
718
- } else if (focus.mode === 'none') {
719
- this.setLiveProjection('listening');
720
- }
721
- this.publish();
722
- this.launch(this.refresh(false), 'openspec');
723
- }
724
-
725
624
  setSubject(subject: SessionSubject): void {
726
625
  if (sameSessionSubject(this.subject, subject)) return;
727
626
  this.subject = subject;
@@ -904,10 +803,6 @@ class GalacticaStatusRuntime {
904
803
  this.publish();
905
804
  return;
906
805
  }
907
- if (this.focus.mode === 'task') {
908
- this.setLiveProjection('waiting', { kind: 'awaiting-validation' });
909
- return;
910
- }
911
806
  this.setLiveProjection('listening');
912
807
  }
913
808
 
@@ -932,13 +827,10 @@ class GalacticaStatusRuntime {
932
827
  );
933
828
  lines.push(
934
829
  snapshot.openspec
935
- ? `OpenSpec focus: ${formatOpenSpec(
936
- snapshot.openspec,
937
- this.focus.mode === 'task' ? this.focus.taskId : null,
938
- )}`
830
+ ? `OpenSpec: ${formatOpenSpec(snapshot.openspec)}`
939
831
  : this.openSpecFeedback
940
- ? `OpenSpec focus: ${this.openSpecFeedback}`
941
- : 'OpenSpec focus: none',
832
+ ? `OpenSpec: ${this.openSpecFeedback}`
833
+ : 'OpenSpec: no aim',
942
834
  );
943
835
  lines.push(
944
836
  snapshot.diagnostics
@@ -960,7 +852,6 @@ class GalacticaStatusRuntime {
960
852
  projectRoot: snapshot.projectRoot,
961
853
  configPath: snapshot.configPath,
962
854
  configWarnings: snapshot.configWarnings,
963
- focus: this.focus,
964
855
  openSpecFeedback: this.openSpecFeedback ?? null,
965
856
  openSpecOverview: this.openSpecOverview ?? null,
966
857
  detected: {
@@ -1011,30 +902,12 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1011
902
  }
1012
903
  });
1013
904
 
1014
- // work-state owns focus writes and emits `focus-changed` after each
1015
- // successful one (pi.events); the renderer subscribes for immediate
1016
- // repaint (pure-renderer-split Slice 2).
1017
- const stopFocusRepaint = pi.events.on('focus-changed', (data: unknown) => {
1018
- const focus = parseOpenSpecFocus(data);
1019
- if (!focus) return;
1020
- runtime?.setFocus(focus);
1021
- runtime?.settle();
1022
- });
1023
-
1024
905
  pi.on('session_start', (_event, ctx) => {
1025
906
  runtime?.stop();
1026
907
  const branch = ctx.sessionManager.getBranch();
1027
- const focus = restoreOpenSpecFocus(branch);
1028
908
  const subject = restoreSessionSubject(branch);
1029
909
  const sessionName = pi.getSessionName() ?? ctx.sessionManager.getSessionName();
1030
- runtime = new GalacticaStatusRuntime(
1031
- pi,
1032
- ctx,
1033
- publisher,
1034
- focus,
1035
- subject,
1036
- sessionName,
1037
- );
910
+ runtime = new GalacticaStatusRuntime(pi, ctx, publisher, subject, sessionName);
1038
911
  runtime.start();
1039
912
  });
1040
913
 
@@ -1044,7 +917,6 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1044
917
 
1045
918
  pi.on('session_tree', (_event, ctx) => {
1046
919
  const branch = ctx.sessionManager.getBranch();
1047
- runtime?.setFocus(restoreOpenSpecFocus(branch));
1048
920
  runtime?.setSubject(restoreSessionSubject(branch));
1049
921
  runtime?.settle();
1050
922
  });
@@ -1131,7 +1003,6 @@ export default function galacticaStatus(pi: ExtensionAPI): void {
1131
1003
  runtime = undefined;
1132
1004
  publisher.removeAll();
1133
1005
  stopReady();
1134
- stopFocusRepaint();
1135
1006
  });
1136
1007
 
1137
1008
  pi.registerCommand('galactica-status', {
@@ -1,19 +1,7 @@
1
1
  import { basename } from 'node:path';
2
2
 
3
- export const OPEN_SPEC_FOCUS_ENTRY_TYPE = 'galactica-status.openspec-focus.v1';
4
3
  export const SESSION_SUBJECT_ENTRY_TYPE = 'galactica-status.session-subject.v1';
5
4
 
6
- export type TaskOpenSpecFocus = {
7
- mode: 'task';
8
- change: string;
9
- taskId: string;
10
- awaitingValidation: boolean;
11
- };
12
-
13
- export type OpenSpecFocus = { mode: 'none' } | TaskOpenSpecFocus;
14
-
15
- export const NO_OPEN_SPEC_FOCUS: OpenSpecFocus = { mode: 'none' };
16
-
17
5
  export type SessionSubject = { state: 'clear' } | { state: 'set'; title: string };
18
6
 
19
7
  export const NO_SESSION_SUBJECT: SessionSubject = { state: 'clear' };
@@ -42,29 +30,6 @@ function explicitId(value: unknown): string | undefined {
42
30
  return normalized || undefined;
43
31
  }
44
32
 
45
- export function taskFocus(
46
- change: unknown,
47
- taskId: unknown,
48
- awaitingValidation?: unknown,
49
- ): TaskOpenSpecFocus | undefined {
50
- const normalizedChange = explicitId(change);
51
- const normalizedTaskId = explicitId(taskId);
52
- if (!normalizedChange || !normalizedTaskId) return undefined;
53
- return {
54
- mode: 'task',
55
- change: normalizedChange,
56
- taskId: normalizedTaskId,
57
- awaitingValidation: awaitingValidation === true,
58
- };
59
- }
60
-
61
- export function parseOpenSpecFocus(value: unknown): OpenSpecFocus | undefined {
62
- if (!isRecord(value)) return undefined;
63
- if (value.mode === 'none') return NO_OPEN_SPEC_FOCUS;
64
- if (value.mode !== 'task') return undefined;
65
- return taskFocus(value.change, value.taskId, value.awaitingValidation);
66
- }
67
-
68
33
  export function sessionSubject(
69
34
  state: unknown,
70
35
  title?: unknown,
@@ -84,22 +49,6 @@ export function parseSessionSubject(value: unknown): SessionSubject | undefined
84
49
  return sessionSubject(value.state, value.title);
85
50
  }
86
51
 
87
- export function restoreOpenSpecFocus(entries: readonly unknown[]): OpenSpecFocus {
88
- for (let index = entries.length - 1; index >= 0; index -= 1) {
89
- const entry = entries[index];
90
- if (
91
- !isRecord(entry) ||
92
- entry.type !== 'custom' ||
93
- entry.customType !== OPEN_SPEC_FOCUS_ENTRY_TYPE
94
- ) {
95
- continue;
96
- }
97
- const focus = parseOpenSpecFocus(entry.data);
98
- if (focus) return focus;
99
- }
100
- return NO_OPEN_SPEC_FOCUS;
101
- }
102
-
103
52
  export function restoreSessionSubject(entries: readonly unknown[]): SessionSubject {
104
53
  for (let index = entries.length - 1; index >= 0; index -= 1) {
105
54
  const entry = entries[index];
@@ -116,39 +65,6 @@ export function restoreSessionSubject(entries: readonly unknown[]): SessionSubje
116
65
  return NO_SESSION_SUBJECT;
117
66
  }
118
67
 
119
- export function taskflowRunFocus(input: unknown): TaskOpenSpecFocus | undefined {
120
- if (!isRecord(input) || input.action !== 'run' || !isRecord(input.args)) {
121
- return undefined;
122
- }
123
- const focus = taskFocus(input.args.change, input.args.task);
124
- return focus && focus.change !== 'none' && focus.taskId !== 'none'
125
- ? focus
126
- : undefined;
127
- }
128
-
129
- /** Task identity (mode · change · taskId) — the replace-safeguard's
130
- * question; the validation flag is state, not identity. */
131
- export function sameTaskFocus(left: OpenSpecFocus, right: OpenSpecFocus): boolean {
132
- if (left.mode !== right.mode) return false;
133
- return (
134
- left.mode === 'none' ||
135
- (right.mode === 'task' &&
136
- left.change === right.change &&
137
- left.taskId === right.taskId)
138
- );
139
- }
140
-
141
- export function sameOpenSpecFocus(left: OpenSpecFocus, right: OpenSpecFocus): boolean {
142
- if (left.mode !== right.mode) return false;
143
- return (
144
- left.mode === 'none' ||
145
- (right.mode === 'task' &&
146
- left.change === right.change &&
147
- left.taskId === right.taskId &&
148
- left.awaitingValidation === right.awaitingValidation)
149
- );
150
- }
151
-
152
68
  export function sameSessionSubject(
153
69
  left: SessionSubject,
154
70
  right: SessionSubject,
@@ -13,7 +13,6 @@ export const DEFAULT_CONFIG: GalacticaStatusConfig = {
13
13
  enabled: true,
14
14
  openspec: {
15
15
  enabled: true,
16
- change: null,
17
16
  },
18
17
  diagnostics: {
19
18
  enabled: true,
@@ -126,7 +125,7 @@ export function parseConfig(value: unknown): {
126
125
  const diagnostics = isRecord(value.diagnostics) ? value.diagnostics : {};
127
126
  const orchestration = isRecord(value.orchestration) ? value.orchestration : {};
128
127
 
129
- unknownKeys(openspec, ['enabled', 'change'], 'openspec', warnings);
128
+ unknownKeys(openspec, ['enabled'], 'openspec', warnings);
130
129
  unknownKeys(
131
130
  diagnostics,
132
131
  [
@@ -148,7 +147,6 @@ export function parseConfig(value: unknown): {
148
147
  enabled: booleanValue(value.enabled, DEFAULT_CONFIG.enabled),
149
148
  openspec: {
150
149
  enabled: booleanValue(openspec.enabled, DEFAULT_CONFIG.openspec.enabled),
151
- change: nullableString(openspec.change, DEFAULT_CONFIG.openspec.change),
152
150
  },
153
151
  diagnostics: {
154
152
  enabled: booleanValue(diagnostics.enabled, DEFAULT_CONFIG.diagnostics.enabled),
@@ -5,7 +5,7 @@ import type {
5
5
  RunSpan,
6
6
  StageValue,
7
7
  } from './types.ts';
8
- import type { OpenSpecFocus, SessionSubject } from './active-work.ts';
8
+ import type { SessionSubject } from './active-work.ts';
9
9
 
10
10
  /**
11
11
  * Append-only event ledger for the cockpit status core.
@@ -17,7 +17,6 @@ import type { OpenSpecFocus, SessionSubject } from './active-work.ts';
17
17
  */
18
18
 
19
19
  export const LEDGER_EVENT_TYPES = [
20
- 'focus/openspec',
21
20
  'focus/subject',
22
21
  'run/start',
23
22
  'run/stage',
@@ -31,11 +30,6 @@ export const LEDGER_EVENT_TYPES = [
31
30
  export type LedgerEventType = (typeof LEDGER_EVENT_TYPES)[number];
32
31
 
33
32
  export type LedgerEvent =
34
- | {
35
- type: 'focus/openspec';
36
- at: number;
37
- focus: OpenSpecFocus;
38
- }
39
33
  | { type: 'focus/subject'; at: number; subject: SessionSubject }
40
34
  | { type: 'run/start'; at: number; span: RunSpan }
41
35
  | { type: 'run/stage'; at: number; id: string; stage: StageValue; declared: boolean }
@@ -270,7 +270,7 @@ export function parseApplyInstructions(value: unknown): ApplyInstructionsResult
270
270
  complete: finiteNumber(value.progress.complete),
271
271
  }
272
272
  : undefined;
273
- // A blocked apply (mid-ladder, artifacts missing) reports zero progress;
273
+ // A blocked apply (part-way through, artifacts missing) reports zero progress;
274
274
  // those zeros must never shadow the honest `openspec list` counts.
275
275
  const progress =
276
276
  state !== 'blocked' && (reportedProgress?.total ?? 0) > 0
@@ -550,7 +550,7 @@ export async function collectOpenSpec(options: {
550
550
  // tile at 0/N (simplify-deck-defaults). CLI progress and list counts
551
551
  // remain fallbacks; the blocked-apply zero-guard stays for the
552
552
  // no-tasks case.
553
- // File-truth ladder: checkbox tasks first, then done/total artifacts
553
+ // File-truth precedence: checkbox tasks first, then done/total artifacts
554
554
  // (file presence via `openspec status`) — CLI task opinion cannot count
555
555
  // custom schemas and pins 0/N (truthful-rendered-state). With neither
556
556
  // readable, counts stay absent — absence never becomes zero.