maestro-flow 0.3.12 → 0.3.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.
Files changed (235) hide show
  1. package/.claude/CLAUDE.md +7 -0
  2. package/.claude/agents/workflow-analyzer.md +0 -1
  3. package/.claude/agents/workflow-collab-planner.md +0 -1
  4. package/.claude/agents/workflow-debugger.md +0 -1
  5. package/.claude/agents/workflow-integration-checker.md +2 -2
  6. package/.claude/agents/workflow-nyquist-auditor.md +0 -1
  7. package/.claude/agents/workflow-phase-researcher.md +2 -2
  8. package/.claude/agents/workflow-plan-checker.md +1 -1
  9. package/.claude/agents/workflow-planner.md +1 -2
  10. package/.claude/agents/workflow-roadmapper.md +1 -1
  11. package/.claude/agents/workflow-verifier.md +0 -1
  12. package/.claude/commands/learn-retro.md +2 -2
  13. package/.claude/commands/learn-second-opinion.md +2 -2
  14. package/.claude/commands/maestro-analyze.md +10 -2
  15. package/.claude/commands/maestro-brainstorm.md +1 -1
  16. package/.claude/commands/maestro-execute.md +21 -4
  17. package/.claude/commands/maestro-milestone-complete.md +14 -0
  18. package/.claude/commands/maestro-plan.md +12 -6
  19. package/.claude/commands/maestro-roadmap.md +1 -1
  20. package/.claude/commands/maestro-ui-design.md +7 -7
  21. package/.claude/commands/maestro-update.md +176 -0
  22. package/.claude/commands/maestro-verify.md +18 -3
  23. package/.claude/commands/manage-codebase-rebuild.md +0 -1
  24. package/.claude/commands/manage-harvest.md +1 -1
  25. package/.claude/commands/manage-learn.md +5 -5
  26. package/.claude/commands/manage-memory-capture.md +4 -4
  27. package/.claude/commands/manage-memory.md +1 -1
  28. package/.claude/commands/manage-wiki.md +62 -0
  29. package/.claude/commands/quality-business-test.md +2 -2
  30. package/.claude/commands/quality-debug.md +53 -7
  31. package/.claude/commands/quality-retrospective.md +5 -5
  32. package/.claude/commands/quality-review.md +39 -7
  33. package/.claude/commands/quality-sync.md +1 -1
  34. package/.claude/commands/quality-test-gen.md +1 -1
  35. package/.claude/commands/quality-test.md +45 -12
  36. package/.claude/commands/spec-remove.md +51 -0
  37. package/.claude/commands/spec-setup.md +1 -3
  38. package/.claude/commands/wiki-connect.md +9 -5
  39. package/.claude/commands/wiki-digest.md +6 -3
  40. package/.codex/skills/maestro/SKILL.md +2 -2
  41. package/.codex/skills/maestro-analyze/SKILL.md +4 -4
  42. package/.codex/skills/maestro-brainstorm/SKILL.md +4 -4
  43. package/.codex/skills/maestro-coordinate/SKILL.md +2 -2
  44. package/.codex/skills/maestro-execute/SKILL.md +15 -5
  45. package/.codex/skills/maestro-init/SKILL.md +1 -1
  46. package/.codex/skills/maestro-milestone-complete/SKILL.md +18 -1
  47. package/.codex/skills/maestro-plan/SKILL.md +6 -6
  48. package/.codex/skills/maestro-roadmap/SKILL.md +3 -4
  49. package/.codex/skills/maestro-spec-generate/SKILL.md +2 -2
  50. package/.codex/skills/maestro-ui-design/SKILL.md +6 -6
  51. package/.codex/skills/maestro-verify/SKILL.md +20 -11
  52. package/.codex/skills/manage-codebase-rebuild/SKILL.md +4 -4
  53. package/.codex/skills/manage-harvest/SKILL.md +10 -1
  54. package/.codex/skills/manage-issue-discover/SKILL.md +3 -3
  55. package/.codex/skills/manage-learn/SKILL.md +3 -2
  56. package/.codex/skills/manage-memory/SKILL.md +3 -3
  57. package/.codex/skills/manage-memory-capture/SKILL.md +8 -14
  58. package/.codex/skills/manage-status/SKILL.md +9 -4
  59. package/.codex/skills/manage-wiki/SKILL.md +55 -0
  60. package/.codex/skills/quality-business-test/SKILL.md +8 -6
  61. package/.codex/skills/quality-debug/SKILL.md +22 -9
  62. package/.codex/skills/quality-integration-test/SKILL.md +11 -7
  63. package/.codex/skills/quality-retrospective/SKILL.md +45 -26
  64. package/.codex/skills/quality-review/SKILL.md +10 -7
  65. package/.codex/skills/quality-test/SKILL.md +9 -4
  66. package/.codex/skills/quality-test-gen/SKILL.md +13 -9
  67. package/.codex/skills/spec-add/SKILL.md +11 -3
  68. package/.codex/skills/spec-load/SKILL.md +7 -0
  69. package/.codex/skills/spec-map/SKILL.md +2 -2
  70. package/.codex/skills/spec-remove/SKILL.md +101 -0
  71. package/.codex/skills/spec-setup/SKILL.md +4 -8
  72. package/.codex/skills/wiki-connect/SKILL.md +6 -5
  73. package/.codex/skills/wiki-digest/SKILL.md +2 -2
  74. package/dashboard/dist-server/dashboard/src/server/agents/claude-code-adapter.d.ts +9 -0
  75. package/dashboard/dist-server/dashboard/src/server/agents/claude-code-adapter.js +109 -9
  76. package/dashboard/dist-server/dashboard/src/server/agents/claude-code-adapter.js.map +1 -1
  77. package/dashboard/dist-server/dashboard/src/server/agents/claude-code-adapter.test.js +49 -0
  78. package/dashboard/dist-server/dashboard/src/server/agents/claude-code-adapter.test.js.map +1 -1
  79. package/dashboard/dist-server/dashboard/src/server/routes/index.js +5 -4
  80. package/dashboard/dist-server/dashboard/src/server/routes/index.js.map +1 -1
  81. package/dashboard/dist-server/dashboard/src/server/routes/specs.d.ts +5 -13
  82. package/dashboard/dist-server/dashboard/src/server/routes/specs.js +97 -155
  83. package/dashboard/dist-server/dashboard/src/server/routes/specs.js.map +1 -1
  84. package/dashboard/dist-server/dashboard/src/server/routes/wiki.d.ts +11 -1
  85. package/dashboard/dist-server/dashboard/src/server/routes/wiki.integration.test.js +27 -6
  86. package/dashboard/dist-server/dashboard/src/server/routes/wiki.integration.test.js.map +1 -1
  87. package/dashboard/dist-server/dashboard/src/server/routes/wiki.js +25 -7
  88. package/dashboard/dist-server/dashboard/src/server/routes/wiki.js.map +1 -1
  89. package/dashboard/dist-server/dashboard/src/server/wiki/graph-analysis.js +8 -0
  90. package/dashboard/dist-server/dashboard/src/server/wiki/graph-analysis.js.map +1 -1
  91. package/dashboard/dist-server/dashboard/src/server/wiki/search.js +1 -0
  92. package/dashboard/dist-server/dashboard/src/server/wiki/search.js.map +1 -1
  93. package/dashboard/dist-server/dashboard/src/server/wiki/spec-entry-parser.d.ts +29 -0
  94. package/dashboard/dist-server/dashboard/src/server/wiki/spec-entry-parser.js +148 -0
  95. package/dashboard/dist-server/dashboard/src/server/wiki/spec-entry-parser.js.map +1 -0
  96. package/dashboard/dist-server/dashboard/src/server/wiki/stress.test.js +4 -2
  97. package/dashboard/dist-server/dashboard/src/server/wiki/stress.test.js.map +1 -1
  98. package/dashboard/dist-server/dashboard/src/server/wiki/virtual-wiki-adapters.js +8 -2
  99. package/dashboard/dist-server/dashboard/src/server/wiki/virtual-wiki-adapters.js.map +1 -1
  100. package/dashboard/dist-server/dashboard/src/server/wiki/wiki-indexer.d.ts +5 -0
  101. package/dashboard/dist-server/dashboard/src/server/wiki/wiki-indexer.js +80 -38
  102. package/dashboard/dist-server/dashboard/src/server/wiki/wiki-indexer.js.map +1 -1
  103. package/dashboard/dist-server/dashboard/src/server/wiki/wiki-indexer.test.js +8 -6
  104. package/dashboard/dist-server/dashboard/src/server/wiki/wiki-indexer.test.js.map +1 -1
  105. package/dashboard/dist-server/dashboard/src/server/wiki/wiki-types.d.ts +40 -5
  106. package/dashboard/dist-server/dashboard/src/server/wiki/writer-stress.test.js +21 -23
  107. package/dashboard/dist-server/dashboard/src/server/wiki/writer-stress.test.js.map +1 -1
  108. package/dashboard/dist-server/dashboard/src/server/wiki/writer.d.ts +33 -3
  109. package/dashboard/dist-server/dashboard/src/server/wiki/writer.js +184 -12
  110. package/dashboard/dist-server/dashboard/src/server/wiki/writer.js.map +1 -1
  111. package/dashboard/dist-server/src/commands/delegate.js +26 -0
  112. package/dashboard/dist-server/src/commands/delegate.js.map +1 -1
  113. package/dashboard/dist-server/src/coordinator/graph-types.d.ts +11 -1
  114. package/dashboard/dist-server/src/coordinator/graph-walker.js +29 -2
  115. package/dashboard/dist-server/src/coordinator/graph-walker.js.map +1 -1
  116. package/dashboard/dist-server/src/coordinator/prompt-assembler.js +3 -2
  117. package/dashboard/dist-server/src/coordinator/prompt-assembler.js.map +1 -1
  118. package/dashboard/dist-server/src/hooks/constants.d.ts +29 -60
  119. package/dashboard/dist-server/src/hooks/constants.js +105 -82
  120. package/dashboard/dist-server/src/hooks/constants.js.map +1 -1
  121. package/dashboard/dist-server/src/types/index.d.ts +2 -1
  122. package/dist/src/commands/delegate.d.ts.map +1 -1
  123. package/dist/src/commands/delegate.js +26 -0
  124. package/dist/src/commands/delegate.js.map +1 -1
  125. package/dist/src/commands/hooks.d.ts +2 -4
  126. package/dist/src/commands/hooks.d.ts.map +1 -1
  127. package/dist/src/commands/hooks.js +4 -7
  128. package/dist/src/commands/hooks.js.map +1 -1
  129. package/dist/src/commands/install-ui/InstallConfirm.d.ts +2 -3
  130. package/dist/src/commands/install-ui/InstallConfirm.d.ts.map +1 -1
  131. package/dist/src/commands/install-ui/InstallConfirm.js +1 -1
  132. package/dist/src/commands/install-ui/InstallConfirm.js.map +1 -1
  133. package/dist/src/commands/install-ui/InstallExecution.d.ts.map +1 -1
  134. package/dist/src/commands/install-ui/InstallExecution.js +1 -2
  135. package/dist/src/commands/install-ui/InstallExecution.js.map +1 -1
  136. package/dist/src/commands/install-ui/InstallFlow.d.ts.map +1 -1
  137. package/dist/src/commands/install-ui/InstallFlow.js +5 -7
  138. package/dist/src/commands/install-ui/InstallFlow.js.map +1 -1
  139. package/dist/src/commands/install-ui/StatuslineConfig.d.ts +3 -6
  140. package/dist/src/commands/install-ui/StatuslineConfig.d.ts.map +1 -1
  141. package/dist/src/commands/install-ui/StatuslineConfig.js +21 -17
  142. package/dist/src/commands/install-ui/StatuslineConfig.js.map +1 -1
  143. package/dist/src/commands/update.d.ts.map +1 -1
  144. package/dist/src/commands/update.js +95 -0
  145. package/dist/src/commands/update.js.map +1 -1
  146. package/dist/src/commands/wiki.d.ts.map +1 -1
  147. package/dist/src/commands/wiki.js +75 -11
  148. package/dist/src/commands/wiki.js.map +1 -1
  149. package/dist/src/coordinator/graph-types.d.ts +11 -1
  150. package/dist/src/coordinator/graph-types.d.ts.map +1 -1
  151. package/dist/src/coordinator/graph-walker.d.ts.map +1 -1
  152. package/dist/src/coordinator/graph-walker.js +29 -2
  153. package/dist/src/coordinator/graph-walker.js.map +1 -1
  154. package/dist/src/coordinator/prompt-assembler.d.ts.map +1 -1
  155. package/dist/src/coordinator/prompt-assembler.js +3 -2
  156. package/dist/src/coordinator/prompt-assembler.js.map +1 -1
  157. package/dist/src/hooks/__tests__/statusline-visual-test.d.ts +4 -1
  158. package/dist/src/hooks/__tests__/statusline-visual-test.d.ts.map +1 -1
  159. package/dist/src/hooks/__tests__/statusline-visual-test.js +55 -174
  160. package/dist/src/hooks/__tests__/statusline-visual-test.js.map +1 -1
  161. package/dist/src/hooks/constants.d.ts +29 -60
  162. package/dist/src/hooks/constants.d.ts.map +1 -1
  163. package/dist/src/hooks/constants.js +105 -82
  164. package/dist/src/hooks/constants.js.map +1 -1
  165. package/dist/src/hooks/skill-context.d.ts.map +1 -1
  166. package/dist/src/hooks/skill-context.js +54 -6
  167. package/dist/src/hooks/skill-context.js.map +1 -1
  168. package/dist/src/hooks/statusline.d.ts +11 -8
  169. package/dist/src/hooks/statusline.d.ts.map +1 -1
  170. package/dist/src/hooks/statusline.js +284 -182
  171. package/dist/src/hooks/statusline.js.map +1 -1
  172. package/dist/src/hooks/workspace.d.ts.map +1 -1
  173. package/dist/src/hooks/workspace.js +2 -1
  174. package/dist/src/hooks/workspace.js.map +1 -1
  175. package/dist/src/migrations/_template.d.ts +12 -0
  176. package/dist/src/migrations/_template.d.ts.map +1 -0
  177. package/dist/src/migrations/_template.js +55 -0
  178. package/dist/src/migrations/_template.js.map +1 -0
  179. package/dist/src/migrations/index.d.ts +14 -0
  180. package/dist/src/migrations/index.d.ts.map +1 -0
  181. package/dist/src/migrations/index.js +20 -0
  182. package/dist/src/migrations/index.js.map +1 -0
  183. package/dist/src/migrations/run.d.ts +12 -0
  184. package/dist/src/migrations/run.d.ts.map +1 -0
  185. package/dist/src/migrations/run.js +119 -0
  186. package/dist/src/migrations/run.js.map +1 -0
  187. package/dist/src/migrations/v1-to-v2.d.ts +10 -0
  188. package/dist/src/migrations/v1-to-v2.d.ts.map +1 -0
  189. package/dist/src/migrations/v1-to-v2.js +71 -0
  190. package/dist/src/migrations/v1-to-v2.js.map +1 -0
  191. package/dist/src/tools/team-activity.d.ts.map +1 -1
  192. package/dist/src/tools/team-activity.js +22 -0
  193. package/dist/src/tools/team-activity.js.map +1 -1
  194. package/dist/src/tools/transition-recorder.d.ts +2 -17
  195. package/dist/src/tools/transition-recorder.d.ts.map +1 -1
  196. package/dist/src/tools/transition-recorder.js +6 -3
  197. package/dist/src/tools/transition-recorder.js.map +1 -1
  198. package/dist/src/types/index.d.ts +2 -1
  199. package/dist/src/types/index.d.ts.map +1 -1
  200. package/dist/src/utils/migration-registry.d.ts +65 -0
  201. package/dist/src/utils/migration-registry.d.ts.map +1 -0
  202. package/dist/src/utils/migration-registry.js +117 -0
  203. package/dist/src/utils/migration-registry.js.map +1 -0
  204. package/dist/src/utils/state-schema.d.ts +153 -0
  205. package/dist/src/utils/state-schema.d.ts.map +1 -0
  206. package/dist/src/utils/state-schema.js +329 -0
  207. package/dist/src/utils/state-schema.js.map +1 -0
  208. package/package.json +1 -1
  209. package/templates/state.json +17 -39
  210. package/workflows/brainstorm.md +3 -3
  211. package/workflows/codebase-rebuild.md +2 -12
  212. package/workflows/debug.md +7 -8
  213. package/workflows/execute.md +18 -4
  214. package/workflows/fork.md +37 -86
  215. package/workflows/init.md +1 -4
  216. package/workflows/integration-test.md +4 -5
  217. package/workflows/issue.md +3 -9
  218. package/workflows/learn.md +20 -19
  219. package/workflows/maestro.codex.md +8 -1
  220. package/workflows/maestro.md +12 -3
  221. package/workflows/memory.md +26 -71
  222. package/workflows/merge.md +45 -107
  223. package/workflows/milestone-complete.md +24 -7
  224. package/workflows/retrospective.md +77 -109
  225. package/workflows/review.md +5 -12
  226. package/workflows/specs-remove.md +115 -0
  227. package/workflows/specs-setup.md +10 -32
  228. package/workflows/status.md +291 -290
  229. package/workflows/sync.md +5 -5
  230. package/workflows/test.md +4 -5
  231. package/workflows/ui-style.md +3 -4
  232. package/workflows/verify.md +2 -2
  233. package/workflows/wiki-connect.md +188 -0
  234. package/workflows/wiki-digest.md +221 -0
  235. package/workflows/wiki-manage.md +204 -0
@@ -1,55 +1,27 @@
1
1
  /**
2
- * Maestro Statusline Hook — Powerline × Notion style
2
+ * Maestro Statusline Hook — Two-line colored text
3
3
  *
4
- * Renders a Nerd-Font Powerline statusline with muted Notion-inspired colors.
5
- * Segments are conditionally shown empty segments are omitted for a clean line.
6
- *
7
- * Segments (left → right):
8
- * Model | Phase | Coordinator | Task | Team | Directory+Git | Context bar
4
+ * Line 1: Model | Coordinator | Task | Team | Dir+Git | Context
5
+ * Line 2: Milestone ◆Phase | Session chain (ANL-001→PLN-001→EXC-001→VRF-001 ✓)
9
6
  *
10
7
  * Input (stdin JSON from Claude Code):
11
8
  * { model, workspace, session_id, context_window }
12
9
  *
13
- * Output (stdout): formatted Powerline string
10
+ * Output (stdout): formatted ANSI string (1 or 2 lines)
14
11
  */
15
12
  import { readFileSync, readdirSync, statSync, existsSync, writeFileSync } from 'node:fs';
16
13
  import { join, basename } from 'node:path';
17
14
  import { homedir, tmpdir } from 'node:os';
18
15
  import { execSync } from 'node:child_process';
19
- import { AUTO_COMPACT_BUFFER_PCT, BRIDGE_PREFIX, ANSI_RESET, ANSI_BOLD, PL_SEP, ICONS, GIT_ICONS, SEGMENT_BG, SEGMENT_FG, TEXT_COLORS, ansiBg, ansiFg, getCtxLevel, getStatuslineStyle, } from './constants.js';
16
+ import { AUTO_COMPACT_BUFFER_PCT, BRIDGE_PREFIX, ANSI_RESET, ICONS, GIT_ICONS, TEXT_COLORS, ansiFg, getCtxLevel, } from './constants.js';
20
17
  import { readCoordBridge } from './coordinator-tracker.js';
21
18
  import { resolveSelf } from '../tools/team-members.js';
22
19
  import { readRecentActivity } from '../tools/team-activity.js';
23
20
  import { findWorkspaceRoot } from './workspace.js';
24
21
  // ---------------------------------------------------------------------------
25
- // Renderers
22
+ // Renderer
26
23
  // ---------------------------------------------------------------------------
27
- /**
28
- * Powerline mode: colored background segments with arrow separators.
29
- * Separator uses bold ANSI to make the V-chevron more prominent.
30
- */
31
- function renderPowerline(segments) {
32
- if (segments.length === 0)
33
- return '';
34
- let out = '';
35
- for (let i = 0; i < segments.length; i++) {
36
- const seg = segments[i];
37
- out += ansiBg(seg.bg) + ansiFg(seg.fg) + ` ${seg.text} `;
38
- if (i < segments.length - 1) {
39
- // Bold separator: fg = current bg, bg = next bg → Powerline dovetail
40
- out += ANSI_BOLD + ansiFg(seg.bg) + ansiBg(segments[i + 1].bg) + PL_SEP + ANSI_RESET;
41
- }
42
- else {
43
- // Last segment: arrow fades into terminal background
44
- out += ANSI_RESET + ANSI_BOLD + ansiFg(seg.bg) + PL_SEP + ANSI_RESET;
45
- }
46
- }
47
- return out;
48
- }
49
- /**
50
- * Colored-text mode: colored text on transparent background, pipe separators.
51
- * Similar style to CCometixLine reference.
52
- */
24
+ /** Colored text on transparent background, pipe separators */
53
25
  function renderColoredText(segments) {
54
26
  if (segments.length === 0)
55
27
  return '';
@@ -65,26 +37,26 @@ function renderColoredText(segments) {
65
37
  // ---------------------------------------------------------------------------
66
38
  // Context usage
67
39
  // ---------------------------------------------------------------------------
68
- /** Normalize remaining% to usable context (accounts for autocompact buffer) */
69
40
  function normalizeUsage(remaining) {
70
41
  const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
71
42
  return Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
72
43
  }
73
- /** Build context bar text: "icon ██████░░░░ 62%" */
74
44
  function buildContextText(usedPct) {
75
45
  const filled = Math.floor(usedPct / 10);
76
46
  const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(10 - filled);
77
47
  return `${ICONS.ctx} ${bar} ${usedPct}%`;
78
48
  }
79
- /** Get context segment colors by level */
80
- function getCtxColors(level) {
81
- const map = {
82
- ok: { bg: SEGMENT_BG.ctxOk, fg: SEGMENT_FG.ctxOk },
83
- warn: { bg: SEGMENT_BG.ctxWarn, fg: SEGMENT_FG.ctxWarn },
84
- alert: { bg: SEGMENT_BG.ctxAlert, fg: SEGMENT_FG.ctxAlert },
85
- crit: { bg: SEGMENT_BG.ctxCrit, fg: SEGMENT_FG.ctxCrit },
86
- };
87
- return map[level];
49
+ /** Format token count: 1234 "1.2k", 123456 → "123k" */
50
+ function formatTokens(n) {
51
+ if (n < 1000)
52
+ return String(n);
53
+ if (n < 10000)
54
+ return (n / 1000).toFixed(1) + 'k';
55
+ return Math.round(n / 1000) + 'k';
56
+ }
57
+ /** Build token usage text: "↑12k ↓3k Σ15k" */
58
+ function buildTokenText(input, output) {
59
+ return `↑${formatTokens(input)} ↓${formatTokens(output)} Σ${formatTokens(input + output)}`;
88
60
  }
89
61
  // ---------------------------------------------------------------------------
90
62
  // Bridge
@@ -107,7 +79,6 @@ function writeBridge(session, remaining, usedPct) {
107
79
  // ---------------------------------------------------------------------------
108
80
  // Data readers
109
81
  // ---------------------------------------------------------------------------
110
- /** Read current in-progress task from Claude Code todos */
111
82
  function readCurrentTask(session) {
112
83
  const claudeDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
113
84
  const todosDir = join(claudeDir, 'todos');
@@ -133,8 +104,63 @@ function readCurrentTask(session) {
133
104
  const emptyWf = {
134
105
  milestone: '', currentPhase: 0, currentStep: 0, status: '',
135
106
  total: 0, completed: 0, inProgress: 0, planned: 0, workspaceRoot: '',
107
+ chains: [], orphans: [], currentTaskId: '',
136
108
  };
137
- /** Read milestone + phase progress from .workflow/state.json */
109
+ /**
110
+ * Build dependency chains from artifacts.
111
+ * Walk depends_on links: find roots (no depends_on), then follow forward.
112
+ */
113
+ function buildChains(artifacts) {
114
+ if (artifacts.length === 0)
115
+ return { chains: [], orphans: [] };
116
+ const byId = new Map();
117
+ for (const a of artifacts)
118
+ byId.set(a.id, a);
119
+ // Build forward map: parent → children
120
+ const children = new Map();
121
+ const hasParent = new Set();
122
+ for (const a of artifacts) {
123
+ const deps = a.depends_on;
124
+ if (!deps)
125
+ continue;
126
+ const depIds = Array.isArray(deps) ? deps : [deps];
127
+ for (const depId of depIds) {
128
+ if (byId.has(depId)) {
129
+ hasParent.add(a.id);
130
+ const existing = children.get(depId) || [];
131
+ existing.push(a.id);
132
+ children.set(depId, existing);
133
+ }
134
+ }
135
+ }
136
+ // Roots: artifacts with no parent in this set
137
+ const roots = artifacts.filter(a => !hasParent.has(a.id));
138
+ const visited = new Set();
139
+ const chains = [];
140
+ for (const root of roots) {
141
+ if (visited.has(root.id))
142
+ continue;
143
+ const chain = [];
144
+ let current = root.id;
145
+ while (current && !visited.has(current)) {
146
+ visited.add(current);
147
+ const art = byId.get(current);
148
+ if (art)
149
+ chain.push(art);
150
+ // Follow first child (linear chain)
151
+ const kids = children.get(current);
152
+ current = kids?.[0];
153
+ }
154
+ if (chain.length > 0) {
155
+ const allCompleted = chain.every(a => a.status === 'completed');
156
+ chains.push({ artifacts: chain, allCompleted });
157
+ }
158
+ }
159
+ // Orphans: not visited by any chain walk
160
+ const orphans = artifacts.filter(a => !visited.has(a.id));
161
+ return { chains, orphans };
162
+ }
163
+ /** Read milestone + artifact chains from .workflow/state.json */
138
164
  function readWorkflowState(dir) {
139
165
  const root = findWorkspaceRoot(dir);
140
166
  if (!root)
@@ -147,13 +173,67 @@ function readWorkflowState(dir) {
147
173
  const result = { ...emptyWf, workspaceRoot: root };
148
174
  if (state.current_milestone)
149
175
  result.milestone = state.current_milestone;
150
- if (state.current_phase)
151
- result.currentPhase = state.current_phase;
152
- if (state.current_step)
153
- result.currentStep = state.current_step;
154
176
  if (state.status)
155
177
  result.status = state.status;
156
- if (state.phases_summary) {
178
+ const rawArtifacts = Array.isArray(state.artifacts) ? state.artifacts : [];
179
+ const milestone = Array.isArray(state.milestones)
180
+ ? state.milestones.find((m) => m.name === state.current_milestone || m.id === state.current_milestone)
181
+ : null;
182
+ const phases = milestone?.phases ?? [];
183
+ // Filter to current milestone artifacts
184
+ const msArtifacts = rawArtifacts
185
+ .filter(a => a.milestone === state.current_milestone && a.id && a.type && a.status)
186
+ .map(a => ({
187
+ id: a.id,
188
+ type: a.type,
189
+ status: a.status,
190
+ phase: a.phase ?? null,
191
+ path: a.path ?? '',
192
+ depends_on: a.depends_on ?? null,
193
+ }));
194
+ if (phases.length > 0 && msArtifacts.length > 0) {
195
+ result.total = phases.length;
196
+ let completed = 0, inProgress = 0, planned = 0;
197
+ for (const p of phases) {
198
+ const phaseArts = msArtifacts.filter(a => a.phase === p);
199
+ if (phaseArts.some(a => a.type === 'execute' && a.status === 'completed')) {
200
+ completed++;
201
+ continue;
202
+ }
203
+ if (phaseArts.some(a => a.type === 'plan' && a.status === 'completed')) {
204
+ planned++;
205
+ inProgress++;
206
+ continue;
207
+ }
208
+ if (phaseArts.length > 0) {
209
+ inProgress++;
210
+ }
211
+ }
212
+ result.completed = completed;
213
+ result.inProgress = inProgress;
214
+ result.planned = planned;
215
+ // Current phase
216
+ for (const p of phases) {
217
+ if (msArtifacts.some(a => a.phase === p && a.status === 'in_progress')) {
218
+ result.currentPhase = p;
219
+ break;
220
+ }
221
+ }
222
+ if (!result.currentPhase) {
223
+ for (const p of phases) {
224
+ if (!msArtifacts.some(a => a.type === 'execute' && a.phase === p && a.status === 'completed')) {
225
+ result.currentPhase = p;
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ // Build chains
231
+ const { chains, orphans } = buildChains(msArtifacts);
232
+ result.chains = chains;
233
+ result.orphans = orphans;
234
+ }
235
+ else if (state.phases_summary) {
236
+ // v1 fallback
157
237
  const s = state.phases_summary;
158
238
  if (typeof s.total === 'number')
159
239
  result.total = s.total;
@@ -161,17 +241,13 @@ function readWorkflowState(dir) {
161
241
  result.completed = s.completed;
162
242
  if (typeof s.in_progress === 'number')
163
243
  result.inProgress = s.in_progress;
244
+ if (state.current_phase)
245
+ result.currentPhase = state.current_phase;
164
246
  }
165
- // Count how many phases have plan artifacts
166
- if (result.total > 0) {
167
- let planned = 0;
168
- for (let p = 1; p <= result.total; p++) {
169
- const planPath = join(root, '.workflow', 'scratch', `P${p}`, 'plan.json');
170
- if (existsSync(planPath))
171
- planned++;
172
- }
173
- result.planned = planned;
174
- }
247
+ if (state.current_step)
248
+ result.currentStep = state.current_step;
249
+ if (state.current_task_id)
250
+ result.currentTaskId = state.current_task_id;
175
251
  return result;
176
252
  }
177
253
  catch {
@@ -207,21 +283,20 @@ function readGitInfo(dir) {
207
283
  }
208
284
  }
209
285
  function formatGitSuffix(git) {
210
- let status = '';
286
+ const parts = [];
211
287
  if (git.conflict)
212
- status += GIT_ICONS.conflict;
288
+ parts.push(GIT_ICONS.conflict);
213
289
  else if (git.dirty)
214
- status += GIT_ICONS.dirty;
215
- else
216
- status += GIT_ICONS.clean;
290
+ parts.push(GIT_ICONS.dirty);
217
291
  if (git.ahead > 0)
218
- status += `${GIT_ICONS.ahead}${git.ahead}`;
292
+ parts.push(`${GIT_ICONS.ahead}${git.ahead}`);
219
293
  if (git.behind > 0)
220
- status += `${GIT_ICONS.behind}${git.behind}`;
221
- return `${ICONS.git} ${git.branch} ${status}`;
294
+ parts.push(`${GIT_ICONS.behind}${git.behind}`);
295
+ const suffix = parts.length > 0 ? ` ${parts.join('')}` : '';
296
+ return `${ICONS.git} ${git.branch}${suffix}`;
222
297
  }
223
298
  // ---------------------------------------------------------------------------
224
- // Teammate activity segment (team-lite Wave 3B)
299
+ // Teammate activity segment
225
300
  // ---------------------------------------------------------------------------
226
301
  const TEAM_CACHE_TTL_MS = 10_000;
227
302
  const TEAM_WINDOW_MIN = 30;
@@ -231,30 +306,23 @@ function teamCachePath(session) {
231
306
  }
232
307
  function writeTeamCache(path, segment) {
233
308
  try {
234
- const data = { ts: Date.now(), segment };
235
- writeFileSync(path, JSON.stringify(data));
236
- }
237
- catch {
238
- // Best-effort
309
+ writeFileSync(path, JSON.stringify({ ts: Date.now(), segment }));
239
310
  }
311
+ catch { /* best-effort */ }
240
312
  return segment;
241
313
  }
242
314
  function shortTaskId(taskId) {
243
315
  const idx = taskId.lastIndexOf('-');
244
- if (idx < 0)
245
- return taskId;
246
- return taskId.slice(idx + 1) || taskId;
316
+ return idx < 0 ? taskId : (taskId.slice(idx + 1) || taskId);
247
317
  }
248
318
  function formatTeammate(name, evt) {
249
319
  if (typeof evt.phase_id === 'number' && typeof evt.task_id === 'string' && evt.task_id) {
250
320
  return `${name} (P${evt.phase_id}/${shortTaskId(evt.task_id)})`;
251
321
  }
252
- if (typeof evt.phase_id === 'number') {
322
+ if (typeof evt.phase_id === 'number')
253
323
  return `${name} (P${evt.phase_id})`;
254
- }
255
- if (typeof evt.target === 'string' && evt.target) {
324
+ if (typeof evt.target === 'string' && evt.target)
256
325
  return `${name} (${evt.target})`;
257
- }
258
326
  return name;
259
327
  }
260
328
  export function buildTeamSegment(session) {
@@ -263,16 +331,11 @@ export function buildTeamSegment(session) {
263
331
  if (existsSync(cachePath)) {
264
332
  try {
265
333
  const cached = JSON.parse(readFileSync(cachePath, 'utf8'));
266
- if (cached &&
267
- typeof cached.ts === 'number' &&
268
- typeof cached.segment === 'string' &&
269
- Date.now() - cached.ts < TEAM_CACHE_TTL_MS) {
334
+ if (cached && typeof cached.ts === 'number' && typeof cached.segment === 'string' && Date.now() - cached.ts < TEAM_CACHE_TTL_MS) {
270
335
  return cached.segment;
271
336
  }
272
337
  }
273
- catch {
274
- // Corrupt cache — recompute
275
- }
338
+ catch { /* corrupt cache */ }
276
339
  }
277
340
  const self = resolveSelf();
278
341
  if (!self)
@@ -294,9 +357,8 @@ export function buildTeamSegment(session) {
294
357
  }
295
358
  const prevT = Date.parse(prev.ts);
296
359
  const curT = Date.parse(evt.ts);
297
- if (!Number.isNaN(curT) && (Number.isNaN(prevT) || curT >= prevT)) {
360
+ if (!Number.isNaN(curT) && (Number.isNaN(prevT) || curT >= prevT))
298
361
  latest.set(key, evt);
299
- }
300
362
  }
301
363
  if (latest.size === 0)
302
364
  return writeTeamCache(cachePath, '');
@@ -305,13 +367,12 @@ export function buildTeamSegment(session) {
305
367
  const tb = Date.parse(b.ts);
306
368
  return (Number.isNaN(tb) ? 0 : tb) - (Number.isNaN(ta) ? 0 : ta);
307
369
  });
308
- const inline = ordered.slice(0, TEAM_MAX_INLINE).map((evt) => formatTeammate(evt.user, evt));
370
+ const inline = ordered.slice(0, TEAM_MAX_INLINE).map(evt => formatTeammate(evt.user, evt));
309
371
  let body = inline.join(' | ');
310
372
  const extra = ordered.length - inline.length;
311
373
  if (extra > 0)
312
374
  body += ` +${extra}`;
313
- const segment = `\u{1F465} ${body}`;
314
- return writeTeamCache(cachePath, segment);
375
+ return writeTeamCache(cachePath, `\u{1F465} ${body}`);
315
376
  }
316
377
  catch {
317
378
  return '';
@@ -333,7 +394,6 @@ export function buildCoordinatorSegment(session) {
333
394
  const isPaused = status === 'paused' || status === 'step_paused';
334
395
  const progress = isPaused ? 'P' : `${steps_completed}/${steps_total}`;
335
396
  const stepLabel = current_step?.skill ?? '';
336
- // chain_name → stepLabel [progress]
337
397
  const parts = [];
338
398
  if (chain_name)
339
399
  parts.push(chain_name);
@@ -346,9 +406,73 @@ export function buildCoordinatorSegment(session) {
346
406
  }
347
407
  }
348
408
  // ---------------------------------------------------------------------------
409
+ // Chain renderer — session chain for line 2+
410
+ // ---------------------------------------------------------------------------
411
+ /** Type abbreviation and color */
412
+ const TYPE_META = {
413
+ analyze: { abbr: 'A', color: TEXT_COLORS.model },
414
+ plan: { abbr: 'P', color: TEXT_COLORS.milestone },
415
+ execute: { abbr: 'E', color: TEXT_COLORS.phase },
416
+ verify: { abbr: 'V', color: TEXT_COLORS.coord },
417
+ brainstorm: { abbr: 'B', color: TEXT_COLORS.team },
418
+ spec: { abbr: 'S', color: TEXT_COLORS.dir },
419
+ review: { abbr: 'R', color: TEXT_COLORS.ctxAlert },
420
+ debug: { abbr: 'D', color: TEXT_COLORS.ctxCrit },
421
+ test: { abbr: 'T', color: TEXT_COLORS.ctxOk },
422
+ };
423
+ /** Color a type abbreviation */
424
+ function colorType(type) {
425
+ const meta = TYPE_META[type] ?? { abbr: type[0]?.toUpperCase() ?? '?', color: TEXT_COLORS.task };
426
+ return ansiFg(meta.color) + meta.abbr + ANSI_RESET;
427
+ }
428
+ /** Extract readable slug from artifact path */
429
+ function extractSlug(art) {
430
+ const b = basename(art.path || '');
431
+ // scratch/analyze-auth-2026-04-20 → auth
432
+ // phases/01-auth-multi-tenant → auth-multi-tenant
433
+ // scratch/20260421-review-P1-auth → auth
434
+ return b
435
+ .replace(/^\d+-/, '') // leading number prefix
436
+ .replace(/^\d{8}-/, '') // YYYYMMDD- prefix
437
+ .replace(/^(analyze|plan|execute|verify|brainstorm|spec|review|debug|test)-/, '') // type prefix
438
+ .replace(/-\d{4}-\d{2}-\d{2}$/, '') // trailing date
439
+ .replace(/-P\d+/, '') // -P1, -P2
440
+ || art.type;
441
+ }
442
+ /** Status suffix */
443
+ function statusSuffix(status) {
444
+ const map = {
445
+ completed: { icon: '✓', color: TEXT_COLORS.ctxOk },
446
+ in_progress: { icon: '●', color: TEXT_COLORS.ctxWarn },
447
+ failed: { icon: '✗', color: TEXT_COLORS.ctxCrit },
448
+ pending: { icon: '○', color: TEXT_COLORS.separator },
449
+ };
450
+ const s = map[status];
451
+ return s ? ansiFg(s.color) + s.icon + ANSI_RESET : '';
452
+ }
453
+ /** Render chain: auth: A→P→E→R→D→T→V ✓ */
454
+ function renderChain(chain) {
455
+ const arrow = ansiFg(TEXT_COLORS.separator) + '→' + ANSI_RESET;
456
+ const slug = extractSlug(chain.artifacts[0]);
457
+ const types = chain.artifacts.map(a => colorType(a.type));
458
+ const lastArt = chain.artifacts[chain.artifacts.length - 1];
459
+ const slugLabel = ansiFg(TEXT_COLORS.task) + slug + ANSI_RESET;
460
+ const flow = types.join(arrow);
461
+ const suffix = chain.allCompleted
462
+ ? ' ' + ansiFg(TEXT_COLORS.ctxOk) + '✓' + ANSI_RESET
463
+ : ' ' + statusSuffix(lastArt.status);
464
+ return `${slugLabel} ${flow}${suffix}`;
465
+ }
466
+ /** Render orphan: brainstorm-ux B ✓ */
467
+ function renderOrphan(art) {
468
+ const slug = extractSlug(art);
469
+ const slugLabel = ansiFg(TEXT_COLORS.task) + slug + ANSI_RESET;
470
+ return `${slugLabel} ${colorType(art.type)} ${statusSuffix(art.status)}`;
471
+ }
472
+ // ---------------------------------------------------------------------------
349
473
  // Main formatter
350
474
  // ---------------------------------------------------------------------------
351
- /** Main statusline handler — processes input and returns Powerline string */
475
+ /** Main statusline handler — two-line output */
352
476
  export function formatStatusline(data) {
353
477
  const model = data.model?.display_name || 'Claude';
354
478
  const dir = data.workspace?.current_dir || process.cwd();
@@ -366,98 +490,76 @@ export function formatStatusline(data) {
366
490
  if (session)
367
491
  writeBridge(session, remaining, usedPct);
368
492
  }
369
- // ---- Build segments ----
493
+ // ---- Line 1: Model | Coord | Task | Team | Dir+Git | Context ----
370
494
  const segments = [];
371
- // 1. Model
372
- segments.push({
373
- key: 'model',
374
- text: `${ICONS.model} ${model}`,
375
- bg: SEGMENT_BG.model,
376
- fg: SEGMENT_FG.model,
377
- });
378
- // 2. Milestone (conditional — shown when workflow has milestones)
379
- if (wf.milestone) {
380
- let msText = `${ICONS.milestone} ${wf.milestone}`;
381
- if (wf.total > 0)
382
- msText += ` ${wf.completed}/${wf.total}`;
383
- segments.push({
384
- key: 'milestone',
385
- text: msText,
386
- bg: SEGMENT_BG.milestone,
387
- fg: SEGMENT_FG.milestone,
388
- });
389
- }
390
- // 3. Phase (conditional — shows current phase + status detail)
391
- if (wf.currentPhase) {
392
- let phaseText = `${ICONS.phase} P${wf.currentPhase}`;
393
- if (wf.currentStep)
394
- phaseText += `.${wf.currentStep}`;
395
- const tags = [];
396
- if (wf.planned > 0)
397
- tags.push(`${wf.planned}plan`);
398
- if (wf.inProgress > 0)
399
- tags.push(`${wf.inProgress}run`);
400
- if (tags.length > 0)
401
- phaseText += ` [${tags.join(' ')}]`;
402
- segments.push({
403
- key: 'phase',
404
- text: phaseText,
405
- bg: SEGMENT_BG.phase,
406
- fg: SEGMENT_FG.phase,
407
- });
408
- }
409
- // 4. Coordinator + chain (conditional)
410
- if (coord) {
411
- segments.push({
412
- key: 'coord',
413
- text: `${ICONS.coord} ${coord}`,
414
- bg: SEGMENT_BG.coord,
415
- fg: SEGMENT_FG.coord,
416
- });
417
- }
418
- // 5. Task (conditional)
419
- if (task) {
420
- segments.push({
421
- key: 'task',
422
- text: `${ICONS.task} ${task}`,
423
- bg: SEGMENT_BG.task,
424
- fg: SEGMENT_FG.task,
425
- });
426
- }
427
- // 6. Team (conditional)
428
- if (team) {
429
- segments.push({
430
- key: 'team',
431
- text: `${ICONS.team} ${team}`,
432
- bg: SEGMENT_BG.team,
433
- fg: SEGMENT_FG.team,
434
- });
435
- }
436
- // 7. Directory + Git
495
+ segments.push({ key: 'model', text: `${ICONS.model} ${model}` });
496
+ if (coord)
497
+ segments.push({ key: 'coord', text: `${ICONS.coord} ${coord}` });
498
+ if (task)
499
+ segments.push({ key: 'task', text: `${ICONS.task} ${task}` });
500
+ if (team)
501
+ segments.push({ key: 'team', text: `${ICONS.team} ${team}` });
437
502
  let dirText = `${ICONS.dir} ${basename(dir)}`;
438
503
  if (git)
439
504
  dirText += ` ${formatGitSuffix(git)}`;
440
- segments.push({
441
- key: 'dir',
442
- text: dirText,
443
- bg: SEGMENT_BG.dir,
444
- fg: SEGMENT_FG.dir,
445
- });
446
- // 8. Context bar (conditional — only when data available)
505
+ segments.push({ key: 'dir', text: dirText });
506
+ // Token usage + lines changed
507
+ const inputTokens = data.context_window?.total_input_tokens;
508
+ const outputTokens = data.context_window?.total_output_tokens;
509
+ const linesAdded = data.cost?.total_lines_added ?? 0;
510
+ const linesRemoved = data.cost?.total_lines_removed ?? 0;
511
+ const statParts = [];
512
+ if (inputTokens != null && outputTokens != null && (inputTokens > 0 || outputTokens > 0)) {
513
+ statParts.push(buildTokenText(inputTokens, outputTokens));
514
+ }
515
+ if (linesAdded > 0 || linesRemoved > 0) {
516
+ const added = ansiFg(TEXT_COLORS.ctxOk) + `+${linesAdded}` + ANSI_RESET;
517
+ const removed = ansiFg(TEXT_COLORS.ctxCrit) + `-${linesRemoved}` + ANSI_RESET;
518
+ statParts.push(`${added} ${removed}`);
519
+ }
520
+ if (statParts.length > 0) {
521
+ segments.push({ key: 'task', text: statParts.join(' ') });
522
+ }
523
+ // Context bar
447
524
  if (remaining != null) {
448
525
  const level = getCtxLevel(usedPct);
449
- const colors = getCtxColors(level);
450
526
  const ctxKey = `ctx${level.charAt(0).toUpperCase()}${level.slice(1)}`;
451
- segments.push({
452
- key: ctxKey,
453
- text: buildContextText(usedPct),
454
- bg: colors.bg,
455
- fg: colors.fg,
456
- });
527
+ segments.push({ key: ctxKey, text: buildContextText(usedPct) });
528
+ }
529
+ const line1 = renderColoredText(segments);
530
+ // ---- Line 2: Milestone ◆Phase | Session chains (conditional) ----
531
+ if (!wf.milestone)
532
+ return line1;
533
+ const sep = ansiFg(TEXT_COLORS.separator) + ' | ' + ANSI_RESET;
534
+ const dot = ansiFg(TEXT_COLORS.separator) + ' · ' + ANSI_RESET;
535
+ // Milestone + phase header
536
+ let header = ansiFg(TEXT_COLORS.milestone) + `${ICONS.milestone} ${wf.milestone}` + ANSI_RESET;
537
+ if (wf.total > 0)
538
+ header += ansiFg(TEXT_COLORS.milestone) + ` ${wf.completed}/${wf.total}` + ANSI_RESET;
539
+ if (wf.currentPhase)
540
+ header += ' ' + ansiFg(TEXT_COLORS.phase) + `${ICONS.phase} P${wf.currentPhase}` + ANSI_RESET;
541
+ // Session chains
542
+ const chainParts = [];
543
+ for (const chain of wf.chains) {
544
+ chainParts.push(renderChain(chain));
545
+ }
546
+ for (const orphan of wf.orphans) {
547
+ chainParts.push(renderOrphan(orphan));
548
+ }
549
+ if (chainParts.length === 0) {
550
+ return line1 + '\n' + header;
551
+ }
552
+ // Auto multi-line: ≤2 chains → single line, >2 → one chain per line
553
+ if (chainParts.length <= 2) {
554
+ const line2 = header + sep + chainParts.join(dot);
555
+ return line1 + '\n' + line2;
556
+ }
557
+ // Multi-line: header on line 2, each chain on its own line
558
+ const lines = [line1, header];
559
+ for (const part of chainParts) {
560
+ lines.push(' ' + part);
457
561
  }
458
- // ---- Render ----
459
- const style = getStatuslineStyle();
460
- return style === 'powerline' ? renderPowerline(segments) : renderColoredText(segments);
562
+ return lines.join('\n');
461
563
  }
462
564
  /** Entry point — reads stdin JSON, writes formatted statusline to stdout */
463
565
  export function runStatusline() {