peaks-loop 4.0.29 → 4.0.30

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cli/commands/_register.js +4 -0
  3. package/dist/cli/commands/dispatch-commands.d.ts +5 -41
  4. package/dist/cli/commands/dispatch-commands.js +35 -185
  5. package/dist/cli/commands/evidence-commands.d.ts +12 -0
  6. package/dist/cli/commands/evidence-commands.js +43 -0
  7. package/dist/cli/commands/fresh-context-commands.d.ts +15 -0
  8. package/dist/cli/commands/fresh-context-commands.js +24 -0
  9. package/dist/cli/commands/request-commands.js +2 -96
  10. package/dist/cli/commands/request-format-helpers.d.ts +19 -0
  11. package/dist/cli/commands/request-format-helpers.js +102 -0
  12. package/dist/cli/commands/worktree-auth-commands.js +2 -472
  13. package/dist/cli/commands/worktree-lease-commands.d.ts +21 -0
  14. package/dist/cli/commands/worktree-lease-commands.js +500 -0
  15. package/dist/services/artifacts/request-artifact-service.js +13 -3
  16. package/dist/services/code/auto-compact-lifecycle.d.ts +102 -0
  17. package/dist/services/code/auto-compact-lifecycle.js +235 -0
  18. package/dist/services/code/auto-compact-orchestrator.d.ts +1 -1
  19. package/dist/services/code/auto-compact-orchestrator.js +1 -222
  20. package/dist/services/context/build-dispatch-system-prompt.d.ts +26 -0
  21. package/dist/services/context/build-dispatch-system-prompt.js +38 -3
  22. package/dist/services/dispatch/dispatch-record-types.d.ts +278 -0
  23. package/dist/services/dispatch/dispatch-record-types.js +15 -0
  24. package/dist/services/dispatch/dispatch-record-upgrade.d.ts +5 -0
  25. package/dist/services/dispatch/dispatch-record-upgrade.js +230 -0
  26. package/dist/services/dispatch/dispatch-record-writer.d.ts +3 -278
  27. package/dist/services/dispatch/dispatch-record-writer.js +3 -245
  28. package/dist/services/dispatch/dispatch-sub-agent.d.ts +43 -0
  29. package/dist/services/dispatch/dispatch-sub-agent.js +55 -0
  30. package/dist/services/dispatch/isolation-lease.d.ts +43 -0
  31. package/dist/services/dispatch/isolation-lease.js +129 -0
  32. package/dist/services/evidence/evidence-generator.d.ts +26 -0
  33. package/dist/services/evidence/evidence-generator.js +349 -0
  34. package/dist/services/fresh-context/config.d.ts +1 -0
  35. package/dist/services/fresh-context/config.js +13 -0
  36. package/dist/services/fresh-context/fresh-context-block.d.ts +12 -0
  37. package/dist/services/fresh-context/fresh-context-block.js +40 -0
  38. package/dist/services/fresh-context/trigger-scan.d.ts +30 -0
  39. package/dist/services/fresh-context/trigger-scan.js +29 -0
  40. package/dist/services/skills/hooks-codegate-superpowers.d.ts +65 -0
  41. package/dist/services/skills/hooks-codegate-superpowers.js +204 -0
  42. package/dist/services/skills/hooks-settings-service.d.ts +2 -64
  43. package/dist/services/skills/hooks-settings-service.js +2 -215
  44. package/dist/services/skills/skill-statusline-renderer.d.ts +2 -34
  45. package/dist/services/skills/skill-statusline-renderer.js +1 -184
  46. package/dist/services/skills/statusline-palette.d.ts +62 -0
  47. package/dist/services/skills/statusline-palette.js +190 -0
  48. package/dist/services/slice/slice-decompose-import-edges.d.ts +8 -0
  49. package/dist/services/slice/slice-decompose-import-edges.js +102 -0
  50. package/dist/services/slice/slice-decompose-service.js +3 -198
  51. package/dist/services/slice/slice-decompose-tarjan.d.ts +8 -0
  52. package/dist/services/slice/slice-decompose-tarjan.js +108 -0
  53. package/dist/services/standards/project-standards-service.d.ts +1 -9
  54. package/dist/services/standards/project-standards-service.js +3 -232
  55. package/dist/services/standards/standards-render.d.ts +23 -0
  56. package/dist/services/standards/standards-render.js +238 -0
  57. package/dist/services/standards/ui-library-dispatch-block.d.ts +27 -0
  58. package/dist/services/standards/ui-library-dispatch-block.js +48 -0
  59. package/dist/services/workspace/reconcile-migrate.d.ts +77 -0
  60. package/dist/services/workspace/reconcile-migrate.js +230 -0
  61. package/dist/services/workspace/reconcile-service.d.ts +0 -72
  62. package/dist/services/workspace/reconcile-service.js +3 -220
  63. package/dist/services/workspace/workspace-service.js +13 -2
  64. package/dist/shared/incrementing-number.d.ts +11 -0
  65. package/dist/shared/incrementing-number.js +16 -3
  66. package/package.json +5 -5
  67. package/skills/peaks-code/SKILL.md +6 -2
  68. package/skills/peaks-code/references/fresh-context-preflight.md +81 -0
  69. package/skills/peaks-code/references/sub-agent-dispatch.md +27 -8
@@ -7,190 +7,7 @@ import { computeRootSuffix as computeRootSuffixImpl, } from './skill-statusline-
7
7
  // repair cycle.
8
8
  export { formatShortSid } from './skill-statusline-sid-suffix.js';
9
9
  export const computeRootSuffix = computeRootSuffixImpl;
10
- /**
11
- * Internal: the brand prefix is plain ASCII so a tiny terminal without
12
- * monospaced UTF-8 still sees a recognizable string. The mountain glyphs
13
- * (`⛰` / `🏔`) used in earlier 1.x renders were the loudest a11y regression
14
- * in the 2026-07-22 ice-cola surface check and are deliberately removed.
15
- */
16
- const BRAND = 'Peaks';
17
- /**
18
- * Attention-gate allowlist. The brief requires conservative classification:
19
- * only gate names that encode a blocking condition surface as a warning
20
- * glyph. Routine progress gates (`startup`, `swarm-fan-out`,
21
- * `swarm-converged`, `repair-cycle-N`, etc.) stay hidden. The map is
22
- * explicit — there is no "non-empty gate means alert" inference.
23
- */
24
- const ATTENTION_GATE_LABELS = new Map([
25
- ['qa-validation', 'QA'],
26
- ['verdict-issued', 'Verdict'],
27
- ['blocked', 'Blocked'],
28
- ]);
29
- function isAttentionGate(gate) {
30
- if (!gate)
31
- return null;
32
- return ATTENTION_GATE_LABELS.get(gate) ?? null;
33
- }
34
- /** Brand accent: per project request, this is the slate-purple `#5A65D8`. */
35
- const BRAND_RGB = '38;2;90;101;216';
36
- const BRAND_SGR_OPEN = `\x1b[1;${BRAND_RGB}m`;
37
- const BRAND_SGR_FULL_OPEN = `\x1b[${BRAND_RGB}m`;
38
- const BRAND_SGR_CLOSE = '\x1b[0m';
39
- /**
40
- * Highlight color used by the marquee scan band. `#E0E0E0` bright-grey
41
- * + bold = "bleached" foreground that reads as a scanner sweeping
42
- * over the colored text. Kept distinct from brand white (`#FFFFFF`)
43
- * because pure white on `#5A65D8` brand purple reads as a strobe;
44
- * the off-white tint gives the same "scanner" affordance without the
45
- * flicker.
46
- */
47
- const HIGHLIGHT_RGB = '38;2;224;224;224';
48
- const HIGHLIGHT_SGR_OPEN = `\x1b[1;${HIGHLIGHT_RGB}m`;
49
- function accent(text) {
50
- return `${BRAND_SGR_OPEN}${text}${BRAND_SGR_CLOSE}`;
51
- }
52
- function accentGlyph(glyph) {
53
- return `${BRAND_SGR_OPEN}${glyph}${BRAND_SGR_CLOSE}`;
54
- }
55
- // (kept for the brandText/active-dot helpers that inline the same SGR; the
56
- // buildPalette factory owns per-token SGR injection now and does not call
57
- // through these wrappers.)
58
- /**
59
- * Render the supplied text in the brand purple, honouring the
60
- * `noColor` flag. Returns plain text when NO_COLOR is set or the
61
- * tier is ASCII. Used by the active / stale / compact renders for
62
- * tokens that the {@link buildPalette} cannot pre-stamp (skill
63
- * name, mode, age, attention labels).
64
- */
65
- function brandRun(text, noColor, capability) {
66
- if (text.length === 0)
67
- return text;
68
- if (noColor || capability === 'ascii')
69
- return text;
70
- return `${BRAND_SGR_OPEN}${text}${BRAND_SGR_CLOSE}`;
71
- }
72
- /**
73
- * Slow-blink variant of the accent glyph. Used for the idle indicator
74
- * so the user perceives a pulse on the statusline. The ANSI slow-blink
75
- * SGR (`\x1b[5m`) is honored by most modern terminals (iTerm2, Windows
76
- * Terminal, GNOME Terminal with the right profile). Hosts that ignore
77
- * the SGR render the glyph statically — graceful degradation.
78
- */
79
- function blinkingAccentGlyph(glyph) {
80
- return `\x1b[5;1;${BRAND_RGB}m${glyph}\x1b[0m`;
81
- }
82
- /**
83
- * Build the per-capability palette. `noColor=true` strips every brand
84
- * ANSI SGR (warning / failed remain because they are SEMANTIC alarms,
85
- * not brand colour). The unicode glyphs are kept as-is — NO_COLOR
86
- * (https://no-color.org) addresses ANSI sequences, not UTF-8 chars.
87
- */
88
- function buildPalette(capability, noColor) {
89
- // Brand SGR helpers, noColor-aware.
90
- const brand = (text) => noColor || capability === 'ascii' ? text : `${BRAND_SGR_OPEN}${text}${BRAND_SGR_CLOSE}`;
91
- const brandGlyph = (glyph) => noColor || capability === 'ascii' ? glyph : `${BRAND_SGR_OPEN}${glyph}${BRAND_SGR_CLOSE}`;
92
- const dimBrand = (text) => {
93
- if (noColor || capability === 'ascii')
94
- return text;
95
- return `\x1b[2;${BRAND_RGB}m${text}\x1b[0m`;
96
- };
97
- const blinkBrand = (glyph) => {
98
- if (noColor || capability === 'ascii')
99
- return glyph;
100
- return `\x1b[5;1;${BRAND_RGB}m${glyph}\x1b[0m`;
101
- };
102
- // Warning + failed stay semantic — they are alarms, not brand colour.
103
- // noColor still suppresses them because a tiny log consumer that
104
- // sets NO_COLOR expects raw text.
105
- const warning = noColor || capability === 'ascii' ? '!' : '\x1b[33m!\x1b[0m';
106
- const failed = noColor || capability === 'ascii' ? 'x' : '\x1b[31m✕\x1b[0m';
107
- if (capability === 'ascii') {
108
- return {
109
- active: '*',
110
- idle: 'o',
111
- // Slice rid-statusline-stale-ux AC-1: stale residue uses the same
112
- // glyph as `idle` (`o`) but stays static (no slow-blink — the user
113
- // is reading a *previous* session's residue, not a live idle state).
114
- // The neutral copy `(previous session · N days ago)` is the
115
- // decisive signal; the glyph is auxiliary.
116
- idleStale: 'o',
117
- warning,
118
- inlineSeparator: ' . ',
119
- trailSeparator: ' -> ',
120
- idleLabel: 'empty',
121
- invalidMessage: 'presence unreadable',
122
- compact: {
123
- queued: '[', preparing: '+', compacting: '+', verifying: '+',
124
- completed: '*', failed,
125
- },
126
- barFilled: '#',
127
- barEmpty: '-',
128
- ratioArrow: '->',
129
- };
130
- }
131
- return {
132
- active: brandGlyph('●'),
133
- idle: blinkBrand('○'),
134
- // Slice rid-statusline-stale-ux AC-1: muted slate (`#AAAAC8` dim)
135
- // — slow-blink OFF, distinct from `idle` (slow-blink brand) and
136
- // `warning` (yellow invalid-presence). The stale branch is
137
- // semantically "previous-session residue" (neutral), NOT an error.
138
- idleStale: dimBrand('○'),
139
- warning,
140
- inlineSeparator: brand(' · '),
141
- trailSeparator: brand(' → '),
142
- idleLabel: brand('empty'),
143
- invalidMessage: brand('presence unreadable'),
144
- compact: {
145
- queued: brandGlyph('◐'),
146
- preparing: brandGlyph('◑'),
147
- compacting: brandGlyph('◒'),
148
- verifying: brandGlyph('◓'),
149
- completed: brandGlyph('✓'),
150
- failed,
151
- },
152
- barFilled: brand('█'),
153
- barEmpty: dimBrand('░'),
154
- ratioArrow: dimBrand('→'),
155
- };
156
- }
157
- const BREATHING_GLYPHS_UNICODE = ['●', '◐', '◑', '◒', '◓'];
158
- const BREATHING_GLYPHS_ASCII = ['*', 'o', '+', '~', '|'];
159
- const BREATHING_PERIOD_MS = 600;
160
- function pickBreathingGlyph(capability, nowMs) {
161
- const set = capability === 'ascii' ? BREATHING_GLYPHS_ASCII : BREATHING_GLYPHS_UNICODE;
162
- const index = Math.floor((nowMs % BREATHING_PERIOD_MS) / (BREATHING_PERIOD_MS / set.length)) % set.length;
163
- return set[index];
164
- }
165
- function renderActiveDot(capability, nowMs, noColor) {
166
- // Brief: the active dot carries the project accent (`#5A65D8` bold)
167
- // in both colored tiers. The breathing glyph is wrapped in a fresh
168
- // SGR on every render so the IDE sees a single accent per refresh.
169
- // NO_COLOR strips the SGR but keeps the glyph (still rotates).
170
- const glyph = pickBreathingGlyph(capability, nowMs);
171
- if (noColor || capability === 'ascii')
172
- return glyph;
173
- return `${BRAND_SGR_OPEN}${glyph}${BRAND_SGR_CLOSE}`;
174
- }
175
- function brandText(capability, noColor) {
176
- // Brief: brand carries the project accent (`#5A65D8` bold) in both
177
- // colored tiers. The `ascii` tier and NO_COLOR stay plain text so
178
- // log / file consumers never see escape codes.
179
- if (noColor || capability === 'ascii')
180
- return BRAND;
181
- return `${BRAND_SGR_OPEN}${BRAND}${BRAND_SGR_CLOSE}`;
182
- }
183
- /**
184
- * Default capability when none is supplied. Per the brief: "if backward
185
- * compatibility requires a renderer default, use unicode, never
186
- * unconditional ANSI." The CLI's `peaks statusline` invocation currently
187
- * passes no options; preserving the legacy no-ANSI behaviour means the
188
- * default is the unicode palette (no escape codes).
189
- */
190
- const DEFAULT_CAPABILITY = 'unicode';
191
- function paletteFor(capability, noColor) {
192
- return buildPalette(capability ?? DEFAULT_CAPABILITY, noColor);
193
- }
10
+ import { DEFAULT_CAPABILITY, HIGHLIGHT_SGR_OPEN, brandRun, brandText, isAttentionGate, paletteFor, renderActiveDot, } from './statusline-palette.js';
194
11
  function formatAge(ageMs) {
195
12
  // Slice rid-statusline-stale-ux AC-1: this legacy `stale <N>h/m`
196
13
  // token is NO LONGER used by renderStale. Kept exported / defined
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Visual / palette / SGR rendering block extracted from
3
+ * `skill-statusline-renderer.ts` (slice 2026-09-06-split-batch-b) so the
4
+ * renderer stays under the 800-line cap. Behaviour-preserving verbatim move;
5
+ * `export` keywords were added to the symbols the renderer still imports.
6
+ */
7
+ export type StatusLineCapability = 'ansi-unicode' | 'unicode' | 'ascii';
8
+ export declare function isAttentionGate(gate: string | undefined): string | null;
9
+ /**
10
+ * Glyph palette per capability. Status glyphs (active / idle / warning),
11
+ * separators (· / › / ▸ / ASCII variants), and the diagnostic marker
12
+ * (`presence unreadable`). Unicode glyphs use widely-supported characters;
13
+ * ASCII keeps byte-identical shape across encodings.
14
+ *
15
+ * Compact glyphs are the unicode quadrant marks ◐◑◒◓, the check ✓, and the
16
+ * ballot ✕. ASCII uses bracket/plus/star/x. Bar characters are █/░ in
17
+ * unicode and #/- in ASCII. Ratios are joined with `·` (` . ` for ASCII)
18
+ * and the before→after arrow is `→` (-> for ASCII).
19
+ */
20
+ export interface StatusPalette {
21
+ readonly active: string;
22
+ readonly idleStale: string;
23
+ readonly idle: string;
24
+ readonly warning: string;
25
+ readonly inlineSeparator: string;
26
+ readonly trailSeparator: string;
27
+ readonly idleLabel: string;
28
+ readonly invalidMessage: string;
29
+ readonly compact: CompactPalette;
30
+ readonly barFilled: string;
31
+ readonly barEmpty: string;
32
+ readonly ratioArrow: string;
33
+ }
34
+ interface CompactPalette {
35
+ readonly queued: string;
36
+ readonly preparing: string;
37
+ readonly compacting: string;
38
+ readonly verifying: string;
39
+ readonly completed: string;
40
+ readonly failed: string;
41
+ }
42
+ export declare const HIGHLIGHT_SGR_OPEN = "\u001B[1;38;2;224;224;224m";
43
+ /**
44
+ * Render the supplied text in the brand purple, honouring the
45
+ * `noColor` flag. Returns plain text when NO_COLOR is set or the
46
+ * tier is ASCII. Used by the active / stale / compact renders for
47
+ * tokens that the {@link buildPalette} cannot pre-stamp (skill
48
+ * name, mode, age, attention labels).
49
+ */
50
+ export declare function brandRun(text: string, noColor: boolean, capability: StatusLineCapability): string;
51
+ export declare function renderActiveDot(capability: StatusLineCapability, nowMs: number, noColor: boolean): string;
52
+ export declare function brandText(capability: StatusLineCapability, noColor: boolean): string;
53
+ /**
54
+ * Default capability when none is supplied. Per the brief: "if backward
55
+ * compatibility requires a renderer default, use unicode, never
56
+ * unconditional ANSI." The CLI's `peaks statusline` invocation currently
57
+ * passes no options; preserving the legacy no-ANSI behaviour means the
58
+ * default is the unicode palette (no escape codes).
59
+ */
60
+ export declare const DEFAULT_CAPABILITY: StatusLineCapability;
61
+ export declare function paletteFor(capability: StatusLineCapability | undefined, noColor: boolean): StatusPalette;
62
+ export {};
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Visual / palette / SGR rendering block extracted from
3
+ * `skill-statusline-renderer.ts` (slice 2026-09-06-split-batch-b) so the
4
+ * renderer stays under the 800-line cap. Behaviour-preserving verbatim move;
5
+ * `export` keywords were added to the symbols the renderer still imports.
6
+ */
7
+ /**
8
+ * Internal: the brand prefix is plain ASCII so a tiny terminal without
9
+ * monospaced UTF-8 still sees a recognizable string. The mountain glyphs
10
+ * (`⛰` / `🏔`) used in earlier 1.x renders were the loudest a11y regression
11
+ * in the 2026-07-22 ice-cola surface check and are deliberately removed.
12
+ */
13
+ const BRAND = 'Peaks';
14
+ /**
15
+ * Attention-gate allowlist. The brief requires conservative classification:
16
+ * only gate names that encode a blocking condition surface as a warning
17
+ * glyph. Routine progress gates (`startup`, `swarm-fan-out`,
18
+ * `swarm-converged`, `repair-cycle-N`, etc.) stay hidden. The map is
19
+ * explicit — there is no "non-empty gate means alert" inference.
20
+ */
21
+ const ATTENTION_GATE_LABELS = new Map([
22
+ ['qa-validation', 'QA'],
23
+ ['verdict-issued', 'Verdict'],
24
+ ['blocked', 'Blocked'],
25
+ ]);
26
+ export function isAttentionGate(gate) {
27
+ if (!gate)
28
+ return null;
29
+ return ATTENTION_GATE_LABELS.get(gate) ?? null;
30
+ }
31
+ /** Brand accent: per project request, this is the slate-purple `#5A65D8`. */
32
+ const BRAND_RGB = '38;2;90;101;216';
33
+ const BRAND_SGR_OPEN = `\x1b[1;${BRAND_RGB}m`;
34
+ const BRAND_SGR_FULL_OPEN = `\x1b[${BRAND_RGB}m`;
35
+ const BRAND_SGR_CLOSE = '\x1b[0m';
36
+ /**
37
+ * Highlight color used by the marquee scan band. `#E0E0E0` bright-grey
38
+ * + bold = "bleached" foreground that reads as a scanner sweeping
39
+ * over the colored text. Kept distinct from brand white (`#FFFFFF`)
40
+ * because pure white on `#5A65D8` brand purple reads as a strobe;
41
+ * the off-white tint gives the same "scanner" affordance without the
42
+ * flicker.
43
+ */
44
+ const HIGHLIGHT_RGB = '38;2;224;224;224';
45
+ export const HIGHLIGHT_SGR_OPEN = `\x1b[1;${HIGHLIGHT_RGB}m`;
46
+ function accent(text) {
47
+ return `${BRAND_SGR_OPEN}${text}${BRAND_SGR_CLOSE}`;
48
+ }
49
+ function accentGlyph(glyph) {
50
+ return `${BRAND_SGR_OPEN}${glyph}${BRAND_SGR_CLOSE}`;
51
+ }
52
+ // (kept for the brandText/active-dot helpers that inline the same SGR; the
53
+ // buildPalette factory owns per-token SGR injection now and does not call
54
+ // through these wrappers.)
55
+ /**
56
+ * Render the supplied text in the brand purple, honouring the
57
+ * `noColor` flag. Returns plain text when NO_COLOR is set or the
58
+ * tier is ASCII. Used by the active / stale / compact renders for
59
+ * tokens that the {@link buildPalette} cannot pre-stamp (skill
60
+ * name, mode, age, attention labels).
61
+ */
62
+ export function brandRun(text, noColor, capability) {
63
+ if (text.length === 0)
64
+ return text;
65
+ if (noColor || capability === 'ascii')
66
+ return text;
67
+ return `${BRAND_SGR_OPEN}${text}${BRAND_SGR_CLOSE}`;
68
+ }
69
+ /**
70
+ * Slow-blink variant of the accent glyph. Used for the idle indicator
71
+ * so the user perceives a pulse on the statusline. The ANSI slow-blink
72
+ * SGR (`\x1b[5m`) is honored by most modern terminals (iTerm2, Windows
73
+ * Terminal, GNOME Terminal with the right profile). Hosts that ignore
74
+ * the SGR render the glyph statically — graceful degradation.
75
+ */
76
+ function blinkingAccentGlyph(glyph) {
77
+ return `\x1b[5;1;${BRAND_RGB}m${glyph}\x1b[0m`;
78
+ }
79
+ /**
80
+ * Build the per-capability palette. `noColor=true` strips every brand
81
+ * ANSI SGR (warning / failed remain because they are SEMANTIC alarms,
82
+ * not brand colour). The unicode glyphs are kept as-is — NO_COLOR
83
+ * (https://no-color.org) addresses ANSI sequences, not UTF-8 chars.
84
+ */
85
+ function buildPalette(capability, noColor) {
86
+ // Brand SGR helpers, noColor-aware.
87
+ const brand = (text) => noColor || capability === 'ascii' ? text : `${BRAND_SGR_OPEN}${text}${BRAND_SGR_CLOSE}`;
88
+ const brandGlyph = (glyph) => noColor || capability === 'ascii' ? glyph : `${BRAND_SGR_OPEN}${glyph}${BRAND_SGR_CLOSE}`;
89
+ const dimBrand = (text) => {
90
+ if (noColor || capability === 'ascii')
91
+ return text;
92
+ return `\x1b[2;${BRAND_RGB}m${text}\x1b[0m`;
93
+ };
94
+ const blinkBrand = (glyph) => {
95
+ if (noColor || capability === 'ascii')
96
+ return glyph;
97
+ return `\x1b[5;1;${BRAND_RGB}m${glyph}\x1b[0m`;
98
+ };
99
+ // Warning + failed stay semantic — they are alarms, not brand colour.
100
+ // noColor still suppresses them because a tiny log consumer that
101
+ // sets NO_COLOR expects raw text.
102
+ const warning = noColor || capability === 'ascii' ? '!' : '\x1b[33m!\x1b[0m';
103
+ const failed = noColor || capability === 'ascii' ? 'x' : '\x1b[31m✕\x1b[0m';
104
+ if (capability === 'ascii') {
105
+ return {
106
+ active: '*',
107
+ idle: 'o',
108
+ // Slice rid-statusline-stale-ux AC-1: stale residue uses the same
109
+ // glyph as `idle` (`o`) but stays static (no slow-blink — the user
110
+ // is reading a *previous* session's residue, not a live idle state).
111
+ // The neutral copy `(previous session · N days ago)` is the
112
+ // decisive signal; the glyph is auxiliary.
113
+ idleStale: 'o',
114
+ warning,
115
+ inlineSeparator: ' . ',
116
+ trailSeparator: ' -> ',
117
+ idleLabel: 'empty',
118
+ invalidMessage: 'presence unreadable',
119
+ compact: {
120
+ queued: '[', preparing: '+', compacting: '+', verifying: '+',
121
+ completed: '*', failed,
122
+ },
123
+ barFilled: '#',
124
+ barEmpty: '-',
125
+ ratioArrow: '->',
126
+ };
127
+ }
128
+ return {
129
+ active: brandGlyph('●'),
130
+ idle: blinkBrand('○'),
131
+ // Slice rid-statusline-stale-ux AC-1: muted slate (`#AAAAC8` dim)
132
+ // — slow-blink OFF, distinct from `idle` (slow-blink brand) and
133
+ // `warning` (yellow invalid-presence). The stale branch is
134
+ // semantically "previous-session residue" (neutral), NOT an error.
135
+ idleStale: dimBrand('○'),
136
+ warning,
137
+ inlineSeparator: brand(' · '),
138
+ trailSeparator: brand(' → '),
139
+ idleLabel: brand('empty'),
140
+ invalidMessage: brand('presence unreadable'),
141
+ compact: {
142
+ queued: brandGlyph('◐'),
143
+ preparing: brandGlyph('◑'),
144
+ compacting: brandGlyph('◒'),
145
+ verifying: brandGlyph('◓'),
146
+ completed: brandGlyph('✓'),
147
+ failed,
148
+ },
149
+ barFilled: brand('█'),
150
+ barEmpty: dimBrand('░'),
151
+ ratioArrow: dimBrand('→'),
152
+ };
153
+ }
154
+ const BREATHING_GLYPHS_UNICODE = ['●', '◐', '◑', '◒', '◓'];
155
+ const BREATHING_GLYPHS_ASCII = ['*', 'o', '+', '~', '|'];
156
+ const BREATHING_PERIOD_MS = 600;
157
+ function pickBreathingGlyph(capability, nowMs) {
158
+ const set = capability === 'ascii' ? BREATHING_GLYPHS_ASCII : BREATHING_GLYPHS_UNICODE;
159
+ const index = Math.floor((nowMs % BREATHING_PERIOD_MS) / (BREATHING_PERIOD_MS / set.length)) % set.length;
160
+ return set[index];
161
+ }
162
+ export function renderActiveDot(capability, nowMs, noColor) {
163
+ // Brief: the active dot carries the project accent (`#5A65D8` bold)
164
+ // in both colored tiers. The breathing glyph is wrapped in a fresh
165
+ // SGR on every render so the IDE sees a single accent per refresh.
166
+ // NO_COLOR strips the SGR but keeps the glyph (still rotates).
167
+ const glyph = pickBreathingGlyph(capability, nowMs);
168
+ if (noColor || capability === 'ascii')
169
+ return glyph;
170
+ return `${BRAND_SGR_OPEN}${glyph}${BRAND_SGR_CLOSE}`;
171
+ }
172
+ export function brandText(capability, noColor) {
173
+ // Brief: brand carries the project accent (`#5A65D8` bold) in both
174
+ // colored tiers. The `ascii` tier and NO_COLOR stay plain text so
175
+ // log / file consumers never see escape codes.
176
+ if (noColor || capability === 'ascii')
177
+ return BRAND;
178
+ return `${BRAND_SGR_OPEN}${BRAND}${BRAND_SGR_CLOSE}`;
179
+ }
180
+ /**
181
+ * Default capability when none is supplied. Per the brief: "if backward
182
+ * compatibility requires a renderer default, use unicode, never
183
+ * unconditional ANSI." The CLI's `peaks statusline` invocation currently
184
+ * passes no options; preserving the legacy no-ANSI behaviour means the
185
+ * default is the unicode palette (no escape codes).
186
+ */
187
+ export const DEFAULT_CAPABILITY = 'unicode';
188
+ export function paletteFor(capability, noColor) {
189
+ return buildPalette(capability ?? DEFAULT_CAPABILITY, noColor);
190
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Import-edge collection helpers for the slice-decompose pipeline.
3
+ * Extracted from `slice-decompose-service.ts` to keep that file under
4
+ * the 800 LOC cap (mechanical verbatim move). `buildDependencyEdges` is
5
+ * the only exported entry point; the rest are internal helpers.
6
+ */
7
+ import type { DependencyEdge, ImportEdge, KnowledgeGraph, WorkUnit } from './slice-decompose-types.js';
8
+ export declare function buildDependencyEdges(wus: readonly WorkUnit[], importEdges: readonly ImportEdge[], kg: KnowledgeGraph | null, projectRoot: string): DependencyEdge[];
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Import-edge collection helpers for the slice-decompose pipeline.
3
+ * Extracted from `slice-decompose-service.ts` to keep that file under
4
+ * the 800 LOC cap (mechanical verbatim move). `buildDependencyEdges` is
5
+ * the only exported entry point; the rest are internal helpers.
6
+ */
7
+ import { dirname, join, relative } from 'node:path';
8
+ function indexFilesToWorkUnits(wus) {
9
+ const fileToWu = new Map();
10
+ for (const wu of wus) {
11
+ for (const f of wu.files) {
12
+ fileToWu.set(f, wu.id);
13
+ }
14
+ }
15
+ return fileToWu;
16
+ }
17
+ function classifyImportEdge(imp, fileToWu, projectRoot) {
18
+ let fromWu = fileToWu.get(imp.from);
19
+ let toWu = fileToWu.get(imp.to);
20
+ if (!fromWu) {
21
+ const resolved = resolveRelativeImport(projectRoot, imp.from, imp.evidence);
22
+ fromWu = fileToWu.get(resolved);
23
+ }
24
+ if (!toWu) {
25
+ const resolved = resolveRelativeImport(projectRoot, imp.from, imp.evidence);
26
+ toWu = fileToWu.get(resolved);
27
+ }
28
+ if (!fromWu || !toWu)
29
+ return null;
30
+ return { fromWu, toWu, evidence: imp.evidence };
31
+ }
32
+ function pushIfNew(collector, edge) {
33
+ if (edge.from === edge.to)
34
+ return;
35
+ const key = `${edge.from}->${edge.to}|${edge.kind}`;
36
+ if (collector.seen.has(key))
37
+ return;
38
+ collector.seen.add(key);
39
+ collector.edges.push(edge);
40
+ }
41
+ function collectImportEdges(importEdges, fileToWu, projectRoot, collector) {
42
+ for (const imp of importEdges) {
43
+ const classified = classifyImportEdge(imp, fileToWu, projectRoot);
44
+ if (classified === null)
45
+ continue;
46
+ const edge = {
47
+ from: classified.fromWu,
48
+ to: classified.toWu,
49
+ kind: 'imports',
50
+ weight: 10.0,
51
+ evidence: classified.evidence,
52
+ isSemantic: false,
53
+ confidence: 'structural'
54
+ };
55
+ pushIfNew(collector, edge);
56
+ }
57
+ }
58
+ function collectSemanticEdges(kg, fileToWu, collector) {
59
+ for (const e of kg.edges) {
60
+ if (e.type !== 'contains_flow' && e.type !== 'flow_step')
61
+ continue;
62
+ const fromNode = kg.nodes.find((n) => n.id === e.source);
63
+ const toNode = kg.nodes.find((n) => n.id === e.target);
64
+ if (!fromNode?.filePath || !toNode?.filePath)
65
+ continue;
66
+ const fromWu = fileToWu.get(fromNode.filePath);
67
+ const toWu = fileToWu.get(toNode.filePath);
68
+ if (!fromWu || !toWu || fromWu === toWu)
69
+ continue;
70
+ const weight = e.type === 'flow_step' ? 0.05 : 0.1;
71
+ const edge = {
72
+ from: fromWu,
73
+ to: toWu,
74
+ kind: e.type,
75
+ weight,
76
+ evidence: `understand-anything: ${e.type} ${e.source}->${e.target}`,
77
+ isSemantic: true,
78
+ confidence: 'semantic'
79
+ };
80
+ pushIfNew(collector, edge);
81
+ }
82
+ }
83
+ export function buildDependencyEdges(wus, importEdges, kg, projectRoot) {
84
+ const fileToWu = indexFilesToWorkUnits(wus);
85
+ const collector = { edges: [], seen: new Set() };
86
+ collectImportEdges(importEdges, fileToWu, projectRoot, collector);
87
+ if (kg !== null) {
88
+ collectSemanticEdges(kg, fileToWu, collector);
89
+ }
90
+ return collector.edges;
91
+ }
92
+ function resolveRelativeImport(projectRoot, sourceFile, evidence) {
93
+ const match = /from\s+['"]([^'"]+)['"]/.exec(evidence);
94
+ if (!match)
95
+ return sourceFile;
96
+ const importPath = match[1];
97
+ if (!importPath.startsWith('.'))
98
+ return sourceFile;
99
+ const sourceDir = dirname(sourceFile);
100
+ const tsPath = importPath.replace(/\.js$/, '.ts').replace(/\.jsx$/, '.tsx');
101
+ return relative(projectRoot, join(projectRoot, sourceDir, tsPath));
102
+ }