planflow-ai 1.3.4 → 1.4.1

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 (90) hide show
  1. package/.claude/commands/create-plan.md +11 -0
  2. package/.claude/commands/discovery-plan.md +12 -0
  3. package/.claude/commands/execute-plan.md +114 -23
  4. package/.claude/commands/flow.md +30 -5
  5. package/.claude/commands/resume-work.md +261 -0
  6. package/.claude/commands/review-code.md +11 -0
  7. package/.claude/commands/review-pr.md +11 -0
  8. package/.claude/resources/core/_index.md +45 -2
  9. package/.claude/resources/core/atomic-commits.md +380 -0
  10. package/.claude/resources/core/autopilot-mode.md +3 -2
  11. package/.claude/resources/core/compaction-guide.md +15 -1
  12. package/.claude/resources/core/heartbeat.md +129 -1
  13. package/.claude/resources/core/model-routing.md +6 -2
  14. package/.claude/resources/core/per-task-verification.md +362 -0
  15. package/.claude/resources/core/phase-isolation.md +192 -4
  16. package/.claude/resources/core/session-scratchpad.md +1 -0
  17. package/.claude/resources/core/wave-execution.md +329 -0
  18. package/.claude/resources/patterns/plans-patterns.md +56 -0
  19. package/.claude/resources/patterns/plans-templates.md +152 -0
  20. package/.claude/resources/skills/_index.md +8 -6
  21. package/.claude/resources/skills/create-plan-skill.md +71 -5
  22. package/.claude/resources/skills/execute-plan-skill.md +357 -12
  23. package/.claude/resources/skills/resume-work-skill.md +159 -0
  24. package/.claude/rules/core/forbidden-patterns.md +38 -0
  25. package/dist/cli/commands/init.js +1 -1
  26. package/dist/cli/commands/init.js.map +1 -1
  27. package/dist/cli/commands/state.d.ts +12 -0
  28. package/dist/cli/commands/state.d.ts.map +1 -0
  29. package/dist/cli/commands/state.js +47 -0
  30. package/dist/cli/commands/state.js.map +1 -0
  31. package/dist/cli/daemon/desktop-notifier.d.ts +16 -0
  32. package/dist/cli/daemon/desktop-notifier.d.ts.map +1 -0
  33. package/dist/cli/daemon/desktop-notifier.js +53 -0
  34. package/dist/cli/daemon/desktop-notifier.js.map +1 -0
  35. package/dist/cli/daemon/event-writer.d.ts +22 -0
  36. package/dist/cli/daemon/event-writer.d.ts.map +1 -0
  37. package/dist/cli/daemon/event-writer.js +76 -0
  38. package/dist/cli/daemon/event-writer.js.map +1 -0
  39. package/dist/cli/daemon/heartbeat-daemon.js +81 -1
  40. package/dist/cli/daemon/heartbeat-daemon.js.map +1 -1
  41. package/dist/cli/daemon/log-writer.d.ts +17 -0
  42. package/dist/cli/daemon/log-writer.d.ts.map +1 -0
  43. package/dist/cli/daemon/log-writer.js +62 -0
  44. package/dist/cli/daemon/log-writer.js.map +1 -0
  45. package/dist/cli/daemon/notification-router.d.ts +17 -0
  46. package/dist/cli/daemon/notification-router.d.ts.map +1 -0
  47. package/dist/cli/daemon/notification-router.js +35 -0
  48. package/dist/cli/daemon/notification-router.js.map +1 -0
  49. package/dist/cli/daemon/prompt-manager.d.ts +27 -0
  50. package/dist/cli/daemon/prompt-manager.d.ts.map +1 -0
  51. package/dist/cli/daemon/prompt-manager.js +107 -0
  52. package/dist/cli/daemon/prompt-manager.js.map +1 -0
  53. package/dist/cli/index.js +9 -0
  54. package/dist/cli/index.js.map +1 -1
  55. package/dist/cli/state/flowconfig-parser.d.ts +16 -0
  56. package/dist/cli/state/flowconfig-parser.d.ts.map +1 -0
  57. package/dist/cli/state/flowconfig-parser.js +166 -0
  58. package/dist/cli/state/flowconfig-parser.js.map +1 -0
  59. package/dist/cli/state/heartbeat-state.d.ts +16 -0
  60. package/dist/cli/state/heartbeat-state.d.ts.map +1 -0
  61. package/dist/cli/state/heartbeat-state.js +97 -0
  62. package/dist/cli/state/heartbeat-state.js.map +1 -0
  63. package/dist/cli/state/model-router.d.ts +8 -0
  64. package/dist/cli/state/model-router.d.ts.map +1 -0
  65. package/dist/cli/state/model-router.js +36 -0
  66. package/dist/cli/state/model-router.js.map +1 -0
  67. package/dist/cli/state/plan-parser.d.ts +16 -0
  68. package/dist/cli/state/plan-parser.d.ts.map +1 -0
  69. package/dist/cli/state/plan-parser.js +124 -0
  70. package/dist/cli/state/plan-parser.js.map +1 -0
  71. package/dist/cli/state/session-state.d.ts +21 -0
  72. package/dist/cli/state/session-state.d.ts.map +1 -0
  73. package/dist/cli/state/session-state.js +36 -0
  74. package/dist/cli/state/session-state.js.map +1 -0
  75. package/dist/cli/state/state-md-parser.d.ts +18 -0
  76. package/dist/cli/state/state-md-parser.d.ts.map +1 -0
  77. package/dist/cli/state/state-md-parser.js +222 -0
  78. package/dist/cli/state/state-md-parser.js.map +1 -0
  79. package/dist/cli/state/types.d.ts +106 -0
  80. package/dist/cli/state/types.d.ts.map +1 -0
  81. package/dist/cli/state/types.js +8 -0
  82. package/dist/cli/state/types.js.map +1 -0
  83. package/dist/cli/state/wave-calculator.d.ts +18 -0
  84. package/dist/cli/state/wave-calculator.d.ts.map +1 -0
  85. package/dist/cli/state/wave-calculator.js +134 -0
  86. package/dist/cli/state/wave-calculator.js.map +1 -0
  87. package/dist/cli/types.d.ts +15 -0
  88. package/dist/cli/types.d.ts.map +1 -1
  89. package/package.json +4 -2
  90. package/templates/shared/CLAUDE.md.template +4 -0
@@ -0,0 +1,159 @@
1
+
2
+ # Resume Work Skill
3
+
4
+ ## Purpose
5
+
6
+ Reconstruct full session context from `flow/STATE.md` after a context reset (compaction, new session, crash). This skill reads the unified state file, identifies the active work, cross-references with the plan file, and outputs a structured summary that enables the LLM to resume work seamlessly.
7
+
8
+ **This skill is read-only** — it does not create, modify, or delete any files.
9
+
10
+ ---
11
+
12
+ ## Restrictions — READ-ONLY
13
+
14
+ | Allowed Action | Purpose |
15
+ |----------------|---------|
16
+ | Read `flow/STATE.md` | Parse execution state |
17
+ | Read plan files (`flow/plans/`) | Cross-reference progress |
18
+ | Read discovery files (`flow/discovery/`) | Context for active discovery |
19
+ | Read `flow/.scratchpad.md` | Restore ephemeral notes |
20
+ | Read `flow/tasklist.md` | Check task status |
21
+
22
+ | Forbidden Action | Reason |
23
+ |------------------|--------|
24
+ | Create/edit any files | Resume is observation-only |
25
+ | Run build or test commands | No execution during resume |
26
+ | Auto-invoke other skills | User decides next action |
27
+ | Modify STATE.md | State persists until actively changed by a skill |
28
+
29
+ ---
30
+
31
+ ## Workflow
32
+
33
+ ### Step 1: Read STATE.md
34
+
35
+ 1. Check if `flow/STATE.md` exists — if not, inform user and stop
36
+ 2. Read the file and parse all sections:
37
+ - `**Updated**` timestamp
38
+ - `## Execution State` — active skill, plan, phase, task, completed phases
39
+ - `## Decisions` — decisions with rationale
40
+ - `## Blockers` — issues with status
41
+ - `## Files Modified` — changed file paths
42
+ - `## Next Action` — immediate next step
43
+
44
+ ---
45
+
46
+ ### Step 2: Staleness Check
47
+
48
+ Parse the `**Updated**` timestamp and compare to current time.
49
+
50
+ | Age | Behavior |
51
+ |-----|----------|
52
+ | < 24 hours | Continue silently |
53
+ | 24-72 hours | Warn: "State is {N} hours old. Context may be outdated." Ask: resume or start fresh? |
54
+ | > 72 hours | Strong warning: "State is {N} days old. Recommend starting fresh." Ask: resume or start fresh? |
55
+
56
+ If user chooses "start fresh":
57
+ 1. Inform them to delete `flow/STATE.md` manually
58
+ 2. Stop — do not proceed with resume
59
+
60
+ ---
61
+
62
+ ### Step 3: Read Active Plan
63
+
64
+ If `Active Plan` references a plan file:
65
+
66
+ 1. Read the plan file from `flow/plans/`
67
+ 2. Parse phase structure and task checkboxes
68
+ 3. Cross-reference:
69
+ - STATE.md `Completed Phases` → plan's `[x]` tasks should match
70
+ - STATE.md `Current Phase` → identify exact task position
71
+ 4. If discrepancies found, note them and trust the plan file
72
+
73
+ If `Active Plan` is "none", skip this step.
74
+
75
+ ---
76
+
77
+ ### Step 4: Identify Next Task
78
+
79
+ Based on STATE.md and plan cross-reference:
80
+
81
+ 1. If `Next Action` is set → use it as the recommended next step
82
+ 2. If `Current Task` is set → resume from that task
83
+ 3. If `Current Phase` is set but no task → start at the first unchecked task in that phase
84
+ 4. If no current phase → suggest continuing from the first unchecked phase
85
+
86
+ ---
87
+
88
+ ### Step 5: Output Context Summary
89
+
90
+ Present a structured summary:
91
+
92
+ ```markdown
93
+ ## Session Resumed
94
+
95
+ **Active Skill**: {skill name}
96
+ **Active Plan**: `{plan file path}`
97
+ **Progress**: Phase {current} of {total} — {phase name}
98
+ **Last Updated**: {timestamp} ({relative time})
99
+
100
+ ### Completed Phases
101
+ | Phase | Name | Outcome |
102
+ |-------|------|---------|
103
+ | 1 | Types and Schemas | done |
104
+ | 2 | Backend Setup | done |
105
+
106
+ ### Decisions Made
107
+ - {decision} — {choice} (reason: {reason})
108
+
109
+ ### Blockers
110
+ | Issue | Status | Tried |
111
+ |-------|--------|-------|
112
+ | {issue} | {status} | {tried} |
113
+
114
+ ### Files Modified ({count})
115
+ - `{file1}`
116
+ - `{file2}`
117
+
118
+ ### Next Action
119
+ > {next action description}
120
+
121
+ ---
122
+
123
+ Ready to continue. Suggested command: `/execute-plan @{plan-file}`
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Error Handling
129
+
130
+ | Scenario | Behavior |
131
+ |----------|----------|
132
+ | STATE.md missing | Inform user: "No active state found." Stop. |
133
+ | Plan file referenced but missing | Warn: "Plan file not found." Show state without plan cross-reference. |
134
+ | STATE.md malformed | Parse what's possible, warn about missing sections. |
135
+ | Active skill is unknown | Show state as-is, don't suggest a specific command. |
136
+ | Scratchpad exists | Mention: "Session scratchpad has notes — read `flow/.scratchpad.md` for context." |
137
+
138
+ ---
139
+
140
+ ## Rules
141
+
142
+ 1. **Read-only** — never modify any files during resume
143
+ 2. **No auto-chaining** — present summary and stop
144
+ 3. **Plan file is truth** — if STATE.md and plan disagree, trust the plan
145
+ 4. **Graceful degradation** — parse what's available, skip missing sections
146
+ 5. **Staleness gate** — always check timestamp before proceeding
147
+ 6. **Complement scratchpad** — mention scratchpad if it exists, but don't merge content
148
+
149
+ ---
150
+
151
+ ## Related Files
152
+
153
+ | File | Purpose |
154
+ |------|---------|
155
+ | `.claude/commands/resume-work.md` | Command file for /resume-work |
156
+ | `.claude/resources/core/compaction-guide.md` | What to preserve during compaction |
157
+ | `.claude/resources/core/session-scratchpad.md` | Complementary session notes |
158
+ | `src/cli/state/state-md-parser.ts` | Programmatic parser for STATE.md |
159
+ | `src/cli/state/types.ts` | ExecutionState type definition |
@@ -256,4 +256,42 @@ const badExample = problematicCode()
256
256
 
257
257
  ## Project Anti-Patterns
258
258
 
259
+ ### 6. DON'T Execute ORM/Database Migration Commands Directly
260
+
261
+ **Problem**: Running ORM tools (Prisma, Drizzle, TypeORM, Sequelize, Knex, Django ORM, Alembic, etc.) directly can cause irreversible data loss, schema corruption, or unintended migrations in production environments.
262
+
263
+ ```bash
264
+ # BAD - Never run these directly
265
+ npx prisma migrate dev
266
+ npx prisma db push
267
+ npx prisma db seed
268
+ npx drizzle-kit push
269
+ npx drizzle-kit migrate
270
+ npx typeorm migration:run
271
+ python manage.py migrate
272
+ alembic upgrade head
273
+ npx knex migrate:latest
274
+ ```
275
+
276
+ **Why This is Wrong**:
277
+
278
+ - Database migrations can be destructive and irreversible (dropping columns, tables, data)
279
+ - ORM commands may connect to production databases if environment is misconfigured
280
+ - Schema changes need human review before execution
281
+ - Seed commands can overwrite existing data
282
+ - The AI agent has no way to verify which database environment it's targeting
283
+
284
+ **Fix**: Always present the command to the user and ask them to execute it manually.
285
+
286
+ ```markdown
287
+ <!-- GOOD - Ask the user to run it -->
288
+ "Please run the following command to apply the migration:"
289
+
290
+ `npx prisma migrate dev --name add_users_table`
291
+
292
+ "Review the generated migration SQL before confirming."
293
+ ```
294
+
295
+ **Scope**: This applies to ALL ORM and database tools — Prisma, Drizzle, TypeORM, Sequelize, Knex, Django ORM, Alembic, SQLAlchemy, ActiveRecord, and any other database migration or seeding tool.
296
+
259
297
  <!-- auto-captured anti-patterns below this line -->
@@ -101,7 +101,7 @@ function printNextSteps(platforms) {
101
101
  log.blank();
102
102
  log.header('Heartbeat Daemon');
103
103
  log.info('The heartbeat daemon runs in the background to execute scheduled tasks.');
104
- log.info('Manage it with: npx plan-flow heartbeat start|stop|status');
104
+ log.info('Manage it with: npx planflow-ai heartbeat start|stop|status');
105
105
  log.blank();
106
106
  log.header('Brain & Obsidian Vault');
107
107
  log.info('Your project brain lives at flow/brain/ — features, errors, and decisions are tracked here.');
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,SAAS,WAAW;IAClB,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9B,GAAG,CAAC,IAAI,CACN,6GAA6G,CAC9G,CAAC;IACF,GAAG,CAAC,KAAK,EAAE,CAAC;IACZ,GAAG,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,eAAe,CAAC,OAAoB;IAC3C,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,IAAI,OAAO,CAAC,MAAM;QAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,MAAM;QAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,QAAQ;QAAE,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,OAAO,CAAC,OAAO;QAAE,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,OAAO,CAAC,KAAK;QAAE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,SAAS,cAAc,CAAC,MAAc;IACpC,sCAAsC;IACtC,MAAM,UAAU,GAAG;QACjB,cAAc;QACd,gBAAgB;QAChB,YAAY;QACZ,QAAQ;QACR,SAAS;QACT,MAAM;KACP,CAAC;IAEF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAErE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CACN,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC,CAAC,8BAA8B;AAC7C,CAAC;AAED,SAAS,YAAY,CAAC,OAAqB,EAAE,MAAc;IACzD,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAEtB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAEtC,YAAY,IAAI,OAAO,CAAC;QACxB,YAAY,IAAI,OAAO,CAAC;QACxB,YAAY,IAAI,OAAO,CAAC;QAExB,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,GAAG,CAAC,OAAO,CAAC,GAAG,QAAQ,KAAK,OAAO,kBAAkB,CAAC,CAAC;QACzD,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,GAAG,CAAC,IAAI,CAAC,GAAG,QAAQ,KAAK,OAAO,kBAAkB,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,GAAG,CAAC,IAAI,CAAC,GAAG,QAAQ,KAAK,OAAO,kCAAkC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,GAAG,CAAC,KAAK,EAAE,CAAC;IAEZ,IAAI,YAAY,GAAG,YAAY,GAAG,CAAC,EAAE,CAAC;QACpC,GAAG,CAAC,OAAO,CACT,SAAS,YAAY,aAAa,YAAY,aAAa,YAAY,WAAW,CACnF,CAAC;IACJ,CAAC;SAAM,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAC5B,GAAG,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,SAAqB;IAC3C,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAEzB,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,CACN,gFAAgF,CACjF,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,GAAG,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;IACtF,CAAC;IAED,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,GAAG,CAAC,IAAI,CAAC,uFAAuF,CAAC,CAAC;IACpG,CAAC;IAED,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CACN,qFAAqF,CACtF,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,KAAK,EAAE,CAAC;IACZ,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAC/B,GAAG,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;IACpF,GAAG,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IACtE,GAAG,CAAC,KAAK,EAAE,CAAC;IACZ,GAAG,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACrC,GAAG,CAAC,IAAI,CAAC,6FAA6F,CAAC,CAAC;IACxG,GAAG,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAC;IAC/E,GAAG,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC;IAC9F,GAAG,CAAC,KAAK,EAAE,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAoB;IAChD,WAAW,EAAE,CAAC;IAEd,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IAErC,cAAc,CAAC,MAAM,CAAC,CAAC;IAEvB,sDAAsD;IACtD,IAAI,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,CAAC;IAED,GAAG,CAAC,KAAK,EAAE,CAAC;IACZ,GAAG,CAAC,IAAI,CAAC,mBAAmB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpD,GAAG,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC;IAC9B,IAAI,KAAK,EAAE,CAAC;QACV,GAAG,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,iCAAiC;IACjC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,GAAG,CAAC,MAAM,CAAC,cAAc,QAAQ,KAAK,CAAC,CAAC;QAExC,IAAI,MAAM,CAAC;QACX,QAAQ,QAAQ,EAAE,CAAC;YACjB,KAAK,QAAQ;gBACX,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC7C,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC7C,MAAM;YACR,KAAK,UAAU;gBACb,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC/C,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC9C,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC5C,MAAM;QACV,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,oDAAoD;IACpD,GAAG,CAAC,MAAM,CAAC,gCAAgC,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAE3D,uCAAuC;IACvC,MAAM,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAErC,+BAA+B;IAC/B,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,cAAc,CAAC,SAAS,CAAC,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * State CLI command
3
+ *
4
+ * Outputs deterministic JSON representing current plan-flow state.
5
+ * Composes all parsers: config, session, heartbeat (always),
6
+ * and plan/waves/tiers (when --plan is provided).
7
+ */
8
+ export declare function runState(options: {
9
+ plan?: string;
10
+ target: string;
11
+ }): Promise<void>;
12
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,wBAAsB,QAAQ,CAAC,OAAO,EAAE;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * State CLI command
3
+ *
4
+ * Outputs deterministic JSON representing current plan-flow state.
5
+ * Composes all parsers: config, session, heartbeat (always),
6
+ * and plan/waves/tiers (when --plan is provided).
7
+ */
8
+ import { join } from 'node:path';
9
+ import { parseFlowConfig } from '../state/flowconfig-parser.js';
10
+ import { getSessionState } from '../state/session-state.js';
11
+ import { getHeartbeatSummary } from '../state/heartbeat-state.js';
12
+ import { parseStateMd } from '../state/state-md-parser.js';
13
+ import { parsePlan } from '../state/plan-parser.js';
14
+ import { calculateWaves } from '../state/wave-calculator.js';
15
+ import { calculateModelTiers } from '../state/model-router.js';
16
+ export async function runState(options) {
17
+ try {
18
+ const flowDir = join(options.target, 'flow');
19
+ const config = parseFlowConfig(flowDir);
20
+ const session = getSessionState(flowDir);
21
+ const heartbeat = getHeartbeatSummary(flowDir);
22
+ const execution = parseStateMd(flowDir);
23
+ const output = {
24
+ config,
25
+ session,
26
+ heartbeat,
27
+ ...(execution ? { execution } : {}),
28
+ };
29
+ if (options.plan) {
30
+ const phases = parsePlan(options.plan);
31
+ const waves = calculateWaves(phases);
32
+ const model_tiers = calculateModelTiers(phases, config.model_routing);
33
+ output.plan = {
34
+ phases,
35
+ waves,
36
+ model_tiers,
37
+ };
38
+ }
39
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n');
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ process.stdout.write(JSON.stringify({ error: message }, null, 2) + '\n');
44
+ process.exitCode = 1;
45
+ }
46
+ }
47
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/cli/commands/state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG/D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAG9B;IACC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAE/C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAExC,MAAM,MAAM,GAAgB;YAC1B,MAAM;YACN,OAAO;YACP,SAAS;YACT,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CAAC;QAEF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,WAAW,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;YAEtE,MAAM,CAAC,IAAI,GAAG;gBACZ,MAAM;gBACN,KAAK;gBACL,WAAW;aACZ,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACzE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Desktop notifier — thin wrapper around node-notifier.
3
+ * Fire-and-forget: never throws, never blocks.
4
+ * Degrades silently on headless servers or when node-notifier is unavailable.
5
+ */
6
+ import type { NotificationEvent } from '../types.js';
7
+ /**
8
+ * Send a desktop notification for the given event.
9
+ *
10
+ * - Title: "Plan-Flow Heartbeat"
11
+ * - Message: "{task}: {message}"
12
+ * - Fire-and-forget — does not await the OS notification result
13
+ * - Catches ALL errors; logs to console.error but never throws
14
+ */
15
+ export declare function sendDesktopNotification(event: NotificationEvent): void;
16
+ //# sourceMappingURL=desktop-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop-notifier.d.ts","sourceRoot":"","sources":["../../../src/cli/daemon/desktop-notifier.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA6BrD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAetE"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Desktop notifier — thin wrapper around node-notifier.
3
+ * Fire-and-forget: never throws, never blocks.
4
+ * Degrades silently on headless servers or when node-notifier is unavailable.
5
+ */
6
+ const NOTIFICATION_TITLE = 'Plan-Flow Heartbeat';
7
+ /**
8
+ * Lazily resolved notifier instance.
9
+ * `null` means the import failed (headless server, missing native deps, etc.)
10
+ */
11
+ let cachedNotifier;
12
+ async function getNotifier() {
13
+ if (cachedNotifier !== undefined)
14
+ return cachedNotifier;
15
+ try {
16
+ // Dynamic import — may fail on headless systems
17
+ const mod = await import('node-notifier');
18
+ // node-notifier exports its instance as both default and named
19
+ cachedNotifier = (mod.default ?? mod);
20
+ return cachedNotifier;
21
+ }
22
+ catch {
23
+ // node-notifier unavailable — degrade silently
24
+ cachedNotifier = null;
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Send a desktop notification for the given event.
30
+ *
31
+ * - Title: "Plan-Flow Heartbeat"
32
+ * - Message: "{task}: {message}"
33
+ * - Fire-and-forget — does not await the OS notification result
34
+ * - Catches ALL errors; logs to console.error but never throws
35
+ */
36
+ export function sendDesktopNotification(event) {
37
+ // Intentionally not awaited — fire-and-forget
38
+ void (async () => {
39
+ try {
40
+ const notifier = await getNotifier();
41
+ if (!notifier)
42
+ return;
43
+ notifier.notify({
44
+ title: NOTIFICATION_TITLE,
45
+ message: `${event.task}: ${event.message}`,
46
+ });
47
+ }
48
+ catch (error) {
49
+ console.error('Desktop notification failed (non-fatal):', error);
50
+ }
51
+ })();
52
+ }
53
+ //# sourceMappingURL=desktop-notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop-notifier.js","sourceRoot":"","sources":["../../../src/cli/daemon/desktop-notifier.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,kBAAkB,GAAG,qBAAqB,CAAC;AAKjD;;;GAGG;AACH,IAAI,cAA+C,CAAC;AAEpD,KAAK,UAAU,WAAW;IACxB,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC;IAExD,IAAI,CAAC;QACH,gDAAgD;QAChD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC1C,+DAA+D;QAC/D,cAAc,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAiB,CAAC;QACtD,OAAO,cAAc,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;QAC/C,cAAc,GAAG,IAAI,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAwB;IAC9D,8CAA8C;IAC9C,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,QAAQ;gBAAE,OAAO;YAEtB,QAAQ,CAAC,MAAM,CAAC;gBACd,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAC;QACnE,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Event writer — appends NotificationEvent records to a JSONL file.
3
+ * Uses atomic write (write-to-temp then rename) to prevent corruption.
4
+ */
5
+ import type { NotificationEvent, NotificationLevel, NotificationType } from '../types.js';
6
+ /**
7
+ * Create a NotificationEvent with an auto-generated id and current timestamp.
8
+ */
9
+ export declare function createEvent(task: string, type: NotificationType, level: NotificationLevel, message: string, phase?: string): NotificationEvent;
10
+ /**
11
+ * Append a single event to the JSONL file using atomic write.
12
+ *
13
+ * Reads the existing file (if any), appends the new JSON line,
14
+ * writes to a temp file, then renames over the original.
15
+ */
16
+ export declare function appendEvent(flowDir: string, event: NotificationEvent): Promise<void>;
17
+ /**
18
+ * Read all events from the JSONL file. Returns an empty array if the file
19
+ * does not exist.
20
+ */
21
+ export declare function readEvents(flowDir: string): Promise<NotificationEvent[]>;
22
+ //# sourceMappingURL=event-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-writer.d.ts","sourceRoot":"","sources":["../../../src/cli/daemon/event-writer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAW1F;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,iBAAiB,CAUnB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiB9E"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Event writer — appends NotificationEvent records to a JSONL file.
3
+ * Uses atomic write (write-to-temp then rename) to prevent corruption.
4
+ */
5
+ import { readFile, writeFile, rename, mkdir } from 'node:fs/promises';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { dirname, join } from 'node:path';
8
+ const EVENTS_FILENAME = '.heartbeat-events.jsonl';
9
+ /**
10
+ * Build the full path to the events file for a given flow directory.
11
+ */
12
+ function eventsPath(flowDir) {
13
+ return join(flowDir, EVENTS_FILENAME);
14
+ }
15
+ /**
16
+ * Create a NotificationEvent with an auto-generated id and current timestamp.
17
+ */
18
+ export function createEvent(task, type, level, message, phase) {
19
+ return {
20
+ id: randomUUID(),
21
+ timestamp: new Date(),
22
+ task,
23
+ type,
24
+ level,
25
+ phase,
26
+ message,
27
+ };
28
+ }
29
+ /**
30
+ * Append a single event to the JSONL file using atomic write.
31
+ *
32
+ * Reads the existing file (if any), appends the new JSON line,
33
+ * writes to a temp file, then renames over the original.
34
+ */
35
+ export async function appendEvent(flowDir, event) {
36
+ const filePath = eventsPath(flowDir);
37
+ const dir = dirname(filePath);
38
+ await mkdir(dir, { recursive: true });
39
+ let existing = '';
40
+ try {
41
+ existing = await readFile(filePath, 'utf-8');
42
+ }
43
+ catch {
44
+ // File does not exist yet — start fresh
45
+ }
46
+ const line = JSON.stringify({
47
+ ...event,
48
+ timestamp: event.timestamp instanceof Date ? event.timestamp.toISOString() : event.timestamp,
49
+ });
50
+ const updated = existing ? `${existing.trimEnd()}\n${line}\n` : `${line}\n`;
51
+ const tempPath = `${filePath}.tmp`;
52
+ await writeFile(tempPath, updated, 'utf-8');
53
+ await rename(tempPath, filePath);
54
+ }
55
+ /**
56
+ * Read all events from the JSONL file. Returns an empty array if the file
57
+ * does not exist.
58
+ */
59
+ export async function readEvents(flowDir) {
60
+ const filePath = eventsPath(flowDir);
61
+ let content;
62
+ try {
63
+ content = await readFile(filePath, 'utf-8');
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ return content
69
+ .split('\n')
70
+ .filter((line) => line.trim().length > 0)
71
+ .map((line) => {
72
+ const parsed = JSON.parse(line);
73
+ return { ...parsed, timestamp: new Date(parsed.timestamp) };
74
+ });
75
+ }
76
+ //# sourceMappingURL=event-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-writer.js","sourceRoot":"","sources":["../../../src/cli/daemon/event-writer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,eAAe,GAAG,yBAAyB,CAAC;AAElD;;GAEG;AACH,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,IAAsB,EACtB,KAAwB,EACxB,OAAe,EACf,KAAc;IAEd,OAAO;QACL,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE;QACrB,IAAI;QACJ,IAAI;QACJ,KAAK;QACL,KAAK;QACL,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe,EACf,KAAwB;IAExB,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE9B,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtC,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,GAAG,KAAK;QACR,SAAS,EAAE,KAAK,CAAC,SAAS,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS;KAC7F,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;IAE5E,MAAM,QAAQ,GAAG,GAAG,QAAQ,MAAM,CAAC;IACnC,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe;IAC9C,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAErC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,OAAO;SACX,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -8,7 +8,10 @@
8
8
  import { readFileSync, writeFileSync, existsSync, unlinkSync, watch, appendFileSync, statSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
  import { spawn } from 'node:child_process';
11
+ import { randomUUID } from 'node:crypto';
11
12
  import { parseHeartbeatFile } from './heartbeat-parser.js';
13
+ import { notify } from './notification-router.js';
14
+ import { writePrompt } from './prompt-manager.js';
12
15
  const target = process.argv[2] || process.cwd();
13
16
  const heartbeatPath = join(target, 'flow', 'heartbeat.md');
14
17
  const pidPath = join(target, 'flow', '.heartbeat.pid');
@@ -18,7 +21,47 @@ let taskRunning = false;
18
21
  const ACTIVE_SESSION_ERROR = 'cannot be launched inside another Claude Code session';
19
22
  const MAX_RETRIES = 5;
20
23
  const RETRY_DELAY_MS = 60_000;
24
+ const TAIL_LINES = 20;
21
25
  const retryCountMap = new Map();
26
+ const flowDir = join(target, 'flow');
27
+ /**
28
+ * Create a NotificationEvent with consistent structure.
29
+ */
30
+ function createEvent(task, type, level, message, phase) {
31
+ return {
32
+ id: randomUUID(),
33
+ timestamp: new Date(),
34
+ task: task.name,
35
+ type,
36
+ level,
37
+ message,
38
+ ...(phase ? { phase } : {}),
39
+ };
40
+ }
41
+ /**
42
+ * Return the last N lines from a string.
43
+ */
44
+ function tailLines(text, n) {
45
+ const lines = text.split('\n');
46
+ return lines.slice(-n).join('\n');
47
+ }
48
+ /**
49
+ * Read the autopilot setting from flow/.flowconfig.
50
+ * Returns true if autopilot is enabled, false otherwise.
51
+ */
52
+ function readAutopilot() {
53
+ try {
54
+ const configPath = join(flowDir, '.flowconfig');
55
+ if (!existsSync(configPath))
56
+ return false;
57
+ const content = readFileSync(configPath, 'utf-8');
58
+ const match = content.match(/^autopilot:\s*(true|false)/m);
59
+ return match?.[1] === 'true';
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
22
65
  function log(message) {
23
66
  const timestamp = new Date().toISOString();
24
67
  const line = `[${timestamp}] ${message}\n`;
@@ -86,6 +129,10 @@ function executeTask(task) {
86
129
  }
87
130
  taskRunning = true;
88
131
  log(`Executing "${task.name}": ${task.command}`);
132
+ // Snapshot prompt file state before task runs
133
+ const promptExistedBefore = existsSync(join(flowDir, '.heartbeat-prompt.md'));
134
+ // Emit task_started notification (fire-and-forget)
135
+ void notify(createEvent(task, 'task_started', 'info', `Starting task: ${task.name}`), flowDir);
89
136
  // Strip CLAUDECODE env var to avoid "nested session" detection.
90
137
  // The daemon runs detached — it's not actually nested.
91
138
  const cleanEnv = { ...process.env };
@@ -118,13 +165,42 @@ function executeTask(task) {
118
165
  });
119
166
  child.on('close', (code) => {
120
167
  taskRunning = false;
168
+ const outputTail = tailLines(stdout, TAIL_LINES);
169
+ const errorTail = tailLines(stderr, TAIL_LINES);
121
170
  if (code === 0) {
122
- log(`Task "${task.name}" completed successfully`);
123
171
  retryCountMap.delete(task.name);
172
+ // Check if the task created a NEW prompt file (blocked via file, not exit code)
173
+ const promptPath = join(flowDir, '.heartbeat-prompt.md');
174
+ if (!promptExistedBefore && existsSync(promptPath)) {
175
+ log(`Task "${task.name}" completed but created prompt file — treating as blocked`);
176
+ void notify(createEvent(task, 'task_blocked', 'error', `Task needs input: ${task.name}`), flowDir);
177
+ }
178
+ else {
179
+ log(`Task "${task.name}" completed successfully`);
180
+ // Emit task_complete notification (fire-and-forget)
181
+ void notify(createEvent(task, 'task_complete', 'info', `Task completed: ${task.name}`), flowDir);
182
+ }
124
183
  if (task.oneShot) {
125
184
  disableOneShotTask(task.name);
126
185
  }
127
186
  }
187
+ else if (code === 2) {
188
+ // Exit code 2 — task needs human input
189
+ log(`Task "${task.name}" blocked — needs input (exit code 2)`);
190
+ if (stderr)
191
+ log(` stderr: ${stderr.slice(0, 500)}`);
192
+ const isAutopilot = readAutopilot();
193
+ // Emit task_blocked notification (fire-and-forget)
194
+ void notify(createEvent(task, 'task_blocked', 'error', `Task blocked (needs input): ${task.name}${isAutopilot ? ' [autopilot ON]' : ''}`), flowDir);
195
+ if (!isAutopilot) {
196
+ void writePrompt(task.name, outputTail, errorTail, flowDir);
197
+ log(` prompt written to flow/${'.heartbeat-prompt.md'} — waiting for human input`);
198
+ }
199
+ else {
200
+ log(` Autopilot ON — skipping prompt, continuing`);
201
+ }
202
+ log(` context (last ${TAIL_LINES} lines): ${outputTail.slice(0, 500)}`);
203
+ }
128
204
  else if (stderr.includes(ACTIVE_SESSION_ERROR)) {
129
205
  scheduleRetry(task);
130
206
  }
@@ -132,6 +208,8 @@ function executeTask(task) {
132
208
  log(`Task "${task.name}" failed with code ${code}`);
133
209
  if (stderr)
134
210
  log(` stderr: ${stderr.slice(0, 500)}`);
211
+ // Emit task_failed notification (fire-and-forget)
212
+ void notify(createEvent(task, 'task_failed', 'error', `Task failed (exit ${code}): ${task.name}${errorTail ? '\n' + errorTail.slice(0, 300) : ''}`), flowDir);
135
213
  }
136
214
  if (stdout)
137
215
  log(` output: ${stdout.slice(0, 500)}`);
@@ -139,6 +217,8 @@ function executeTask(task) {
139
217
  child.on('error', (err) => {
140
218
  taskRunning = false;
141
219
  log(`Task "${task.name}" error: ${err.message}`);
220
+ // Emit task_failed notification for spawn errors (fire-and-forget)
221
+ void notify(createEvent(task, 'task_failed', 'error', `Task spawn error: ${task.name} — ${err.message}`), flowDir);
142
222
  });
143
223
  }
144
224
  function scheduleRetry(task) {