gsd-antigravity-kit 2.0.0 → 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 (258) 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 +4 -2
  252. package/.agent/skills/release-manager/SKILL.md +0 -162
  253. package/.agent/skills/release-manager/bin/LICENSE +0 -21
  254. package/.agent/skills/release-manager/bin/gh.exe +0 -0
  255. package/.agent/skills/release-manager/references/update_kb_from_fixes.md +0 -29
  256. package/.agent/skills/release-manager/scripts/release.ps1 +0 -222
  257. package/.agent/skills/selectpaste-update/SKILL.md +0 -46
  258. package/.agent/skills/selectpaste-update/scripts/sync-commands.py +0 -317
@@ -4,7 +4,7 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { safeReadFile, normalizeMd, output, error } = require('./core.cjs');
7
+ const { safeReadFile, normalizeMd, output, error, atomicWriteFileSync } = require('./core.cjs');
8
8
 
9
9
  // ─── Parsing engine ───────────────────────────────────────────────────────────
10
10
 
@@ -42,11 +42,9 @@ function splitInlineArray(body) {
42
42
 
43
43
  function extractFrontmatter(content) {
44
44
  const frontmatter = {};
45
- // Find ALL frontmatter blocks at the start of the file.
46
- // If multiple blocks exist (corruption from CRLF mismatch), use the LAST one
47
- // since it represents the most recent state sync.
48
- const allBlocks = [...content.matchAll(/(?:^|\n)\s*---\r?\n([\s\S]+?)\r?\n---/g)];
49
- const match = allBlocks.length > 0 ? allBlocks[allBlocks.length - 1] : null;
45
+ // Match frontmatter only at byte 0 a `---` block later in the document
46
+ // body (YAML examples, horizontal rules) must never be treated as frontmatter.
47
+ const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
50
48
  if (!match) return frontmatter;
51
49
 
52
50
  const yaml = match[1];
@@ -337,7 +335,7 @@ function cmdFrontmatterSet(cwd, filePath, field, value, raw) {
337
335
  try { parsedValue = JSON.parse(value); } catch { parsedValue = value; }
338
336
  fm[field] = parsedValue;
339
337
  const newContent = spliceFrontmatter(content, fm);
340
- fs.writeFileSync(fullPath, normalizeMd(newContent), 'utf-8');
338
+ atomicWriteFileSync(fullPath, normalizeMd(newContent));
341
339
  output({ updated: true, field, value: parsedValue }, raw, 'true');
342
340
  }
343
341
 
@@ -351,7 +349,7 @@ function cmdFrontmatterMerge(cwd, filePath, data, raw) {
351
349
  try { mergeData = JSON.parse(data); } catch { error('Invalid JSON for --data'); return; }
352
350
  Object.assign(fm, mergeData);
353
351
  const newContent = spliceFrontmatter(content, fm);
354
- fs.writeFileSync(fullPath, normalizeMd(newContent), 'utf-8');
352
+ atomicWriteFileSync(fullPath, normalizeMd(newContent));
355
353
  output({ merged: true, fields: Object.keys(mergeData) }, raw, 'true');
356
354
  }
357
355
 
@@ -0,0 +1,494 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const childProcess = require('child_process');
6
+ const { atomicWriteFileSync } = require('./core.cjs');
7
+
8
+ // ─── Config Gate ─────────────────────────────────────────────────────────────
9
+
10
+ /**
11
+ * Check whether graphify is enabled in the project config.
12
+ * Reads config.json directly via fs. Returns false by default
13
+ * (when no config, no graphify key, or on error).
14
+ *
15
+ * @param {string} planningDir - Path to .planning directory
16
+ * @returns {boolean}
17
+ */
18
+ function isGraphifyEnabled(planningDir) {
19
+ try {
20
+ const configPath = path.join(planningDir, 'config.json');
21
+ if (!fs.existsSync(configPath)) return false;
22
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
23
+ if (config && config.graphify && config.graphify.enabled === true) return true;
24
+ return false;
25
+ } catch (_e) {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Return the standard disabled response object.
32
+ * @returns {{ disabled: true, message: string }}
33
+ */
34
+ function disabledResponse() {
35
+ return { disabled: true, message: 'graphify is not enabled. Enable with: gsd-tools config-set graphify.enabled true' };
36
+ }
37
+
38
+ // ─── Subprocess Helper ───────────────────────────────────────────────────────
39
+
40
+ /**
41
+ * Execute graphify CLI as a subprocess with proper env and timeout handling.
42
+ *
43
+ * @param {string} cwd - Working directory for the subprocess
44
+ * @param {string[]} args - Arguments to pass to graphify
45
+ * @param {{ timeout?: number }} [options={}] - Options (timeout in ms, default 30000)
46
+ * @returns {{ exitCode: number, stdout: string, stderr: string }}
47
+ */
48
+ function execGraphify(cwd, args, options = {}) {
49
+ const timeout = options.timeout ?? 30000;
50
+ const result = childProcess.spawnSync('graphify', args, {
51
+ cwd,
52
+ stdio: 'pipe',
53
+ encoding: 'utf-8',
54
+ timeout,
55
+ env: { ...process.env, PYTHONUNBUFFERED: '1' },
56
+ });
57
+
58
+ // ENOENT -- graphify binary not found on PATH
59
+ if (result.error && result.error.code === 'ENOENT') {
60
+ return { exitCode: 127, stdout: '', stderr: 'graphify not found on PATH' };
61
+ }
62
+
63
+ // Timeout -- subprocess killed via SIGTERM
64
+ if (result.signal === 'SIGTERM') {
65
+ return {
66
+ exitCode: 124,
67
+ stdout: (result.stdout ?? '').toString().trim(),
68
+ stderr: 'graphify timed out after ' + timeout + 'ms',
69
+ };
70
+ }
71
+
72
+ return {
73
+ exitCode: result.status ?? 1,
74
+ stdout: (result.stdout ?? '').toString().trim(),
75
+ stderr: (result.stderr ?? '').toString().trim(),
76
+ };
77
+ }
78
+
79
+ // ─── Presence & Version ──────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Check whether the graphify CLI binary is installed and accessible on PATH.
83
+ * Uses --help (NOT --version, which graphify does not support).
84
+ *
85
+ * @returns {{ installed: boolean, message?: string }}
86
+ */
87
+ function checkGraphifyInstalled() {
88
+ const result = childProcess.spawnSync('graphify', ['--help'], {
89
+ stdio: 'pipe',
90
+ encoding: 'utf-8',
91
+ timeout: 5000,
92
+ });
93
+
94
+ if (result.error) {
95
+ return {
96
+ installed: false,
97
+ message: 'graphify is not installed.\n\nInstall with:\n uv pip install graphifyy && graphify install',
98
+ };
99
+ }
100
+
101
+ return { installed: true };
102
+ }
103
+
104
+ /**
105
+ * Detect graphify version via python3 importlib.metadata and check compatibility.
106
+ * Tested range: >=0.4.0,<1.0
107
+ *
108
+ * @returns {{ version: string|null, compatible: boolean|null, warning: string|null }}
109
+ */
110
+ function checkGraphifyVersion() {
111
+ const result = childProcess.spawnSync('python3', [
112
+ '-c',
113
+ 'from importlib.metadata import version; print(version("graphifyy"))',
114
+ ], {
115
+ stdio: 'pipe',
116
+ encoding: 'utf-8',
117
+ timeout: 5000,
118
+ });
119
+
120
+ if (result.status !== 0 || !result.stdout || !result.stdout.trim()) {
121
+ return { version: null, compatible: null, warning: 'Could not determine graphify version' };
122
+ }
123
+
124
+ const versionStr = result.stdout.trim();
125
+ const parts = versionStr.split('.').map(Number);
126
+
127
+ if (parts.length < 2 || parts.some(isNaN)) {
128
+ return { version: versionStr, compatible: null, warning: 'Could not parse version: ' + versionStr };
129
+ }
130
+
131
+ const compatible = parts[0] === 0 && parts[1] >= 4;
132
+ const warning = compatible ? null : 'graphify version ' + versionStr + ' is outside tested range >=0.4.0,<1.0';
133
+
134
+ return { version: versionStr, compatible, warning };
135
+ }
136
+
137
+ // ─── Internal Helpers ────────────────────────────────────────────────────────
138
+
139
+ /**
140
+ * Safely read and parse a JSON file. Returns null on missing file or parse error.
141
+ * Prevents crashes on malformed JSON (T-02-01 mitigation).
142
+ *
143
+ * @param {string} filePath - Absolute path to JSON file
144
+ * @returns {object|null}
145
+ */
146
+ function safeReadJson(filePath) {
147
+ try {
148
+ if (!fs.existsSync(filePath)) return null;
149
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
150
+ } catch (_e) {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Build a bidirectional adjacency map from graph nodes and edges.
157
+ * Each node ID maps to an array of { target, edge } entries.
158
+ * Bidirectional: both source->target and target->source are added (Pitfall 3).
159
+ *
160
+ * @param {{ nodes: object[], edges: object[] }} graph
161
+ * @returns {Object.<string, Array<{ target: string, edge: object }>>}
162
+ */
163
+ function buildAdjacencyMap(graph) {
164
+ const adj = {};
165
+ for (const node of (graph.nodes || [])) {
166
+ adj[node.id] = [];
167
+ }
168
+ for (const edge of (graph.edges || graph.links || [])) {
169
+ if (!adj[edge.source]) adj[edge.source] = [];
170
+ if (!adj[edge.target]) adj[edge.target] = [];
171
+ adj[edge.source].push({ target: edge.target, edge });
172
+ adj[edge.target].push({ target: edge.source, edge });
173
+ }
174
+ return adj;
175
+ }
176
+
177
+ /**
178
+ * Seed-then-expand query: find nodes matching term, then BFS-expand up to maxHops.
179
+ * Matches on node label and description (case-insensitive substring, D-01).
180
+ *
181
+ * @param {{ nodes: object[], edges: object[] }} graph
182
+ * @param {string} term - Search term
183
+ * @param {number} [maxHops=2] - Maximum BFS hops from seed nodes
184
+ * @returns {{ nodes: object[], edges: object[], seeds: Set<string> }}
185
+ */
186
+ function seedAndExpand(graph, term, maxHops = 2) {
187
+ const lowerTerm = term.toLowerCase();
188
+ const nodeMap = Object.fromEntries((graph.nodes || []).map(n => [n.id, n]));
189
+ const adj = buildAdjacencyMap(graph);
190
+
191
+ // Seed: match on label and description (case-insensitive substring)
192
+ const seeds = (graph.nodes || []).filter(n =>
193
+ (n.label || '').toLowerCase().includes(lowerTerm) ||
194
+ (n.description || '').toLowerCase().includes(lowerTerm)
195
+ );
196
+
197
+ // BFS expand from seeds
198
+ const visitedNodes = new Set(seeds.map(n => n.id));
199
+ const collectedEdges = [];
200
+ const seenEdgeKeys = new Set();
201
+ let frontier = seeds.map(n => n.id);
202
+
203
+ for (let hop = 0; hop < maxHops && frontier.length > 0; hop++) {
204
+ const nextFrontier = [];
205
+ for (const nodeId of frontier) {
206
+ for (const entry of (adj[nodeId] || [])) {
207
+ // Deduplicate edges by source::target::label key
208
+ const edgeKey = `${entry.edge.source}::${entry.edge.target}::${entry.edge.label || ''}`;
209
+ if (!seenEdgeKeys.has(edgeKey)) {
210
+ seenEdgeKeys.add(edgeKey);
211
+ collectedEdges.push(entry.edge);
212
+ }
213
+ if (!visitedNodes.has(entry.target)) {
214
+ visitedNodes.add(entry.target);
215
+ nextFrontier.push(entry.target);
216
+ }
217
+ }
218
+ }
219
+ frontier = nextFrontier;
220
+ }
221
+
222
+ const resultNodes = [...visitedNodes].map(id => nodeMap[id]).filter(Boolean);
223
+ return { nodes: resultNodes, edges: collectedEdges, seeds: new Set(seeds.map(n => n.id)) };
224
+ }
225
+
226
+ /**
227
+ * Apply token budget by dropping edges by confidence tier (D-04, D-05, D-06).
228
+ * Token estimation: Math.ceil(JSON.stringify(obj).length / 4).
229
+ * Drop order: AMBIGUOUS -> INFERRED -> EXTRACTED.
230
+ *
231
+ * @param {{ nodes: object[], edges: object[], seeds: Set<string> }} result
232
+ * @param {number|null} budgetTokens - Max tokens, or null/falsy for unlimited
233
+ * @returns {{ nodes: object[], edges: object[], trimmed: string|null, total_nodes: number, total_edges: number, term?: string }}
234
+ */
235
+ function applyBudget(result, budgetTokens) {
236
+ if (!budgetTokens) return result;
237
+
238
+ const CONFIDENCE_ORDER = ['AMBIGUOUS', 'INFERRED', 'EXTRACTED'];
239
+ let edges = [...result.edges];
240
+ let omitted = 0;
241
+
242
+ const estimateTokens = (obj) => Math.ceil(JSON.stringify(obj).length / 4);
243
+
244
+ for (const tier of CONFIDENCE_ORDER) {
245
+ if (estimateTokens({ nodes: result.nodes, edges }) <= budgetTokens) break;
246
+ const before = edges.length;
247
+ // Check both confidence and confidence_score field names (Open Question 1)
248
+ edges = edges.filter(e => (e.confidence || e.confidence_score) !== tier);
249
+ omitted += before - edges.length;
250
+ }
251
+
252
+ // Find unreachable nodes after edge removal
253
+ const reachableNodes = new Set();
254
+ for (const edge of edges) {
255
+ reachableNodes.add(edge.source);
256
+ reachableNodes.add(edge.target);
257
+ }
258
+ // Always keep seed nodes
259
+ const nodes = result.nodes.filter(n => reachableNodes.has(n.id) || (result.seeds && result.seeds.has(n.id)));
260
+ const unreachable = result.nodes.length - nodes.length;
261
+
262
+ return {
263
+ nodes,
264
+ edges,
265
+ trimmed: omitted > 0 ? `[${omitted} edges omitted, ${unreachable} nodes unreachable]` : null,
266
+ total_nodes: nodes.length,
267
+ total_edges: edges.length,
268
+ };
269
+ }
270
+
271
+ // ─── Public API ──────────────────────────────────────────────────────────────
272
+
273
+ /**
274
+ * Query the knowledge graph for nodes matching a term, with optional budget cap.
275
+ * Uses seed-then-expand BFS traversal (D-01).
276
+ *
277
+ * @param {string} cwd - Working directory
278
+ * @param {string} term - Search term
279
+ * @param {{ budget?: number|null }} [options={}]
280
+ * @returns {object}
281
+ */
282
+ function graphifyQuery(cwd, term, options = {}) {
283
+ const planningDir = path.join(cwd, '.planning');
284
+ if (!isGraphifyEnabled(planningDir)) return disabledResponse();
285
+
286
+ const graphPath = path.join(planningDir, 'graphs', 'graph.json');
287
+ if (!fs.existsSync(graphPath)) {
288
+ return { error: 'No graph built yet. Run graphify build first.' };
289
+ }
290
+
291
+ const graph = safeReadJson(graphPath);
292
+ if (!graph) {
293
+ return { error: 'Failed to parse graph.json' };
294
+ }
295
+
296
+ let result = seedAndExpand(graph, term);
297
+
298
+ if (options.budget) {
299
+ result = applyBudget(result, options.budget);
300
+ }
301
+
302
+ return {
303
+ term,
304
+ nodes: result.nodes,
305
+ edges: result.edges,
306
+ total_nodes: result.nodes.length,
307
+ total_edges: result.edges.length,
308
+ trimmed: result.trimmed || null,
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Return status information about the knowledge graph (STAT-01, STAT-02).
314
+ *
315
+ * @param {string} cwd - Working directory
316
+ * @returns {object}
317
+ */
318
+ function graphifyStatus(cwd) {
319
+ const planningDir = path.join(cwd, '.planning');
320
+ if (!isGraphifyEnabled(planningDir)) return disabledResponse();
321
+
322
+ const graphPath = path.join(planningDir, 'graphs', 'graph.json');
323
+ if (!fs.existsSync(graphPath)) {
324
+ return { exists: false, message: 'No graph built yet. Run graphify build to create one.' };
325
+ }
326
+
327
+ const stat = fs.statSync(graphPath);
328
+ const graph = safeReadJson(graphPath);
329
+ if (!graph) {
330
+ return { error: 'Failed to parse graph.json' };
331
+ }
332
+
333
+ const STALE_MS = 24 * 60 * 60 * 1000; // 24 hours
334
+ const age = Date.now() - stat.mtimeMs;
335
+
336
+ return {
337
+ exists: true,
338
+ last_build: stat.mtime.toISOString(),
339
+ node_count: (graph.nodes || []).length,
340
+ edge_count: (graph.edges || graph.links || []).length,
341
+ hyperedge_count: (graph.hyperedges || []).length,
342
+ stale: age > STALE_MS,
343
+ age_hours: Math.round(age / (60 * 60 * 1000)),
344
+ };
345
+ }
346
+
347
+ /**
348
+ * Compute topology-level diff between current graph and last build snapshot (D-07, D-08, D-09).
349
+ *
350
+ * @param {string} cwd - Working directory
351
+ * @returns {object}
352
+ */
353
+ function graphifyDiff(cwd) {
354
+ const planningDir = path.join(cwd, '.planning');
355
+ if (!isGraphifyEnabled(planningDir)) return disabledResponse();
356
+
357
+ const snapshotPath = path.join(planningDir, 'graphs', '.last-build-snapshot.json');
358
+ const graphPath = path.join(planningDir, 'graphs', 'graph.json');
359
+
360
+ if (!fs.existsSync(snapshotPath)) {
361
+ return { no_baseline: true, message: 'No previous snapshot. Run graphify build first, then build again to generate a diff baseline.' };
362
+ }
363
+
364
+ if (!fs.existsSync(graphPath)) {
365
+ return { error: 'No current graph. Run graphify build first.' };
366
+ }
367
+
368
+ const current = safeReadJson(graphPath);
369
+ const snapshot = safeReadJson(snapshotPath);
370
+
371
+ if (!current || !snapshot) {
372
+ return { error: 'Failed to parse graph or snapshot file' };
373
+ }
374
+
375
+ // Diff nodes
376
+ const currentNodeMap = Object.fromEntries((current.nodes || []).map(n => [n.id, n]));
377
+ const snapshotNodeMap = Object.fromEntries((snapshot.nodes || []).map(n => [n.id, n]));
378
+
379
+ const nodesAdded = Object.keys(currentNodeMap).filter(id => !snapshotNodeMap[id]);
380
+ const nodesRemoved = Object.keys(snapshotNodeMap).filter(id => !currentNodeMap[id]);
381
+ const nodesChanged = Object.keys(currentNodeMap).filter(id =>
382
+ snapshotNodeMap[id] && JSON.stringify(currentNodeMap[id]) !== JSON.stringify(snapshotNodeMap[id])
383
+ );
384
+
385
+ // Diff edges (keyed by source+target+relation)
386
+ const edgeKey = (e) => `${e.source}::${e.target}::${e.relation || e.label || ''}`;
387
+ const currentEdgeMap = Object.fromEntries((current.edges || current.links || []).map(e => [edgeKey(e), e]));
388
+ const snapshotEdgeMap = Object.fromEntries((snapshot.edges || snapshot.links || []).map(e => [edgeKey(e), e]));
389
+
390
+ const edgesAdded = Object.keys(currentEdgeMap).filter(k => !snapshotEdgeMap[k]);
391
+ const edgesRemoved = Object.keys(snapshotEdgeMap).filter(k => !currentEdgeMap[k]);
392
+ const edgesChanged = Object.keys(currentEdgeMap).filter(k =>
393
+ snapshotEdgeMap[k] && JSON.stringify(currentEdgeMap[k]) !== JSON.stringify(snapshotEdgeMap[k])
394
+ );
395
+
396
+ return {
397
+ nodes: { added: nodesAdded.length, removed: nodesRemoved.length, changed: nodesChanged.length },
398
+ edges: { added: edgesAdded.length, removed: edgesRemoved.length, changed: edgesChanged.length },
399
+ timestamp: snapshot.timestamp || null,
400
+ };
401
+ }
402
+
403
+ // ─── Build Pipeline (Phase 3) ───────────────────────────────────────────────
404
+
405
+ /**
406
+ * Pre-flight checks for graphify build (BUILD-01, BUILD-02, D-09).
407
+ * Does NOT invoke graphify -- returns structured JSON for the builder agent.
408
+ *
409
+ * @param {string} cwd - Working directory
410
+ * @returns {object}
411
+ */
412
+ function graphifyBuild(cwd) {
413
+ const planningDir = path.join(cwd, '.planning');
414
+ if (!isGraphifyEnabled(planningDir)) return disabledResponse();
415
+
416
+ const installed = checkGraphifyInstalled();
417
+ if (!installed.installed) return { error: installed.message };
418
+
419
+ const version = checkGraphifyVersion();
420
+
421
+ // Ensure output directory exists (D-05)
422
+ const graphsDir = path.join(planningDir, 'graphs');
423
+ fs.mkdirSync(graphsDir, { recursive: true });
424
+
425
+ // Read build timeout from config -- default 300s per D-02
426
+ const config = safeReadJson(path.join(planningDir, 'config.json')) || {};
427
+ const timeoutSec = (config.graphify && config.graphify.build_timeout) || 300;
428
+
429
+ return {
430
+ action: 'spawn_agent',
431
+ graphs_dir: graphsDir,
432
+ graphify_out: path.join(cwd, 'graphify-out'),
433
+ timeout_seconds: timeoutSec,
434
+ version: version.version,
435
+ version_warning: version.warning,
436
+ artifacts: ['graph.json', 'graph.html', 'GRAPH_REPORT.md'],
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Write a diff snapshot after successful build (D-06).
442
+ * Reads graph.json from .planning/graphs/ and writes .last-build-snapshot.json
443
+ * using atomicWriteFileSync for crash safety.
444
+ *
445
+ * @param {string} cwd - Working directory
446
+ * @returns {object}
447
+ */
448
+ function writeSnapshot(cwd) {
449
+ const graphPath = path.join(cwd, '.planning', 'graphs', 'graph.json');
450
+ const graph = safeReadJson(graphPath);
451
+ if (!graph) return { error: 'Cannot write snapshot: graph.json not parseable' };
452
+
453
+ const snapshot = {
454
+ version: 1,
455
+ timestamp: new Date().toISOString(),
456
+ nodes: graph.nodes || [],
457
+ edges: graph.edges || graph.links || [],
458
+ };
459
+
460
+ const snapshotPath = path.join(cwd, '.planning', 'graphs', '.last-build-snapshot.json');
461
+ atomicWriteFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
462
+ return {
463
+ saved: true,
464
+ timestamp: snapshot.timestamp,
465
+ node_count: snapshot.nodes.length,
466
+ edge_count: snapshot.edges.length,
467
+ };
468
+ }
469
+
470
+ // ─── Exports ─────────────────────────────────────────────────────────────────
471
+
472
+ module.exports = {
473
+ // Config gate
474
+ isGraphifyEnabled,
475
+ disabledResponse,
476
+ // Subprocess
477
+ execGraphify,
478
+ // Presence and version
479
+ checkGraphifyInstalled,
480
+ checkGraphifyVersion,
481
+ // Query (Phase 2)
482
+ graphifyQuery,
483
+ safeReadJson,
484
+ buildAdjacencyMap,
485
+ seedAndExpand,
486
+ applyBudget,
487
+ // Status (Phase 2)
488
+ graphifyStatus,
489
+ // Diff (Phase 2)
490
+ graphifyDiff,
491
+ // Build (Phase 3)
492
+ graphifyBuild,
493
+ writeSnapshot,
494
+ };