agileflow 3.3.0 → 3.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 (210) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/skill-loader.js +0 -1
  4. package/package.json +1 -1
  5. package/scripts/agileflow-statusline.sh +81 -0
  6. package/scripts/agileflow-welcome.js +79 -0
  7. package/scripts/claude-tmux.sh +90 -23
  8. package/scripts/claude-watchdog.sh +225 -0
  9. package/scripts/generators/agent-registry.js +14 -1
  10. package/scripts/generators/inject-babysit.js +22 -9
  11. package/scripts/generators/inject-help.js +19 -9
  12. package/scripts/lib/ac-test-matcher.js +452 -0
  13. package/scripts/lib/audit-cleanup.js +250 -0
  14. package/scripts/lib/audit-registry.js +304 -0
  15. package/scripts/lib/configure-features.js +35 -0
  16. package/scripts/lib/feature-catalog.js +3 -3
  17. package/scripts/lib/gate-enforcer.js +295 -0
  18. package/scripts/lib/model-profiles.js +118 -0
  19. package/scripts/lib/quality-gates.js +163 -0
  20. package/scripts/lib/signal-detectors.js +44 -1
  21. package/scripts/lib/skill-catalog.js +557 -0
  22. package/scripts/lib/skill-recommender.js +311 -0
  23. package/scripts/lib/status-writer.js +255 -0
  24. package/scripts/lib/story-claiming.js +128 -45
  25. package/scripts/lib/task-sync.js +32 -38
  26. package/scripts/lib/tdd-phase-manager.js +455 -0
  27. package/scripts/lib/team-events.js +34 -3
  28. package/scripts/lib/tmux-audit-monitor.js +611 -0
  29. package/scripts/lib/tmux-group-colors.js +113 -0
  30. package/scripts/lib/tool-registry.yaml +241 -0
  31. package/scripts/lib/tool-shed.js +441 -0
  32. package/scripts/messaging-bridge.js +209 -1
  33. package/scripts/native-team-observer.js +219 -0
  34. package/scripts/obtain-context.js +14 -0
  35. package/scripts/ralph-loop.js +30 -5
  36. package/scripts/smart-detect.js +21 -0
  37. package/scripts/spawn-audit-sessions.js +877 -0
  38. package/scripts/team-manager.js +56 -16
  39. package/scripts/tmux-close-windows.sh +180 -0
  40. package/src/core/agents/a11y-analyzer-aria.md +155 -0
  41. package/src/core/agents/a11y-analyzer-forms.md +162 -0
  42. package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
  43. package/src/core/agents/a11y-analyzer-semantic.md +153 -0
  44. package/src/core/agents/a11y-analyzer-visual.md +158 -0
  45. package/src/core/agents/a11y-consensus.md +248 -0
  46. package/src/core/agents/ads-audit-budget.md +181 -0
  47. package/src/core/agents/ads-audit-compliance.md +169 -0
  48. package/src/core/agents/ads-audit-creative.md +164 -0
  49. package/src/core/agents/ads-audit-google.md +226 -0
  50. package/src/core/agents/ads-audit-meta.md +183 -0
  51. package/src/core/agents/ads-audit-tracking.md +197 -0
  52. package/src/core/agents/ads-consensus.md +396 -0
  53. package/src/core/agents/ads-generate.md +145 -0
  54. package/src/core/agents/ads-performance-tracker.md +197 -0
  55. package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
  56. package/src/core/agents/api-quality-analyzer-docs.md +176 -0
  57. package/src/core/agents/api-quality-analyzer-errors.md +183 -0
  58. package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
  59. package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
  60. package/src/core/agents/api-quality-consensus.md +214 -0
  61. package/src/core/agents/arch-analyzer-circular.md +148 -0
  62. package/src/core/agents/arch-analyzer-complexity.md +171 -0
  63. package/src/core/agents/arch-analyzer-coupling.md +146 -0
  64. package/src/core/agents/arch-analyzer-layering.md +151 -0
  65. package/src/core/agents/arch-analyzer-patterns.md +162 -0
  66. package/src/core/agents/arch-consensus.md +227 -0
  67. package/src/core/agents/brainstorm-analyzer-features.md +169 -0
  68. package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
  69. package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
  70. package/src/core/agents/brainstorm-analyzer-market.md +147 -0
  71. package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
  72. package/src/core/agents/brainstorm-consensus.md +237 -0
  73. package/src/core/agents/completeness-consensus.md +5 -5
  74. package/src/core/agents/perf-consensus.md +2 -2
  75. package/src/core/agents/security-consensus.md +2 -2
  76. package/src/core/agents/seo-analyzer-content.md +167 -0
  77. package/src/core/agents/seo-analyzer-images.md +187 -0
  78. package/src/core/agents/seo-analyzer-performance.md +206 -0
  79. package/src/core/agents/seo-analyzer-schema.md +176 -0
  80. package/src/core/agents/seo-analyzer-sitemap.md +172 -0
  81. package/src/core/agents/seo-analyzer-technical.md +144 -0
  82. package/src/core/agents/seo-consensus.md +289 -0
  83. package/src/core/agents/test-consensus.md +2 -2
  84. package/src/core/commands/adr.md +1 -0
  85. package/src/core/commands/ads/audit.md +375 -0
  86. package/src/core/commands/ads/budget.md +97 -0
  87. package/src/core/commands/ads/competitor.md +112 -0
  88. package/src/core/commands/ads/creative.md +85 -0
  89. package/src/core/commands/ads/generate.md +238 -0
  90. package/src/core/commands/ads/google.md +112 -0
  91. package/src/core/commands/ads/health.md +327 -0
  92. package/src/core/commands/ads/landing.md +119 -0
  93. package/src/core/commands/ads/linkedin.md +112 -0
  94. package/src/core/commands/ads/meta.md +91 -0
  95. package/src/core/commands/ads/microsoft.md +115 -0
  96. package/src/core/commands/ads/plan.md +321 -0
  97. package/src/core/commands/ads/test-plan.md +317 -0
  98. package/src/core/commands/ads/tiktok.md +129 -0
  99. package/src/core/commands/ads/track.md +288 -0
  100. package/src/core/commands/ads/youtube.md +124 -0
  101. package/src/core/commands/ads.md +140 -0
  102. package/src/core/commands/assign.md +1 -0
  103. package/src/core/commands/audit.md +43 -6
  104. package/src/core/commands/babysit.md +315 -1266
  105. package/src/core/commands/baseline.md +1 -0
  106. package/src/core/commands/blockers.md +1 -0
  107. package/src/core/commands/board.md +1 -0
  108. package/src/core/commands/changelog.md +1 -0
  109. package/src/core/commands/choose.md +1 -0
  110. package/src/core/commands/ci.md +1 -0
  111. package/src/core/commands/code/accessibility.md +347 -0
  112. package/src/core/commands/code/api.md +297 -0
  113. package/src/core/commands/code/architecture.md +297 -0
  114. package/src/core/commands/{audit → code}/completeness.md +72 -25
  115. package/src/core/commands/{audit → code}/legal.md +63 -16
  116. package/src/core/commands/{audit → code}/logic.md +64 -16
  117. package/src/core/commands/{audit → code}/performance.md +67 -20
  118. package/src/core/commands/{audit → code}/security.md +69 -19
  119. package/src/core/commands/{audit → code}/test.md +67 -20
  120. package/src/core/commands/configure.md +1 -0
  121. package/src/core/commands/council.md +1 -0
  122. package/src/core/commands/deploy.md +1 -0
  123. package/src/core/commands/diagnose.md +1 -0
  124. package/src/core/commands/docs.md +1 -0
  125. package/src/core/commands/epic/edit.md +213 -0
  126. package/src/core/commands/epic.md +1 -0
  127. package/src/core/commands/export.md +238 -0
  128. package/src/core/commands/help.md +16 -1
  129. package/src/core/commands/{discovery → ideate}/brief.md +12 -12
  130. package/src/core/commands/{discovery/new.md → ideate/discover.md} +20 -16
  131. package/src/core/commands/ideate/features.md +496 -0
  132. package/src/core/commands/ideate/new.md +158 -124
  133. package/src/core/commands/impact.md +1 -0
  134. package/src/core/commands/learn/explain.md +118 -0
  135. package/src/core/commands/learn/glossary.md +135 -0
  136. package/src/core/commands/learn/patterns.md +138 -0
  137. package/src/core/commands/learn/tour.md +126 -0
  138. package/src/core/commands/migrate/codemods.md +151 -0
  139. package/src/core/commands/migrate/plan.md +131 -0
  140. package/src/core/commands/migrate/scan.md +114 -0
  141. package/src/core/commands/migrate/validate.md +119 -0
  142. package/src/core/commands/multi-expert.md +1 -0
  143. package/src/core/commands/pr.md +1 -0
  144. package/src/core/commands/review.md +1 -0
  145. package/src/core/commands/seo/audit.md +373 -0
  146. package/src/core/commands/seo/competitor.md +174 -0
  147. package/src/core/commands/seo/content.md +107 -0
  148. package/src/core/commands/seo/geo.md +229 -0
  149. package/src/core/commands/seo/hreflang.md +140 -0
  150. package/src/core/commands/seo/images.md +96 -0
  151. package/src/core/commands/seo/page.md +198 -0
  152. package/src/core/commands/seo/plan.md +163 -0
  153. package/src/core/commands/seo/programmatic.md +131 -0
  154. package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
  155. package/src/core/commands/seo/references/eeat-framework.md +110 -0
  156. package/src/core/commands/seo/references/quality-gates.md +91 -0
  157. package/src/core/commands/seo/references/schema-types.md +102 -0
  158. package/src/core/commands/seo/schema.md +183 -0
  159. package/src/core/commands/seo/sitemap.md +97 -0
  160. package/src/core/commands/seo/technical.md +100 -0
  161. package/src/core/commands/seo.md +107 -0
  162. package/src/core/commands/skill/list.md +68 -212
  163. package/src/core/commands/skill/recommend.md +216 -0
  164. package/src/core/commands/sprint.md +1 -0
  165. package/src/core/commands/status/undo.md +191 -0
  166. package/src/core/commands/status.md +1 -0
  167. package/src/core/commands/story/edit.md +204 -0
  168. package/src/core/commands/story/view.md +29 -7
  169. package/src/core/commands/story-validate.md +1 -0
  170. package/src/core/commands/story.md +1 -0
  171. package/src/core/commands/tdd-next.md +238 -0
  172. package/src/core/commands/tdd.md +211 -0
  173. package/src/core/commands/team/start.md +10 -6
  174. package/src/core/commands/tests.md +1 -0
  175. package/src/core/commands/verify.md +27 -1
  176. package/src/core/commands/workflow.md +2 -0
  177. package/src/core/experts/_core-expertise.yaml +105 -0
  178. package/src/core/experts/analytics/expertise.yaml +5 -99
  179. package/src/core/experts/codebase-query/expertise.yaml +3 -72
  180. package/src/core/experts/compliance/expertise.yaml +6 -72
  181. package/src/core/experts/database/expertise.yaml +9 -52
  182. package/src/core/experts/documentation/expertise.yaml +7 -140
  183. package/src/core/experts/integrations/expertise.yaml +7 -127
  184. package/src/core/experts/mentor/expertise.yaml +8 -35
  185. package/src/core/experts/monitoring/expertise.yaml +7 -49
  186. package/src/core/experts/performance/expertise.yaml +1 -26
  187. package/src/core/experts/security/expertise.yaml +9 -34
  188. package/src/core/experts/ui/expertise.yaml +6 -36
  189. package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
  190. package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
  191. package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
  192. package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
  193. package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
  194. package/src/core/teams/backend.json +41 -0
  195. package/src/core/teams/frontend.json +41 -0
  196. package/src/core/teams/qa.json +41 -0
  197. package/src/core/teams/solo.json +35 -0
  198. package/src/core/templates/agileflow-metadata.json +20 -1
  199. package/tools/cli/commands/setup.js +85 -3
  200. package/tools/cli/commands/update.js +42 -0
  201. package/tools/cli/installers/ide/_base-ide.js +42 -5
  202. package/tools/cli/installers/ide/claude-code.js +71 -3
  203. package/tools/cli/lib/content-injector.js +160 -12
  204. package/tools/cli/lib/docs-setup.js +1 -1
  205. package/src/core/commands/skill/create.md +0 -698
  206. package/src/core/commands/skill/delete.md +0 -316
  207. package/src/core/commands/skill/edit.md +0 -359
  208. package/src/core/commands/skill/test.md +0 -394
  209. package/src/core/commands/skill/upgrade.md +0 -552
  210. package/src/core/templates/skill-template.md +0 -117
@@ -28,6 +28,19 @@ const { c } = require('../../lib/colors');
28
28
  // Default claim expiration: 4 hours
29
29
  const DEFAULT_CLAIM_TTL_HOURS = 4;
30
30
 
31
+ // Canonical status-writer (lazy-loaded)
32
+ let _statusWriter;
33
+ function getStatusWriter() {
34
+ if (_statusWriter === undefined) {
35
+ try {
36
+ _statusWriter = require('./status-writer');
37
+ } catch (e) {
38
+ _statusWriter = null;
39
+ }
40
+ }
41
+ return _statusWriter;
42
+ }
43
+
31
44
  // Agent Teams integration (lazy-loaded)
32
45
  let _featureFlags, _taskSync;
33
46
  function getFeatureFlags() {
@@ -209,9 +222,57 @@ function isStoryClaimed(story, currentSessionId) {
209
222
  function claimStory(storyId, options = {}) {
210
223
  const { force = false, rootDir } = options;
211
224
  const root = rootDir || getProjectRoot();
212
- const statusPath = getStatusPath(root);
213
225
 
214
- // Load status.json
226
+ // Pre-check: read story and validate claim eligibility
227
+ const statusWriter = getStatusWriter();
228
+ if (statusWriter) {
229
+ const readResult = statusWriter.readStory(root, storyId);
230
+ if (!readResult.ok) {
231
+ return { ok: false, error: readResult.error };
232
+ }
233
+
234
+ // Check if already claimed by someone else
235
+ const claimCheck = isStoryClaimed(readResult.story);
236
+ if (claimCheck.claimed && !force) {
237
+ return {
238
+ ok: false,
239
+ claimed: true,
240
+ claimedBy: claimCheck.claimedBy,
241
+ error: `Story ${storyId} is claimed by session ${claimCheck.claimedBy.session_id}`,
242
+ };
243
+ }
244
+
245
+ // Get current session info
246
+ const currentSession = getCurrentSession(root);
247
+ if (!currentSession) {
248
+ return { ok: false, error: 'Could not determine current session' };
249
+ }
250
+
251
+ // Build claim object
252
+ const claimObj = {
253
+ session_id: currentSession.session_id,
254
+ pid: currentSession.pid,
255
+ path: currentSession.path,
256
+ claimed_at: new Date().toISOString(),
257
+ claim_type: isTeamSessionActive(root) ? 'team' : 'worktree',
258
+ };
259
+
260
+ // Write via status-writer (atomic, validated)
261
+ const writeResult = statusWriter.updateStory(
262
+ root,
263
+ storyId,
264
+ { claimed_by: claimObj },
265
+ { skipValidation: true }
266
+ );
267
+ if (!writeResult.ok) {
268
+ return { ok: false, error: writeResult.error };
269
+ }
270
+
271
+ return { ok: true, claimed: true };
272
+ }
273
+
274
+ // Fallback: direct safeWriteJSON if status-writer unavailable
275
+ const statusPath = getStatusPath(root);
215
276
  const result = safeReadJSON(statusPath, { defaultValue: null });
216
277
  if (!result.ok || !result.data) {
217
278
  return { ok: false, error: result.error || 'Could not load status.json' };
@@ -224,7 +285,6 @@ function claimStory(storyId, options = {}) {
224
285
  return { ok: false, error: `Story ${storyId} not found` };
225
286
  }
226
287
 
227
- // Check if already claimed by someone else
228
288
  const claimCheck = isStoryClaimed(story);
229
289
  if (claimCheck.claimed && !force) {
230
290
  return {
@@ -235,13 +295,11 @@ function claimStory(storyId, options = {}) {
235
295
  };
236
296
  }
237
297
 
238
- // Get current session info
239
298
  const currentSession = getCurrentSession(root);
240
299
  if (!currentSession) {
241
300
  return { ok: false, error: 'Could not determine current session' };
242
301
  }
243
302
 
244
- // Set the claim
245
303
  story.claimed_by = {
246
304
  session_id: currentSession.session_id,
247
305
  pid: currentSession.pid,
@@ -250,23 +308,12 @@ function claimStory(storyId, options = {}) {
250
308
  claim_type: isTeamSessionActive(root) ? 'team' : 'worktree',
251
309
  };
252
310
 
253
- // Save status.json
254
311
  status.updated = new Date().toISOString();
255
312
  const writeResult = safeWriteJSON(statusPath, status);
256
313
  if (!writeResult.ok) {
257
314
  return { ok: false, error: writeResult.error };
258
315
  }
259
316
 
260
- // Sync claim to native task list when team session is active
261
- if (isTeamSessionActive(root)) {
262
- const taskSync = getTaskSync();
263
- if (taskSync) {
264
- taskSync.syncToStatus(root, storyId, {
265
- claimed_by: story.claimed_by,
266
- });
267
- }
268
- }
269
-
270
317
  return { ok: true, claimed: true };
271
318
  }
272
319
 
@@ -281,9 +328,41 @@ function claimStory(storyId, options = {}) {
281
328
  function releaseStory(storyId, options = {}) {
282
329
  const { rootDir } = options;
283
330
  const root = rootDir || getProjectRoot();
284
- const statusPath = getStatusPath(root);
285
331
 
286
- // Load status.json
332
+ const statusWriter = getStatusWriter();
333
+ if (statusWriter) {
334
+ // Pre-check: verify ownership
335
+ const readResult = statusWriter.readStory(root, storyId);
336
+ if (!readResult.ok) {
337
+ return { ok: false, error: readResult.error };
338
+ }
339
+
340
+ const currentSession = getCurrentSession(root);
341
+ const mySessionId = currentSession ? currentSession.session_id : null;
342
+
343
+ if (readResult.story.claimed_by && readResult.story.claimed_by.session_id !== mySessionId) {
344
+ return {
345
+ ok: false,
346
+ error: `Story ${storyId} is claimed by session ${readResult.story.claimed_by.session_id}, not you`,
347
+ };
348
+ }
349
+
350
+ // Remove claim via status-writer (null deletes the field)
351
+ const writeResult = statusWriter.updateStory(
352
+ root,
353
+ storyId,
354
+ { claimed_by: null },
355
+ { skipValidation: true }
356
+ );
357
+ if (!writeResult.ok) {
358
+ return { ok: false, error: writeResult.error };
359
+ }
360
+
361
+ return { ok: true, released: true };
362
+ }
363
+
364
+ // Fallback: direct safeWriteJSON if status-writer unavailable
365
+ const statusPath = getStatusPath(root);
287
366
  const result = safeReadJSON(statusPath, { defaultValue: null });
288
367
  if (!result.ok || !result.data) {
289
368
  return { ok: false, error: result.error || 'Could not load status.json' };
@@ -296,11 +375,9 @@ function releaseStory(storyId, options = {}) {
296
375
  return { ok: false, error: `Story ${storyId} not found` };
297
376
  }
298
377
 
299
- // Get current session info
300
378
  const currentSession = getCurrentSession(root);
301
379
  const mySessionId = currentSession ? currentSession.session_id : null;
302
380
 
303
- // Check if we own this claim
304
381
  if (story.claimed_by && story.claimed_by.session_id !== mySessionId) {
305
382
  return {
306
383
  ok: false,
@@ -308,26 +385,14 @@ function releaseStory(storyId, options = {}) {
308
385
  };
309
386
  }
310
387
 
311
- // Remove the claim
312
388
  delete story.claimed_by;
313
389
 
314
- // Save status.json
315
390
  status.updated = new Date().toISOString();
316
391
  const writeResult = safeWriteJSON(statusPath, status);
317
392
  if (!writeResult.ok) {
318
393
  return { ok: false, error: writeResult.error };
319
394
  }
320
395
 
321
- // Sync release to native task list when team session is active
322
- if (isTeamSessionActive(root)) {
323
- const taskSync = getTaskSync();
324
- if (taskSync) {
325
- taskSync.syncToStatus(root, storyId, {
326
- claimed_by: null,
327
- });
328
- }
329
- }
330
-
331
396
  return { ok: true, released: true };
332
397
  }
333
398
 
@@ -432,35 +497,53 @@ function cleanupStaleClaims(options = {}) {
432
497
  const root = rootDir || getProjectRoot();
433
498
  const statusPath = getStatusPath(root);
434
499
 
435
- // Load status.json
500
+ // Load status.json to find stale claims
436
501
  const result = safeReadJSON(statusPath, { defaultValue: null });
437
502
  if (!result.ok || !result.data) {
438
503
  return { ok: false, error: result.error || 'Could not load status.json' };
439
504
  }
440
505
 
441
506
  const status = result.data;
442
- let cleanedCount = 0;
507
+ const staleStoryIds = [];
443
508
 
444
509
  for (const [id, story] of Object.entries(status.stories || {})) {
445
510
  if (!story.claimed_by) continue;
446
-
447
- // Check if claim is stale
448
511
  if (!isClaimValid(story.claimed_by)) {
449
- delete story.claimed_by;
450
- cleanedCount++;
512
+ staleStoryIds.push(id);
451
513
  }
452
514
  }
453
515
 
454
- // Save if anything was cleaned
455
- if (cleanedCount > 0) {
456
- status.updated = new Date().toISOString();
457
- const writeResult = safeWriteJSON(statusPath, status);
458
- if (!writeResult.ok) {
459
- return { ok: false, error: writeResult.error };
516
+ if (staleStoryIds.length === 0) {
517
+ return { ok: true, cleaned: 0 };
518
+ }
519
+
520
+ // Use status-writer for each stale claim if available
521
+ const statusWriter = getStatusWriter();
522
+ if (statusWriter) {
523
+ let cleanedCount = 0;
524
+ for (const id of staleStoryIds) {
525
+ const writeResult = statusWriter.updateStory(
526
+ root,
527
+ id,
528
+ { claimed_by: null },
529
+ { skipValidation: true }
530
+ );
531
+ if (writeResult.ok) cleanedCount++;
460
532
  }
533
+ return { ok: true, cleaned: cleanedCount };
534
+ }
535
+
536
+ // Fallback: bulk write via safeWriteJSON
537
+ for (const id of staleStoryIds) {
538
+ delete status.stories[id].claimed_by;
539
+ }
540
+ status.updated = new Date().toISOString();
541
+ const writeResult = safeWriteJSON(statusPath, status);
542
+ if (!writeResult.ok) {
543
+ return { ok: false, error: writeResult.error };
461
544
  }
462
545
 
463
- return { ok: true, cleaned: cleanedCount };
546
+ return { ok: true, cleaned: staleStoryIds.length };
464
547
  }
465
548
 
466
549
  /**
@@ -28,6 +28,19 @@ function getPaths() {
28
28
  return _paths;
29
29
  }
30
30
 
31
+ // Lazy-load status-writer for canonical writes
32
+ let _statusWriter;
33
+ function getStatusWriter() {
34
+ if (_statusWriter === undefined) {
35
+ try {
36
+ _statusWriter = require('./status-writer');
37
+ } catch (e) {
38
+ _statusWriter = null;
39
+ }
40
+ }
41
+ return _statusWriter;
42
+ }
43
+
31
44
  /**
32
45
  * Map AgileFlow story status to native task status.
33
46
  */
@@ -75,6 +88,7 @@ function readStatusStories(rootDir) {
75
88
 
76
89
  /**
77
90
  * Write status.json stories back.
91
+ * @deprecated Use status-writer.updateStory() for individual story mutations instead.
78
92
  */
79
93
  function writeStatusStories(rootDir, stories) {
80
94
  try {
@@ -105,34 +119,12 @@ function writeStatusStories(rootDir, stories) {
105
119
  * @returns {{ ok: boolean }}
106
120
  */
107
121
  function syncToStatus(rootDir, storyId, updates) {
108
- try {
109
- const paths = getPaths();
110
- const statusPath = paths
111
- ? paths.getStatusPath(rootDir)
112
- : path.join(rootDir, 'docs', '09-agents', 'status.json');
113
-
114
- if (!fs.existsSync(statusPath)) {
115
- return { ok: false, error: 'status.json not found' };
116
- }
117
-
118
- const data = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
119
- if (!data.stories) data.stories = {};
120
-
121
- if (!data.stories[storyId]) {
122
- return { ok: false, error: `Story ${storyId} not found` };
123
- }
124
-
125
- // Apply updates
126
- Object.assign(data.stories[storyId], updates);
127
-
128
- // Update timestamp
129
- data.stories[storyId].updated_at = new Date().toISOString();
130
-
131
- fs.writeFileSync(statusPath, JSON.stringify(data, null, 2) + '\n');
132
- return { ok: true };
133
- } catch (e) {
134
- return { ok: false, error: e.message };
122
+ const statusWriter = getStatusWriter();
123
+ if (statusWriter) {
124
+ return statusWriter.updateStory(rootDir, storyId, updates, { skipValidation: true });
135
125
  }
126
+
127
+ return { ok: false, error: 'status-writer module not available' };
136
128
  }
137
129
 
138
130
  /**
@@ -184,6 +176,7 @@ function syncFromStatus(rootDir, filters = {}) {
184
176
  */
185
177
  function reconcile(rootDir, nativeTasks) {
186
178
  let updated = 0;
179
+ const statusWriter = getStatusWriter();
187
180
 
188
181
  try {
189
182
  const paths = getPaths();
@@ -195,6 +188,7 @@ function reconcile(rootDir, nativeTasks) {
195
188
  return { ok: false, error: 'status.json not found', updated: 0 };
196
189
  }
197
190
 
191
+ // Pre-read current data to check which stories actually changed
198
192
  const data = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
199
193
  if (!data.stories) data.stories = {};
200
194
 
@@ -203,20 +197,20 @@ function reconcile(rootDir, nativeTasks) {
203
197
  if (!data.stories[storyId]) continue;
204
198
 
205
199
  const newStatus = taskStatusToStoryStatus(task.status);
206
- if (data.stories[storyId].status !== newStatus) {
207
- data.stories[storyId].status = newStatus;
208
- data.stories[storyId].updated_at = new Date().toISOString();
200
+ if (data.stories[storyId].status === newStatus) continue;
209
201
 
210
- if (newStatus === 'completed') {
211
- data.stories[storyId].completed_at = new Date().toISOString();
212
- }
213
-
214
- updated++;
202
+ const storyUpdates = { status: newStatus };
203
+ if (newStatus === 'completed') {
204
+ storyUpdates.completed_at = new Date().toISOString();
215
205
  }
216
- }
217
206
 
218
- if (updated > 0) {
219
- fs.writeFileSync(statusPath, JSON.stringify(data, null, 2) + '\n');
207
+ if (statusWriter) {
208
+ const result = statusWriter.updateStory(rootDir, storyId, storyUpdates, {
209
+ skipValidation: true,
210
+ });
211
+ if (result.ok) updated++;
212
+ }
213
+ // If no status-writer, skip this story (no fallback direct write)
220
214
  }
221
215
 
222
216
  return { ok: true, updated };