gsd-antigravity-kit 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/.agent/skills/gsd/SKILL.md +26 -4
  2. package/.agent/skills/gsd/VERSION +1 -1
  3. package/.agent/skills/gsd/assets/templates/AI-SPEC.md +246 -0
  4. package/.agent/skills/gsd/assets/templates/DEBUG.md +7 -2
  5. package/.agent/skills/gsd/assets/templates/config.json +56 -48
  6. package/.agent/skills/gsd/assets/templates/research.md +40 -0
  7. package/.agent/skills/gsd/assets/templates/spec.md +307 -0
  8. package/.agent/skills/gsd/assets/templates/state.md +8 -0
  9. package/.agent/skills/gsd/bin/gsd-tools.cjs +212 -11
  10. package/.agent/skills/gsd/bin/help-manifest.json +8 -2
  11. package/.agent/skills/gsd/bin/hooks/gsd-check-update-worker.js +108 -0
  12. package/.agent/skills/gsd/bin/hooks/gsd-check-update.js +14 -89
  13. package/.agent/skills/gsd/bin/hooks/gsd-context-monitor.js +34 -5
  14. package/.agent/skills/gsd/bin/hooks/gsd-phase-boundary.sh +1 -0
  15. package/.agent/skills/gsd/bin/hooks/gsd-prompt-guard.js +1 -1
  16. package/.agent/skills/gsd/bin/hooks/gsd-read-guard.js +6 -1
  17. package/.agent/skills/gsd/bin/hooks/gsd-session-state.sh +1 -0
  18. package/.agent/skills/gsd/bin/hooks/gsd-statusline.js +150 -16
  19. package/.agent/skills/gsd/bin/hooks/gsd-validate-commit.sh +1 -0
  20. package/.agent/skills/gsd/bin/hooks/gsd-workflow-guard.js +1 -1
  21. package/.agent/skills/gsd/bin/lib/audit.cjs +757 -0
  22. package/.agent/skills/gsd/bin/lib/commands.cjs +17 -7
  23. package/.agent/skills/gsd/bin/lib/config.cjs +66 -20
  24. package/.agent/skills/gsd/bin/lib/core.cjs +212 -12
  25. package/.agent/skills/gsd/bin/lib/frontmatter.cjs +6 -8
  26. package/.agent/skills/gsd/bin/lib/graphify.cjs +494 -0
  27. package/.agent/skills/gsd/bin/lib/gsd2-import.cjs +511 -0
  28. package/.agent/skills/gsd/bin/lib/init.cjs +371 -18
  29. package/.agent/skills/gsd/bin/lib/intel.cjs +9 -30
  30. package/.agent/skills/gsd/bin/lib/milestone.cjs +18 -17
  31. package/.agent/skills/gsd/bin/lib/model-profiles.cjs +1 -0
  32. package/.agent/skills/gsd/bin/lib/phase.cjs +225 -98
  33. package/.agent/skills/gsd/bin/lib/profile-output.cjs +17 -5
  34. package/.agent/skills/gsd/bin/lib/roadmap.cjs +12 -5
  35. package/.agent/skills/gsd/bin/lib/state.cjs +394 -129
  36. package/.agent/skills/gsd/bin/lib/template.cjs +8 -4
  37. package/.agent/skills/gsd/bin/lib/uat.cjs +2 -1
  38. package/.agent/skills/gsd/bin/lib/verify.cjs +111 -42
  39. package/.agent/skills/gsd/migration_report.md +2 -2
  40. package/.agent/skills/gsd/references/agents/gsd-advisor-researcher.md +23 -0
  41. package/.agent/skills/gsd/references/agents/gsd-ai-researcher.md +133 -0
  42. package/.agent/skills/gsd/references/agents/gsd-code-fixer.md +11 -10
  43. package/.agent/skills/gsd/references/agents/gsd-code-reviewer.md +2 -2
  44. package/.agent/skills/gsd/references/agents/gsd-codebase-mapper.md +13 -2
  45. package/.agent/skills/gsd/references/agents/gsd-debug-session-manager.md +314 -0
  46. package/.agent/skills/gsd/references/agents/gsd-debugger.md +147 -76
  47. package/.agent/skills/gsd/references/agents/gsd-doc-verifier.md +1 -1
  48. package/.agent/skills/gsd/references/agents/gsd-doc-writer.md +615 -602
  49. package/.agent/skills/gsd/references/agents/gsd-domain-researcher.md +153 -0
  50. package/.agent/skills/gsd/references/agents/gsd-eval-auditor.md +175 -0
  51. package/.agent/skills/gsd/references/agents/gsd-eval-planner.md +154 -0
  52. package/.agent/skills/gsd/references/agents/gsd-executor.md +108 -38
  53. package/.agent/skills/gsd/references/agents/gsd-framework-selector.md +160 -0
  54. package/.agent/skills/gsd/references/agents/gsd-integration-checker.md +454 -443
  55. package/.agent/skills/gsd/references/agents/gsd-intel-updater.md +40 -20
  56. package/.agent/skills/gsd/references/agents/gsd-nyquist-auditor.md +187 -176
  57. package/.agent/skills/gsd/references/agents/gsd-pattern-mapper.md +335 -0
  58. package/.agent/skills/gsd/references/agents/gsd-phase-researcher.md +112 -13
  59. package/.agent/skills/gsd/references/agents/gsd-plan-checker.md +104 -10
  60. package/.agent/skills/gsd/references/agents/gsd-planner.md +125 -167
  61. package/.agent/skills/gsd/references/agents/gsd-project-researcher.md +25 -2
  62. package/.agent/skills/gsd/references/agents/gsd-research-synthesizer.md +3 -3
  63. package/.agent/skills/gsd/references/agents/gsd-roadmapper.md +12 -1
  64. package/.agent/skills/gsd/references/agents/gsd-security-auditor.md +139 -128
  65. package/.agent/skills/gsd/references/agents/gsd-ui-auditor.md +3 -3
  66. package/.agent/skills/gsd/references/agents/gsd-ui-checker.md +11 -2
  67. package/.agent/skills/gsd/references/agents/gsd-ui-researcher.md +27 -4
  68. package/.agent/skills/gsd/references/agents/gsd-verifier.md +13 -19
  69. package/.agent/skills/gsd/references/commands/atomic/add-todo.md +2 -2
  70. package/.agent/skills/gsd/references/commands/atomic/check-todos.md +2 -2
  71. package/.agent/skills/gsd/references/commands/atomic/cleanup.md +2 -2
  72. package/.agent/skills/gsd/references/commands/atomic/do.md +2 -2
  73. package/.agent/skills/gsd/references/commands/atomic/help.md +2 -2
  74. package/.agent/skills/gsd/references/commands/atomic/join-discord.md +2 -2
  75. package/.agent/skills/gsd/references/commands/atomic/note.md +2 -2
  76. package/.agent/skills/gsd/references/commands/atomic/session-report.md +2 -2
  77. package/.agent/skills/gsd/references/commands/atomic/ship.md +2 -2
  78. package/.agent/skills/gsd/references/commands/atomic/stats.md +2 -2
  79. package/.agent/skills/gsd/references/commands/atomic/thread.md +141 -41
  80. package/.agent/skills/gsd/references/commands/atomic/undo.md +2 -2
  81. package/.agent/skills/gsd/references/commands/milestone/add-backlog.md +15 -12
  82. package/.agent/skills/gsd/references/commands/milestone/audit-milestone.md +2 -2
  83. package/.agent/skills/gsd/references/commands/milestone/complete-milestone.md +2 -2
  84. package/.agent/skills/gsd/references/commands/milestone/milestone-summary.md +2 -2
  85. package/.agent/skills/gsd/references/commands/milestone/new-milestone.md +2 -2
  86. package/.agent/skills/gsd/references/commands/milestone/plan-milestone-gaps.md +2 -2
  87. package/.agent/skills/gsd/references/commands/milestone/plant-seed.md +2 -2
  88. package/.agent/skills/gsd/references/commands/milestone/review-backlog.md +4 -4
  89. package/.agent/skills/gsd/references/commands/misc/ai-integration-phase.md +38 -0
  90. package/.agent/skills/gsd/references/commands/misc/audit-fix.md +2 -2
  91. package/.agent/skills/gsd/references/commands/misc/audit-uat.md +2 -2
  92. package/.agent/skills/gsd/references/commands/misc/eval-review.md +34 -0
  93. package/.agent/skills/gsd/references/commands/misc/extract_learnings.md +24 -0
  94. package/.agent/skills/gsd/references/commands/misc/from-gsd2.md +49 -0
  95. package/.agent/skills/gsd/references/commands/misc/graphify.md +203 -0
  96. package/.agent/skills/gsd/references/commands/misc/inbox.md +40 -0
  97. package/.agent/skills/gsd/references/commands/misc/next.md +5 -3
  98. package/.agent/skills/gsd/references/commands/misc/progress.md +4 -3
  99. package/.agent/skills/gsd/references/commands/misc/sketch-wrap-up.md +33 -0
  100. package/.agent/skills/gsd/references/commands/misc/sketch.md +47 -0
  101. package/.agent/skills/gsd/references/commands/misc/spec-phase.md +64 -0
  102. package/.agent/skills/gsd/references/commands/misc/spike-wrap-up.md +33 -0
  103. package/.agent/skills/gsd/references/commands/misc/spike.md +43 -0
  104. package/.agent/skills/gsd/references/commands/misc/verify-work.md +2 -2
  105. package/.agent/skills/gsd/references/commands/phase/add-phase.md +2 -2
  106. package/.agent/skills/gsd/references/commands/phase/add-tests.md +2 -2
  107. package/.agent/skills/gsd/references/commands/phase/discuss-phase.md +5 -5
  108. package/.agent/skills/gsd/references/commands/phase/execute-phase.md +4 -4
  109. package/.agent/skills/gsd/references/commands/phase/insert-phase.md +2 -2
  110. package/.agent/skills/gsd/references/commands/phase/list-phase-assumptions.md +2 -2
  111. package/.agent/skills/gsd/references/commands/phase/plan-phase.md +3 -3
  112. package/.agent/skills/gsd/references/commands/phase/remove-phase.md +2 -2
  113. package/.agent/skills/gsd/references/commands/phase/research-phase.md +5 -5
  114. package/.agent/skills/gsd/references/commands/phase/secure-phase.md +2 -2
  115. package/.agent/skills/gsd/references/commands/phase/ui-phase.md +2 -2
  116. package/.agent/skills/gsd/references/commands/phase/ui-review.md +2 -2
  117. package/.agent/skills/gsd/references/commands/phase/validate-phase.md +2 -2
  118. package/.agent/skills/gsd/references/commands/phase/workstreams.md +9 -9
  119. package/.agent/skills/gsd/references/commands/project/analyze-dependencies.md +2 -2
  120. package/.agent/skills/gsd/references/commands/project/explore.md +2 -2
  121. package/.agent/skills/gsd/references/commands/project/import.md +2 -2
  122. package/.agent/skills/gsd/references/commands/project/intel.md +10 -10
  123. package/.agent/skills/gsd/references/commands/project/list-workspaces.md +2 -2
  124. package/.agent/skills/gsd/references/commands/project/map-codebase.md +2 -2
  125. package/.agent/skills/gsd/references/commands/project/new-project.md +2 -2
  126. package/.agent/skills/gsd/references/commands/project/new-workspace.md +2 -2
  127. package/.agent/skills/gsd/references/commands/project/remove-workspace.md +2 -2
  128. package/.agent/skills/gsd/references/commands/project/scan.md +2 -2
  129. package/.agent/skills/gsd/references/commands/system/autonomous.md +4 -3
  130. package/.agent/skills/gsd/references/commands/system/code-review-fix.md +3 -3
  131. package/.agent/skills/gsd/references/commands/system/code-review.md +3 -3
  132. package/.agent/skills/gsd/references/commands/system/debug.md +177 -100
  133. package/.agent/skills/gsd/references/commands/system/docs-update.md +2 -2
  134. package/.agent/skills/gsd/references/commands/system/fast.md +2 -2
  135. package/.agent/skills/gsd/references/commands/system/forensics.md +2 -2
  136. package/.agent/skills/gsd/references/commands/system/gsd-tools.md +153 -6
  137. package/.agent/skills/gsd/references/commands/system/health.md +2 -2
  138. package/.agent/skills/gsd/references/commands/system/manager.md +3 -3
  139. package/.agent/skills/gsd/references/commands/system/pause-work.md +2 -2
  140. package/.agent/skills/gsd/references/commands/system/pr-branch.md +2 -2
  141. package/.agent/skills/gsd/references/commands/system/profile-user.md +2 -2
  142. package/.agent/skills/gsd/references/commands/system/quick.md +127 -3
  143. package/.agent/skills/gsd/references/commands/system/reapply-patches.md +45 -6
  144. package/.agent/skills/gsd/references/commands/system/resume-work.md +2 -2
  145. package/.agent/skills/gsd/references/commands/system/review.md +6 -4
  146. package/.agent/skills/gsd/references/commands/system/set-profile.md +3 -3
  147. package/.agent/skills/gsd/references/commands/system/settings.md +2 -2
  148. package/.agent/skills/gsd/references/commands/system/update.md +2 -2
  149. package/.agent/skills/gsd/references/docs/ai-evals.md +156 -0
  150. package/.agent/skills/gsd/references/docs/ai-frameworks.md +186 -0
  151. package/.agent/skills/gsd/references/docs/artifact-types.md +18 -0
  152. package/.agent/skills/gsd/references/docs/autonomous-smart-discuss.md +277 -0
  153. package/.agent/skills/gsd/references/docs/checkpoints.md +30 -0
  154. package/.agent/skills/gsd/references/docs/common-bug-patterns.md +49 -49
  155. package/.agent/skills/gsd/references/docs/continuation-format.md +11 -7
  156. package/.agent/skills/gsd/references/docs/debugger-philosophy.md +76 -0
  157. package/.agent/skills/gsd/references/docs/decimal-phase-calculation.md +64 -64
  158. package/.agent/skills/gsd/references/docs/executor-examples.md +110 -0
  159. package/.agent/skills/gsd/references/docs/git-integration.md +4 -4
  160. package/.agent/skills/gsd/references/docs/git-planning-commit.md +40 -38
  161. package/.agent/skills/gsd/references/docs/ios-scaffold.md +123 -0
  162. package/.agent/skills/gsd/references/docs/mandatory-initial-read.md +2 -0
  163. package/.agent/skills/gsd/references/docs/phase-argument-parsing.md +61 -61
  164. package/.agent/skills/gsd/references/docs/planner-antipatterns.md +89 -0
  165. package/.agent/skills/gsd/references/docs/planner-revision.md +87 -87
  166. package/.agent/skills/gsd/references/docs/planner-source-audit.md +73 -0
  167. package/.agent/skills/gsd/references/docs/planning-config.md +33 -8
  168. package/.agent/skills/gsd/references/docs/project-skills-discovery.md +19 -0
  169. package/.agent/skills/gsd/references/docs/sketch-interactivity.md +41 -0
  170. package/.agent/skills/gsd/references/docs/sketch-theme-system.md +94 -0
  171. package/.agent/skills/gsd/references/docs/sketch-tooling.md +45 -0
  172. package/.agent/skills/gsd/references/docs/sketch-variant-patterns.md +81 -0
  173. package/.agent/skills/gsd/references/docs/tdd.md +67 -0
  174. package/.agent/skills/gsd/references/docs/universal-anti-patterns.md +5 -0
  175. package/.agent/skills/gsd/references/docs/workstream-flag.md +11 -11
  176. package/.agent/skills/gsd/references/mapping.md +1 -1
  177. package/.agent/skills/gsd/references/workflows/add-phase.md +112 -112
  178. package/.agent/skills/gsd/references/workflows/add-tests.md +6 -3
  179. package/.agent/skills/gsd/references/workflows/add-todo.md +5 -3
  180. package/.agent/skills/gsd/references/workflows/ai-integration-phase.md +284 -0
  181. package/.agent/skills/gsd/references/workflows/audit-fix.md +157 -157
  182. package/.agent/skills/gsd/references/workflows/audit-milestone.md +340 -340
  183. package/.agent/skills/gsd/references/workflows/audit-uat.md +109 -109
  184. package/.agent/skills/gsd/references/workflows/autonomous.md +20 -288
  185. package/.agent/skills/gsd/references/workflows/check-todos.md +4 -2
  186. package/.agent/skills/gsd/references/workflows/cleanup.md +3 -1
  187. package/.agent/skills/gsd/references/workflows/code-review-fix.md +497 -497
  188. package/.agent/skills/gsd/references/workflows/code-review.md +515 -515
  189. package/.agent/skills/gsd/references/workflows/complete-milestone.md +97 -24
  190. package/.agent/skills/gsd/references/workflows/diagnose-issues.md +238 -238
  191. package/.agent/skills/gsd/references/workflows/discovery-phase.md +2 -0
  192. package/.agent/skills/gsd/references/workflows/discuss-phase-assumptions.md +11 -11
  193. package/.agent/skills/gsd/references/workflows/discuss-phase.md +143 -19
  194. package/.agent/skills/gsd/references/workflows/do.md +8 -2
  195. package/.agent/skills/gsd/references/workflows/docs-update.md +5 -3
  196. package/.agent/skills/gsd/references/workflows/eval-review.md +155 -0
  197. package/.agent/skills/gsd/references/workflows/execute-phase.md +338 -54
  198. package/.agent/skills/gsd/references/workflows/execute-plan.md +80 -104
  199. package/.agent/skills/gsd/references/workflows/explore.md +3 -1
  200. package/.agent/skills/gsd/references/workflows/extract_learnings.md +232 -0
  201. package/.agent/skills/gsd/references/workflows/forensics.md +3 -3
  202. package/.agent/skills/gsd/references/workflows/health.md +2 -2
  203. package/.agent/skills/gsd/references/workflows/help.md +59 -1
  204. package/.agent/skills/gsd/references/workflows/import.md +3 -1
  205. package/.agent/skills/gsd/references/workflows/inbox.md +387 -384
  206. package/.agent/skills/gsd/references/workflows/insert-phase.md +130 -130
  207. package/.agent/skills/gsd/references/workflows/list-workspaces.md +56 -56
  208. package/.agent/skills/gsd/references/workflows/manager.md +5 -3
  209. package/.agent/skills/gsd/references/workflows/map-codebase.md +19 -5
  210. package/.agent/skills/gsd/references/workflows/milestone-summary.md +6 -6
  211. package/.agent/skills/gsd/references/workflows/new-milestone.md +63 -9
  212. package/.agent/skills/gsd/references/workflows/new-project.md +126 -22
  213. package/.agent/skills/gsd/references/workflows/new-workspace.md +6 -4
  214. package/.agent/skills/gsd/references/workflows/next.md +220 -153
  215. package/.agent/skills/gsd/references/workflows/note.md +2 -0
  216. package/.agent/skills/gsd/references/workflows/pause-work.md +11 -7
  217. package/.agent/skills/gsd/references/workflows/plan-milestone-gaps.md +273 -273
  218. package/.agent/skills/gsd/references/workflows/plan-phase.md +281 -62
  219. package/.agent/skills/gsd/references/workflows/plant-seed.md +4 -1
  220. package/.agent/skills/gsd/references/workflows/pr-branch.md +41 -13
  221. package/.agent/skills/gsd/references/workflows/profile-user.md +15 -13
  222. package/.agent/skills/gsd/references/workflows/progress.md +133 -21
  223. package/.agent/skills/gsd/references/workflows/quick.md +67 -27
  224. package/.agent/skills/gsd/references/workflows/remove-phase.md +155 -155
  225. package/.agent/skills/gsd/references/workflows/remove-workspace.md +4 -2
  226. package/.agent/skills/gsd/references/workflows/research-phase.md +3 -3
  227. package/.agent/skills/gsd/references/workflows/resume-project.md +3 -3
  228. package/.agent/skills/gsd/references/workflows/review.md +71 -8
  229. package/.agent/skills/gsd/references/workflows/scan.md +102 -102
  230. package/.agent/skills/gsd/references/workflows/secure-phase.md +7 -5
  231. package/.agent/skills/gsd/references/workflows/settings.md +24 -7
  232. package/.agent/skills/gsd/references/workflows/ship.md +71 -6
  233. package/.agent/skills/gsd/references/workflows/sketch-wrap-up.md +283 -0
  234. package/.agent/skills/gsd/references/workflows/sketch.md +263 -0
  235. package/.agent/skills/gsd/references/workflows/spec-phase.md +262 -0
  236. package/.agent/skills/gsd/references/workflows/spike-wrap-up.md +273 -0
  237. package/.agent/skills/gsd/references/workflows/spike.md +270 -0
  238. package/.agent/skills/gsd/references/workflows/stats.md +60 -60
  239. package/.agent/skills/gsd/references/workflows/transition.md +671 -671
  240. package/.agent/skills/gsd/references/workflows/ui-phase.md +33 -12
  241. package/.agent/skills/gsd/references/workflows/ui-review.md +6 -4
  242. package/.agent/skills/gsd/references/workflows/undo.md +3 -1
  243. package/.agent/skills/gsd/references/workflows/update.md +113 -2
  244. package/.agent/skills/gsd/references/workflows/validate-phase.md +7 -5
  245. package/.agent/skills/gsd/references/workflows/verify-phase.md +93 -10
  246. package/.agent/skills/gsd/references/workflows/verify-work.md +50 -10
  247. package/.agent/skills/gsd-converter/references/mapping.md +1 -1
  248. package/.agent/skills/gsd-converter/scripts/convert.py +36 -17
  249. package/.agent/skills/gsd-converter/scripts/regression_test.py +68 -33
  250. package/README.md +3 -2
  251. package/package.json +1 -1
@@ -0,0 +1,757 @@
1
+ /**
2
+ * Open Artifact Audit — Cross-type unresolved state scanner
3
+ *
4
+ * Scans all .planning/ artifact categories for items with open/unresolved state.
5
+ * Returns structured JSON for workflow consumption.
6
+ * Called by: gsd-tools.cjs audit-open
7
+ * Used by: /gsd-complete-milestone pre-close gate
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { planningDir, toPosixPath } = require('./core.cjs');
15
+ const { extractFrontmatter } = require('./frontmatter.cjs');
16
+ const { requireSafePath, sanitizeForDisplay } = require('./security.cjs');
17
+
18
+ /**
19
+ * Scan .planning/debug/ for open sessions.
20
+ * Open = status NOT in ['resolved', 'complete'].
21
+ * Ignores the resolved/ subdirectory.
22
+ */
23
+ function scanDebugSessions(planDir) {
24
+ const debugDir = path.join(planDir, 'debug');
25
+ if (!fs.existsSync(debugDir)) return [];
26
+
27
+ const results = [];
28
+ let files;
29
+ try {
30
+ files = fs.readdirSync(debugDir, { withFileTypes: true });
31
+ } catch {
32
+ return [{ scan_error: true }];
33
+ }
34
+
35
+ for (const entry of files) {
36
+ if (!entry.isFile()) continue;
37
+ if (!entry.name.endsWith('.md')) continue;
38
+
39
+ const filePath = path.join(debugDir, entry.name);
40
+
41
+ let safeFilePath;
42
+ try {
43
+ safeFilePath = requireSafePath(filePath, planDir, 'debug session file', { allowAbsolute: true });
44
+ } catch {
45
+ continue;
46
+ }
47
+
48
+ let content;
49
+ try {
50
+ content = fs.readFileSync(safeFilePath, 'utf-8');
51
+ } catch {
52
+ continue;
53
+ }
54
+
55
+ const fm = extractFrontmatter(content);
56
+ const status = (fm.status || 'unknown').toLowerCase();
57
+ if (status === 'resolved' || status === 'complete') continue;
58
+
59
+ // Extract hypothesis from "Current Focus" block if parseable
60
+ let hypothesis = '';
61
+ const focusMatch = content.match(/##\s*Current Focus[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
62
+ if (focusMatch) {
63
+ const focusText = focusMatch[1].trim().split('\n')[0].trim();
64
+ hypothesis = sanitizeForDisplay(focusText.slice(0, 100));
65
+ }
66
+
67
+ const slug = path.basename(entry.name, '.md');
68
+ results.push({
69
+ slug: sanitizeForDisplay(slug),
70
+ status: sanitizeForDisplay(status),
71
+ updated: sanitizeForDisplay(String(fm.updated || fm.date || '')),
72
+ hypothesis,
73
+ });
74
+ }
75
+
76
+ return results;
77
+ }
78
+
79
+ /**
80
+ * Scan .planning/quick/ for incomplete tasks.
81
+ * Incomplete if SUMMARY.md missing or status !== 'complete'.
82
+ */
83
+ function scanQuickTasks(planDir) {
84
+ const quickDir = path.join(planDir, 'quick');
85
+ if (!fs.existsSync(quickDir)) return [];
86
+
87
+ let entries;
88
+ try {
89
+ entries = fs.readdirSync(quickDir, { withFileTypes: true });
90
+ } catch {
91
+ return [{ scan_error: true }];
92
+ }
93
+
94
+ const results = [];
95
+ for (const entry of entries) {
96
+ if (!entry.isDirectory()) continue;
97
+
98
+ const dirName = entry.name;
99
+ const taskDir = path.join(quickDir, dirName);
100
+
101
+ let safeTaskDir;
102
+ try {
103
+ safeTaskDir = requireSafePath(taskDir, planDir, 'quick task dir', { allowAbsolute: true });
104
+ } catch {
105
+ continue;
106
+ }
107
+
108
+ const summaryPath = path.join(safeTaskDir, 'SUMMARY.md');
109
+
110
+ let status = 'missing';
111
+ let description = '';
112
+
113
+ if (fs.existsSync(summaryPath)) {
114
+ let safeSum;
115
+ try {
116
+ safeSum = requireSafePath(summaryPath, planDir, 'quick task summary', { allowAbsolute: true });
117
+ } catch {
118
+ continue;
119
+ }
120
+ try {
121
+ const content = fs.readFileSync(safeSum, 'utf-8');
122
+ const fm = extractFrontmatter(content);
123
+ status = (fm.status || 'unknown').toLowerCase();
124
+ } catch {
125
+ status = 'unreadable';
126
+ }
127
+ }
128
+
129
+ if (status === 'complete') continue;
130
+
131
+ // Parse date and slug from directory name: YYYYMMDD-slug or YYYY-MM-DD-slug
132
+ let date = '';
133
+ let slug = sanitizeForDisplay(dirName);
134
+ const dateMatch = dirName.match(/^(\d{4}-?\d{2}-?\d{2})-(.+)$/);
135
+ if (dateMatch) {
136
+ date = dateMatch[1];
137
+ slug = sanitizeForDisplay(dateMatch[2]);
138
+ }
139
+
140
+ results.push({
141
+ slug,
142
+ date,
143
+ status: sanitizeForDisplay(status),
144
+ description,
145
+ });
146
+ }
147
+
148
+ return results;
149
+ }
150
+
151
+ /**
152
+ * Scan .planning/threads/ for open threads.
153
+ * Open if status in ['open', 'in_progress', 'in progress'] (case-insensitive).
154
+ */
155
+ function scanThreads(planDir) {
156
+ const threadsDir = path.join(planDir, 'threads');
157
+ if (!fs.existsSync(threadsDir)) return [];
158
+
159
+ let files;
160
+ try {
161
+ files = fs.readdirSync(threadsDir, { withFileTypes: true });
162
+ } catch {
163
+ return [{ scan_error: true }];
164
+ }
165
+
166
+ const openStatuses = new Set(['open', 'in_progress', 'in progress']);
167
+ const results = [];
168
+
169
+ for (const entry of files) {
170
+ if (!entry.isFile()) continue;
171
+ if (!entry.name.endsWith('.md')) continue;
172
+
173
+ const filePath = path.join(threadsDir, entry.name);
174
+
175
+ let safeFilePath;
176
+ try {
177
+ safeFilePath = requireSafePath(filePath, planDir, 'thread file', { allowAbsolute: true });
178
+ } catch {
179
+ continue;
180
+ }
181
+
182
+ let content;
183
+ try {
184
+ content = fs.readFileSync(safeFilePath, 'utf-8');
185
+ } catch {
186
+ continue;
187
+ }
188
+
189
+ const fm = extractFrontmatter(content);
190
+ let status = (fm.status || '').toLowerCase().trim();
191
+
192
+ // Fall back to scanning body for ## Status: OPEN / IN PROGRESS
193
+ if (!status) {
194
+ const bodyStatusMatch = content.match(/##\s*Status:\s*(OPEN|IN PROGRESS|IN_PROGRESS)/i);
195
+ if (bodyStatusMatch) {
196
+ status = bodyStatusMatch[1].toLowerCase().replace(/ /g, '_');
197
+ }
198
+ }
199
+
200
+ if (!openStatuses.has(status)) continue;
201
+
202
+ // Extract title from # Thread: heading or frontmatter title
203
+ let title = sanitizeForDisplay(String(fm.title || ''));
204
+ if (!title) {
205
+ const headingMatch = content.match(/^#\s*Thread:\s*(.+)$/m);
206
+ if (headingMatch) {
207
+ title = sanitizeForDisplay(headingMatch[1].trim().slice(0, 100));
208
+ }
209
+ }
210
+
211
+ const slug = path.basename(entry.name, '.md');
212
+ results.push({
213
+ slug: sanitizeForDisplay(slug),
214
+ status: sanitizeForDisplay(status),
215
+ updated: sanitizeForDisplay(String(fm.updated || fm.date || '')),
216
+ title,
217
+ });
218
+ }
219
+
220
+ return results;
221
+ }
222
+
223
+ /**
224
+ * Scan .planning/todos/pending/ for pending todos.
225
+ * Returns array of { filename, priority, area, summary }.
226
+ * Display limited to first 5 + count of remainder.
227
+ */
228
+ function scanTodos(planDir) {
229
+ const pendingDir = path.join(planDir, 'todos', 'pending');
230
+ if (!fs.existsSync(pendingDir)) return [];
231
+
232
+ let files;
233
+ try {
234
+ files = fs.readdirSync(pendingDir, { withFileTypes: true });
235
+ } catch {
236
+ return [{ scan_error: true }];
237
+ }
238
+
239
+ const mdFiles = files.filter(e => e.isFile() && e.name.endsWith('.md'));
240
+ const results = [];
241
+
242
+ const displayFiles = mdFiles.slice(0, 5);
243
+ for (const entry of displayFiles) {
244
+ const filePath = path.join(pendingDir, entry.name);
245
+
246
+ let safeFilePath;
247
+ try {
248
+ safeFilePath = requireSafePath(filePath, planDir, 'todo file', { allowAbsolute: true });
249
+ } catch {
250
+ continue;
251
+ }
252
+
253
+ let content;
254
+ try {
255
+ content = fs.readFileSync(safeFilePath, 'utf-8');
256
+ } catch {
257
+ continue;
258
+ }
259
+
260
+ const fm = extractFrontmatter(content);
261
+
262
+ // Extract first line of body after frontmatter
263
+ const bodyMatch = content.replace(/^---[\s\S]*?---\n?/, '');
264
+ const firstLine = bodyMatch.trim().split('\n')[0] || '';
265
+ const summary = sanitizeForDisplay(firstLine.slice(0, 100));
266
+
267
+ results.push({
268
+ filename: sanitizeForDisplay(entry.name),
269
+ priority: sanitizeForDisplay(String(fm.priority || '')),
270
+ area: sanitizeForDisplay(String(fm.area || '')),
271
+ summary,
272
+ });
273
+ }
274
+
275
+ if (mdFiles.length > 5) {
276
+ results.push({ _remainder_count: mdFiles.length - 5 });
277
+ }
278
+
279
+ return results;
280
+ }
281
+
282
+ /**
283
+ * Scan .planning/seeds/SEED-*.md for unimplemented seeds.
284
+ * Unimplemented if status in ['dormant', 'active', 'triggered'].
285
+ */
286
+ function scanSeeds(planDir) {
287
+ const seedsDir = path.join(planDir, 'seeds');
288
+ if (!fs.existsSync(seedsDir)) return [];
289
+
290
+ let files;
291
+ try {
292
+ files = fs.readdirSync(seedsDir, { withFileTypes: true });
293
+ } catch {
294
+ return [{ scan_error: true }];
295
+ }
296
+
297
+ const unimplementedStatuses = new Set(['dormant', 'active', 'triggered']);
298
+ const results = [];
299
+
300
+ for (const entry of files) {
301
+ if (!entry.isFile()) continue;
302
+ if (!entry.name.startsWith('SEED-') || !entry.name.endsWith('.md')) continue;
303
+
304
+ const filePath = path.join(seedsDir, entry.name);
305
+
306
+ let safeFilePath;
307
+ try {
308
+ safeFilePath = requireSafePath(filePath, planDir, 'seed file', { allowAbsolute: true });
309
+ } catch {
310
+ continue;
311
+ }
312
+
313
+ let content;
314
+ try {
315
+ content = fs.readFileSync(safeFilePath, 'utf-8');
316
+ } catch {
317
+ continue;
318
+ }
319
+
320
+ const fm = extractFrontmatter(content);
321
+ const status = (fm.status || 'dormant').toLowerCase();
322
+
323
+ if (!unimplementedStatuses.has(status)) continue;
324
+
325
+ // Extract seed_id from filename or frontmatter
326
+ const seedIdMatch = entry.name.match(/^(SEED-[\w-]+)\.md$/);
327
+ const seed_id = seedIdMatch ? seedIdMatch[1] : path.basename(entry.name, '.md');
328
+ const slug = sanitizeForDisplay(seed_id.replace(/^SEED-/, ''));
329
+
330
+ let title = sanitizeForDisplay(String(fm.title || ''));
331
+ if (!title) {
332
+ const headingMatch = content.match(/^#\s*(.+)$/m);
333
+ if (headingMatch) title = sanitizeForDisplay(headingMatch[1].trim().slice(0, 100));
334
+ }
335
+
336
+ results.push({
337
+ seed_id: sanitizeForDisplay(seed_id),
338
+ slug,
339
+ status: sanitizeForDisplay(status),
340
+ title,
341
+ });
342
+ }
343
+
344
+ return results;
345
+ }
346
+
347
+ /**
348
+ * Scan .planning/phases for UAT gaps (UAT files with status != 'complete').
349
+ */
350
+ function scanUatGaps(planDir) {
351
+ const phasesDir = path.join(planDir, 'phases');
352
+ if (!fs.existsSync(phasesDir)) return [];
353
+
354
+ let dirs;
355
+ try {
356
+ dirs = fs.readdirSync(phasesDir, { withFileTypes: true })
357
+ .filter(e => e.isDirectory())
358
+ .map(e => e.name)
359
+ .sort();
360
+ } catch {
361
+ return [{ scan_error: true }];
362
+ }
363
+
364
+ const results = [];
365
+
366
+ for (const dir of dirs) {
367
+ const phaseDir = path.join(phasesDir, dir);
368
+ const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
369
+ const phaseNum = phaseMatch ? phaseMatch[1] : dir;
370
+
371
+ let files;
372
+ try {
373
+ files = fs.readdirSync(phaseDir);
374
+ } catch {
375
+ continue;
376
+ }
377
+
378
+ for (const file of files.filter(f => f.includes('-UAT') && f.endsWith('.md'))) {
379
+ const filePath = path.join(phaseDir, file);
380
+
381
+ let safeFilePath;
382
+ try {
383
+ safeFilePath = requireSafePath(filePath, planDir, 'UAT file', { allowAbsolute: true });
384
+ } catch {
385
+ continue;
386
+ }
387
+
388
+ let content;
389
+ try {
390
+ content = fs.readFileSync(safeFilePath, 'utf-8');
391
+ } catch {
392
+ continue;
393
+ }
394
+
395
+ const fm = extractFrontmatter(content);
396
+ const status = (fm.status || 'unknown').toLowerCase();
397
+
398
+ if (status === 'complete') continue;
399
+
400
+ // Count open scenarios
401
+ const pendingMatches = (content.match(/result:\s*(?:pending|\[pending\])/gi) || []).length;
402
+
403
+ results.push({
404
+ phase: sanitizeForDisplay(phaseNum),
405
+ file: sanitizeForDisplay(file),
406
+ status: sanitizeForDisplay(status),
407
+ open_scenario_count: pendingMatches,
408
+ });
409
+ }
410
+ }
411
+
412
+ return results;
413
+ }
414
+
415
+ /**
416
+ * Scan .planning/phases for VERIFICATION gaps.
417
+ */
418
+ function scanVerificationGaps(planDir) {
419
+ const phasesDir = path.join(planDir, 'phases');
420
+ if (!fs.existsSync(phasesDir)) return [];
421
+
422
+ let dirs;
423
+ try {
424
+ dirs = fs.readdirSync(phasesDir, { withFileTypes: true })
425
+ .filter(e => e.isDirectory())
426
+ .map(e => e.name)
427
+ .sort();
428
+ } catch {
429
+ return [{ scan_error: true }];
430
+ }
431
+
432
+ const results = [];
433
+
434
+ for (const dir of dirs) {
435
+ const phaseDir = path.join(phasesDir, dir);
436
+ const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
437
+ const phaseNum = phaseMatch ? phaseMatch[1] : dir;
438
+
439
+ let files;
440
+ try {
441
+ files = fs.readdirSync(phaseDir);
442
+ } catch {
443
+ continue;
444
+ }
445
+
446
+ for (const file of files.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md'))) {
447
+ const filePath = path.join(phaseDir, file);
448
+
449
+ let safeFilePath;
450
+ try {
451
+ safeFilePath = requireSafePath(filePath, planDir, 'VERIFICATION file', { allowAbsolute: true });
452
+ } catch {
453
+ continue;
454
+ }
455
+
456
+ let content;
457
+ try {
458
+ content = fs.readFileSync(safeFilePath, 'utf-8');
459
+ } catch {
460
+ continue;
461
+ }
462
+
463
+ const fm = extractFrontmatter(content);
464
+ const status = (fm.status || 'unknown').toLowerCase();
465
+
466
+ if (status !== 'gaps_found' && status !== 'human_needed') continue;
467
+
468
+ results.push({
469
+ phase: sanitizeForDisplay(phaseNum),
470
+ file: sanitizeForDisplay(file),
471
+ status: sanitizeForDisplay(status),
472
+ });
473
+ }
474
+ }
475
+
476
+ return results;
477
+ }
478
+
479
+ /**
480
+ * Scan .planning/phases for CONTEXT files with open_questions.
481
+ */
482
+ function scanContextQuestions(planDir) {
483
+ const phasesDir = path.join(planDir, 'phases');
484
+ if (!fs.existsSync(phasesDir)) return [];
485
+
486
+ let dirs;
487
+ try {
488
+ dirs = fs.readdirSync(phasesDir, { withFileTypes: true })
489
+ .filter(e => e.isDirectory())
490
+ .map(e => e.name)
491
+ .sort();
492
+ } catch {
493
+ return [{ scan_error: true }];
494
+ }
495
+
496
+ const results = [];
497
+
498
+ for (const dir of dirs) {
499
+ const phaseDir = path.join(phasesDir, dir);
500
+ const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
501
+ const phaseNum = phaseMatch ? phaseMatch[1] : dir;
502
+
503
+ let files;
504
+ try {
505
+ files = fs.readdirSync(phaseDir);
506
+ } catch {
507
+ continue;
508
+ }
509
+
510
+ for (const file of files.filter(f => f.includes('-CONTEXT') && f.endsWith('.md'))) {
511
+ const filePath = path.join(phaseDir, file);
512
+
513
+ let safeFilePath;
514
+ try {
515
+ safeFilePath = requireSafePath(filePath, planDir, 'CONTEXT file', { allowAbsolute: true });
516
+ } catch {
517
+ continue;
518
+ }
519
+
520
+ let content;
521
+ try {
522
+ content = fs.readFileSync(safeFilePath, 'utf-8');
523
+ } catch {
524
+ continue;
525
+ }
526
+
527
+ const fm = extractFrontmatter(content);
528
+
529
+ // Check frontmatter open_questions field
530
+ let questions = [];
531
+ if (fm.open_questions) {
532
+ if (Array.isArray(fm.open_questions) && fm.open_questions.length > 0) {
533
+ questions = fm.open_questions.map(q => sanitizeForDisplay(String(q).slice(0, 200)));
534
+ }
535
+ }
536
+
537
+ // Also check for ## Open Questions section in body
538
+ if (questions.length === 0) {
539
+ const oqMatch = content.match(/##\s*Open Questions[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
540
+ if (oqMatch) {
541
+ const oqBody = oqMatch[1].trim();
542
+ if (oqBody && oqBody.length > 0 && !/^\s*none\s*$/i.test(oqBody)) {
543
+ const items = oqBody.split('\n')
544
+ .map(l => l.trim())
545
+ .filter(l => l && l !== '-' && l !== '*')
546
+ .filter(l => /^[-*\d]/.test(l) || l.includes('?'));
547
+ questions = items.slice(0, 3).map(q => sanitizeForDisplay(q.slice(0, 200)));
548
+ }
549
+ }
550
+ }
551
+
552
+ if (questions.length === 0) continue;
553
+
554
+ results.push({
555
+ phase: sanitizeForDisplay(phaseNum),
556
+ file: sanitizeForDisplay(file),
557
+ question_count: questions.length,
558
+ questions: questions.slice(0, 3),
559
+ });
560
+ }
561
+ }
562
+
563
+ return results;
564
+ }
565
+
566
+ /**
567
+ * Main audit function. Scans all .planning/ artifact categories.
568
+ *
569
+ * @param {string} cwd - Project root directory
570
+ * @returns {object} Structured audit result
571
+ */
572
+ function auditOpenArtifacts(cwd) {
573
+ const planDir = planningDir(cwd);
574
+
575
+ const debugSessions = (() => {
576
+ try { return scanDebugSessions(planDir); } catch { return [{ scan_error: true }]; }
577
+ })();
578
+
579
+ const quickTasks = (() => {
580
+ try { return scanQuickTasks(planDir); } catch { return [{ scan_error: true }]; }
581
+ })();
582
+
583
+ const threads = (() => {
584
+ try { return scanThreads(planDir); } catch { return [{ scan_error: true }]; }
585
+ })();
586
+
587
+ const todos = (() => {
588
+ try { return scanTodos(planDir); } catch { return [{ scan_error: true }]; }
589
+ })();
590
+
591
+ const seeds = (() => {
592
+ try { return scanSeeds(planDir); } catch { return [{ scan_error: true }]; }
593
+ })();
594
+
595
+ const uatGaps = (() => {
596
+ try { return scanUatGaps(planDir); } catch { return [{ scan_error: true }]; }
597
+ })();
598
+
599
+ const verificationGaps = (() => {
600
+ try { return scanVerificationGaps(planDir); } catch { return [{ scan_error: true }]; }
601
+ })();
602
+
603
+ const contextQuestions = (() => {
604
+ try { return scanContextQuestions(planDir); } catch { return [{ scan_error: true }]; }
605
+ })();
606
+
607
+ // Count real items (not scan_error sentinels)
608
+ const countReal = arr => arr.filter(i => !i.scan_error && !i._remainder_count).length;
609
+
610
+ const counts = {
611
+ debug_sessions: countReal(debugSessions),
612
+ quick_tasks: countReal(quickTasks),
613
+ threads: countReal(threads),
614
+ todos: countReal(todos),
615
+ seeds: countReal(seeds),
616
+ uat_gaps: countReal(uatGaps),
617
+ verification_gaps: countReal(verificationGaps),
618
+ context_questions: countReal(contextQuestions),
619
+ };
620
+ counts.total = Object.values(counts).reduce((s, n) => s + n, 0);
621
+
622
+ return {
623
+ scanned_at: new Date().toISOString(),
624
+ has_open_items: counts.total > 0,
625
+ counts,
626
+ items: {
627
+ debug_sessions: debugSessions,
628
+ quick_tasks: quickTasks,
629
+ threads,
630
+ todos,
631
+ seeds,
632
+ uat_gaps: uatGaps,
633
+ verification_gaps: verificationGaps,
634
+ context_questions: contextQuestions,
635
+ },
636
+ };
637
+ }
638
+
639
+ /**
640
+ * Format the audit result as a human-readable report.
641
+ *
642
+ * @param {object} auditResult - Result from auditOpenArtifacts()
643
+ * @returns {string} Formatted report
644
+ */
645
+ function formatAuditReport(auditResult) {
646
+ const { counts, items, has_open_items } = auditResult;
647
+ const lines = [];
648
+ const hr = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
649
+
650
+ lines.push(hr);
651
+ lines.push(' Milestone Close: Open Artifact Audit');
652
+ lines.push(hr);
653
+
654
+ if (!has_open_items) {
655
+ lines.push('');
656
+ lines.push(' All artifact types clear. Safe to proceed.');
657
+ lines.push('');
658
+ lines.push(hr);
659
+ return lines.join('\n');
660
+ }
661
+
662
+ // Debug sessions (blocking quality — red)
663
+ if (counts.debug_sessions > 0) {
664
+ lines.push('');
665
+ lines.push(`🔴 Debug Sessions (${counts.debug_sessions} open)`);
666
+ for (const item of items.debug_sessions.filter(i => !i.scan_error)) {
667
+ const hyp = item.hypothesis ? ` — ${item.hypothesis}` : '';
668
+ lines.push(` • ${item.slug} [${item.status}]${hyp}`);
669
+ }
670
+ }
671
+
672
+ // UAT gaps (blocking quality — red)
673
+ if (counts.uat_gaps > 0) {
674
+ lines.push('');
675
+ lines.push(`🔴 UAT Gaps (${counts.uat_gaps} phases with incomplete UAT)`);
676
+ for (const item of items.uat_gaps.filter(i => !i.scan_error)) {
677
+ lines.push(` • Phase ${item.phase}: ${item.file} [${item.status}] — ${item.open_scenario_count} pending scenarios`);
678
+ }
679
+ }
680
+
681
+ // Verification gaps (blocking quality — red)
682
+ if (counts.verification_gaps > 0) {
683
+ lines.push('');
684
+ lines.push(`🔴 Verification Gaps (${counts.verification_gaps} unresolved)`);
685
+ for (const item of items.verification_gaps.filter(i => !i.scan_error)) {
686
+ lines.push(` • Phase ${item.phase}: ${item.file} [${item.status}]`);
687
+ }
688
+ }
689
+
690
+ // Quick tasks (incomplete work — yellow)
691
+ if (counts.quick_tasks > 0) {
692
+ lines.push('');
693
+ lines.push(`🟡 Quick Tasks (${counts.quick_tasks} incomplete)`);
694
+ for (const item of items.quick_tasks.filter(i => !i.scan_error)) {
695
+ const d = item.date ? ` (${item.date})` : '';
696
+ lines.push(` • ${item.slug}${d} [${item.status}]`);
697
+ }
698
+ }
699
+
700
+ // Todos (incomplete work — yellow)
701
+ if (counts.todos > 0) {
702
+ const realTodos = items.todos.filter(i => !i.scan_error && !i._remainder_count);
703
+ const remainder = items.todos.find(i => i._remainder_count);
704
+ lines.push('');
705
+ lines.push(`🟡 Pending Todos (${counts.todos} pending)`);
706
+ for (const item of realTodos) {
707
+ const area = item.area ? ` [${item.area}]` : '';
708
+ const pri = item.priority ? ` (${item.priority})` : '';
709
+ lines.push(` • ${item.filename}${area}${pri}`);
710
+ if (item.summary) lines.push(` ${item.summary}`);
711
+ }
712
+ if (remainder) {
713
+ lines.push(` ... and ${remainder._remainder_count} more`);
714
+ }
715
+ }
716
+
717
+ // Threads (deferred decisions — blue)
718
+ if (counts.threads > 0) {
719
+ lines.push('');
720
+ lines.push(`🔵 Open Threads (${counts.threads} active)`);
721
+ for (const item of items.threads.filter(i => !i.scan_error)) {
722
+ const title = item.title ? ` — ${item.title}` : '';
723
+ lines.push(` • ${item.slug} [${item.status}]${title}`);
724
+ }
725
+ }
726
+
727
+ // Seeds (deferred decisions — blue)
728
+ if (counts.seeds > 0) {
729
+ lines.push('');
730
+ lines.push(`🔵 Unimplemented Seeds (${counts.seeds} pending)`);
731
+ for (const item of items.seeds.filter(i => !i.scan_error)) {
732
+ const title = item.title ? ` — ${item.title}` : '';
733
+ lines.push(` • ${item.seed_id} [${item.status}]${title}`);
734
+ }
735
+ }
736
+
737
+ // Context questions (deferred decisions — blue)
738
+ if (counts.context_questions > 0) {
739
+ lines.push('');
740
+ lines.push(`🔵 CONTEXT Open Questions (${counts.context_questions} phases with open questions)`);
741
+ for (const item of items.context_questions.filter(i => !i.scan_error)) {
742
+ lines.push(` • Phase ${item.phase}: ${item.file} (${item.question_count} question${item.question_count !== 1 ? 's' : ''})`);
743
+ for (const q of item.questions) {
744
+ lines.push(` - ${q}`);
745
+ }
746
+ }
747
+ }
748
+
749
+ lines.push('');
750
+ lines.push(hr);
751
+ lines.push(` ${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.`);
752
+ lines.push(hr);
753
+
754
+ return lines.join('\n');
755
+ }
756
+
757
+ module.exports = { auditOpenArtifacts, formatAuditReport };