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.
- package/.agent/skills/gsd/SKILL.md +26 -4
- package/.agent/skills/gsd/VERSION +1 -1
- package/.agent/skills/gsd/assets/templates/AI-SPEC.md +246 -0
- package/.agent/skills/gsd/assets/templates/DEBUG.md +7 -2
- package/.agent/skills/gsd/assets/templates/config.json +56 -48
- package/.agent/skills/gsd/assets/templates/research.md +40 -0
- package/.agent/skills/gsd/assets/templates/spec.md +307 -0
- package/.agent/skills/gsd/assets/templates/state.md +8 -0
- package/.agent/skills/gsd/bin/gsd-tools.cjs +212 -11
- package/.agent/skills/gsd/bin/help-manifest.json +8 -2
- package/.agent/skills/gsd/bin/hooks/gsd-check-update-worker.js +108 -0
- package/.agent/skills/gsd/bin/hooks/gsd-check-update.js +14 -89
- package/.agent/skills/gsd/bin/hooks/gsd-context-monitor.js +34 -5
- package/.agent/skills/gsd/bin/hooks/gsd-phase-boundary.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-prompt-guard.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-read-guard.js +6 -1
- package/.agent/skills/gsd/bin/hooks/gsd-session-state.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-statusline.js +150 -16
- package/.agent/skills/gsd/bin/hooks/gsd-validate-commit.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-workflow-guard.js +1 -1
- package/.agent/skills/gsd/bin/lib/audit.cjs +757 -0
- package/.agent/skills/gsd/bin/lib/commands.cjs +17 -7
- package/.agent/skills/gsd/bin/lib/config.cjs +66 -20
- package/.agent/skills/gsd/bin/lib/core.cjs +212 -12
- package/.agent/skills/gsd/bin/lib/frontmatter.cjs +6 -8
- package/.agent/skills/gsd/bin/lib/graphify.cjs +494 -0
- package/.agent/skills/gsd/bin/lib/gsd2-import.cjs +511 -0
- package/.agent/skills/gsd/bin/lib/init.cjs +371 -18
- package/.agent/skills/gsd/bin/lib/intel.cjs +9 -30
- package/.agent/skills/gsd/bin/lib/milestone.cjs +18 -17
- package/.agent/skills/gsd/bin/lib/model-profiles.cjs +1 -0
- package/.agent/skills/gsd/bin/lib/phase.cjs +225 -98
- package/.agent/skills/gsd/bin/lib/profile-output.cjs +17 -5
- package/.agent/skills/gsd/bin/lib/roadmap.cjs +12 -5
- package/.agent/skills/gsd/bin/lib/state.cjs +394 -129
- package/.agent/skills/gsd/bin/lib/template.cjs +8 -4
- package/.agent/skills/gsd/bin/lib/uat.cjs +2 -1
- package/.agent/skills/gsd/bin/lib/verify.cjs +111 -42
- package/.agent/skills/gsd/migration_report.md +2 -2
- package/.agent/skills/gsd/references/agents/gsd-advisor-researcher.md +23 -0
- package/.agent/skills/gsd/references/agents/gsd-ai-researcher.md +133 -0
- package/.agent/skills/gsd/references/agents/gsd-code-fixer.md +11 -10
- package/.agent/skills/gsd/references/agents/gsd-code-reviewer.md +2 -2
- package/.agent/skills/gsd/references/agents/gsd-codebase-mapper.md +13 -2
- package/.agent/skills/gsd/references/agents/gsd-debug-session-manager.md +314 -0
- package/.agent/skills/gsd/references/agents/gsd-debugger.md +147 -76
- package/.agent/skills/gsd/references/agents/gsd-doc-verifier.md +1 -1
- package/.agent/skills/gsd/references/agents/gsd-doc-writer.md +615 -602
- package/.agent/skills/gsd/references/agents/gsd-domain-researcher.md +153 -0
- package/.agent/skills/gsd/references/agents/gsd-eval-auditor.md +175 -0
- package/.agent/skills/gsd/references/agents/gsd-eval-planner.md +154 -0
- package/.agent/skills/gsd/references/agents/gsd-executor.md +108 -38
- package/.agent/skills/gsd/references/agents/gsd-framework-selector.md +160 -0
- package/.agent/skills/gsd/references/agents/gsd-integration-checker.md +454 -443
- package/.agent/skills/gsd/references/agents/gsd-intel-updater.md +40 -20
- package/.agent/skills/gsd/references/agents/gsd-nyquist-auditor.md +187 -176
- package/.agent/skills/gsd/references/agents/gsd-pattern-mapper.md +335 -0
- package/.agent/skills/gsd/references/agents/gsd-phase-researcher.md +112 -13
- package/.agent/skills/gsd/references/agents/gsd-plan-checker.md +104 -10
- package/.agent/skills/gsd/references/agents/gsd-planner.md +125 -167
- package/.agent/skills/gsd/references/agents/gsd-project-researcher.md +25 -2
- package/.agent/skills/gsd/references/agents/gsd-research-synthesizer.md +3 -3
- package/.agent/skills/gsd/references/agents/gsd-roadmapper.md +12 -1
- package/.agent/skills/gsd/references/agents/gsd-security-auditor.md +139 -128
- package/.agent/skills/gsd/references/agents/gsd-ui-auditor.md +3 -3
- package/.agent/skills/gsd/references/agents/gsd-ui-checker.md +11 -2
- package/.agent/skills/gsd/references/agents/gsd-ui-researcher.md +27 -4
- package/.agent/skills/gsd/references/agents/gsd-verifier.md +13 -19
- package/.agent/skills/gsd/references/commands/atomic/add-todo.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/check-todos.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/cleanup.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/do.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/help.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/join-discord.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/note.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/session-report.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/ship.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/stats.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/thread.md +141 -41
- package/.agent/skills/gsd/references/commands/atomic/undo.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/add-backlog.md +15 -12
- package/.agent/skills/gsd/references/commands/milestone/audit-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/complete-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/milestone-summary.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/new-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/plan-milestone-gaps.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/plant-seed.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/review-backlog.md +4 -4
- package/.agent/skills/gsd/references/commands/misc/ai-integration-phase.md +38 -0
- package/.agent/skills/gsd/references/commands/misc/audit-fix.md +2 -2
- package/.agent/skills/gsd/references/commands/misc/audit-uat.md +2 -2
- package/.agent/skills/gsd/references/commands/misc/eval-review.md +34 -0
- package/.agent/skills/gsd/references/commands/misc/extract_learnings.md +24 -0
- package/.agent/skills/gsd/references/commands/misc/from-gsd2.md +49 -0
- package/.agent/skills/gsd/references/commands/misc/graphify.md +203 -0
- package/.agent/skills/gsd/references/commands/misc/inbox.md +40 -0
- package/.agent/skills/gsd/references/commands/misc/next.md +5 -3
- package/.agent/skills/gsd/references/commands/misc/progress.md +4 -3
- package/.agent/skills/gsd/references/commands/misc/sketch-wrap-up.md +33 -0
- package/.agent/skills/gsd/references/commands/misc/sketch.md +47 -0
- package/.agent/skills/gsd/references/commands/misc/spec-phase.md +64 -0
- package/.agent/skills/gsd/references/commands/misc/spike-wrap-up.md +33 -0
- package/.agent/skills/gsd/references/commands/misc/spike.md +43 -0
- package/.agent/skills/gsd/references/commands/misc/verify-work.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/add-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/add-tests.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/discuss-phase.md +5 -5
- package/.agent/skills/gsd/references/commands/phase/execute-phase.md +4 -4
- package/.agent/skills/gsd/references/commands/phase/insert-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/list-phase-assumptions.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/plan-phase.md +3 -3
- package/.agent/skills/gsd/references/commands/phase/remove-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/research-phase.md +5 -5
- package/.agent/skills/gsd/references/commands/phase/secure-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/ui-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/ui-review.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/validate-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/workstreams.md +9 -9
- package/.agent/skills/gsd/references/commands/project/analyze-dependencies.md +2 -2
- package/.agent/skills/gsd/references/commands/project/explore.md +2 -2
- package/.agent/skills/gsd/references/commands/project/import.md +2 -2
- package/.agent/skills/gsd/references/commands/project/intel.md +10 -10
- package/.agent/skills/gsd/references/commands/project/list-workspaces.md +2 -2
- package/.agent/skills/gsd/references/commands/project/map-codebase.md +2 -2
- package/.agent/skills/gsd/references/commands/project/new-project.md +2 -2
- package/.agent/skills/gsd/references/commands/project/new-workspace.md +2 -2
- package/.agent/skills/gsd/references/commands/project/remove-workspace.md +2 -2
- package/.agent/skills/gsd/references/commands/project/scan.md +2 -2
- package/.agent/skills/gsd/references/commands/system/autonomous.md +4 -3
- package/.agent/skills/gsd/references/commands/system/code-review-fix.md +3 -3
- package/.agent/skills/gsd/references/commands/system/code-review.md +3 -3
- package/.agent/skills/gsd/references/commands/system/debug.md +177 -100
- package/.agent/skills/gsd/references/commands/system/docs-update.md +2 -2
- package/.agent/skills/gsd/references/commands/system/fast.md +2 -2
- package/.agent/skills/gsd/references/commands/system/forensics.md +2 -2
- package/.agent/skills/gsd/references/commands/system/gsd-tools.md +153 -6
- package/.agent/skills/gsd/references/commands/system/health.md +2 -2
- package/.agent/skills/gsd/references/commands/system/manager.md +3 -3
- package/.agent/skills/gsd/references/commands/system/pause-work.md +2 -2
- package/.agent/skills/gsd/references/commands/system/pr-branch.md +2 -2
- package/.agent/skills/gsd/references/commands/system/profile-user.md +2 -2
- package/.agent/skills/gsd/references/commands/system/quick.md +127 -3
- package/.agent/skills/gsd/references/commands/system/reapply-patches.md +45 -6
- package/.agent/skills/gsd/references/commands/system/resume-work.md +2 -2
- package/.agent/skills/gsd/references/commands/system/review.md +6 -4
- package/.agent/skills/gsd/references/commands/system/set-profile.md +3 -3
- package/.agent/skills/gsd/references/commands/system/settings.md +2 -2
- package/.agent/skills/gsd/references/commands/system/update.md +2 -2
- package/.agent/skills/gsd/references/docs/ai-evals.md +156 -0
- package/.agent/skills/gsd/references/docs/ai-frameworks.md +186 -0
- package/.agent/skills/gsd/references/docs/artifact-types.md +18 -0
- package/.agent/skills/gsd/references/docs/autonomous-smart-discuss.md +277 -0
- package/.agent/skills/gsd/references/docs/checkpoints.md +30 -0
- package/.agent/skills/gsd/references/docs/common-bug-patterns.md +49 -49
- package/.agent/skills/gsd/references/docs/continuation-format.md +11 -7
- package/.agent/skills/gsd/references/docs/debugger-philosophy.md +76 -0
- package/.agent/skills/gsd/references/docs/decimal-phase-calculation.md +64 -64
- package/.agent/skills/gsd/references/docs/executor-examples.md +110 -0
- package/.agent/skills/gsd/references/docs/git-integration.md +4 -4
- package/.agent/skills/gsd/references/docs/git-planning-commit.md +40 -38
- package/.agent/skills/gsd/references/docs/ios-scaffold.md +123 -0
- package/.agent/skills/gsd/references/docs/mandatory-initial-read.md +2 -0
- package/.agent/skills/gsd/references/docs/phase-argument-parsing.md +61 -61
- package/.agent/skills/gsd/references/docs/planner-antipatterns.md +89 -0
- package/.agent/skills/gsd/references/docs/planner-revision.md +87 -87
- package/.agent/skills/gsd/references/docs/planner-source-audit.md +73 -0
- package/.agent/skills/gsd/references/docs/planning-config.md +33 -8
- package/.agent/skills/gsd/references/docs/project-skills-discovery.md +19 -0
- package/.agent/skills/gsd/references/docs/sketch-interactivity.md +41 -0
- package/.agent/skills/gsd/references/docs/sketch-theme-system.md +94 -0
- package/.agent/skills/gsd/references/docs/sketch-tooling.md +45 -0
- package/.agent/skills/gsd/references/docs/sketch-variant-patterns.md +81 -0
- package/.agent/skills/gsd/references/docs/tdd.md +67 -0
- package/.agent/skills/gsd/references/docs/universal-anti-patterns.md +5 -0
- package/.agent/skills/gsd/references/docs/workstream-flag.md +11 -11
- package/.agent/skills/gsd/references/mapping.md +1 -1
- package/.agent/skills/gsd/references/workflows/add-phase.md +112 -112
- package/.agent/skills/gsd/references/workflows/add-tests.md +6 -3
- package/.agent/skills/gsd/references/workflows/add-todo.md +5 -3
- package/.agent/skills/gsd/references/workflows/ai-integration-phase.md +284 -0
- package/.agent/skills/gsd/references/workflows/audit-fix.md +157 -157
- package/.agent/skills/gsd/references/workflows/audit-milestone.md +340 -340
- package/.agent/skills/gsd/references/workflows/audit-uat.md +109 -109
- package/.agent/skills/gsd/references/workflows/autonomous.md +20 -288
- package/.agent/skills/gsd/references/workflows/check-todos.md +4 -2
- package/.agent/skills/gsd/references/workflows/cleanup.md +3 -1
- package/.agent/skills/gsd/references/workflows/code-review-fix.md +497 -497
- package/.agent/skills/gsd/references/workflows/code-review.md +515 -515
- package/.agent/skills/gsd/references/workflows/complete-milestone.md +97 -24
- package/.agent/skills/gsd/references/workflows/diagnose-issues.md +238 -238
- package/.agent/skills/gsd/references/workflows/discovery-phase.md +2 -0
- package/.agent/skills/gsd/references/workflows/discuss-phase-assumptions.md +11 -11
- package/.agent/skills/gsd/references/workflows/discuss-phase.md +143 -19
- package/.agent/skills/gsd/references/workflows/do.md +8 -2
- package/.agent/skills/gsd/references/workflows/docs-update.md +5 -3
- package/.agent/skills/gsd/references/workflows/eval-review.md +155 -0
- package/.agent/skills/gsd/references/workflows/execute-phase.md +338 -54
- package/.agent/skills/gsd/references/workflows/execute-plan.md +80 -104
- package/.agent/skills/gsd/references/workflows/explore.md +3 -1
- package/.agent/skills/gsd/references/workflows/extract_learnings.md +232 -0
- package/.agent/skills/gsd/references/workflows/forensics.md +3 -3
- package/.agent/skills/gsd/references/workflows/health.md +2 -2
- package/.agent/skills/gsd/references/workflows/help.md +59 -1
- package/.agent/skills/gsd/references/workflows/import.md +3 -1
- package/.agent/skills/gsd/references/workflows/inbox.md +387 -384
- package/.agent/skills/gsd/references/workflows/insert-phase.md +130 -130
- package/.agent/skills/gsd/references/workflows/list-workspaces.md +56 -56
- package/.agent/skills/gsd/references/workflows/manager.md +5 -3
- package/.agent/skills/gsd/references/workflows/map-codebase.md +19 -5
- package/.agent/skills/gsd/references/workflows/milestone-summary.md +6 -6
- package/.agent/skills/gsd/references/workflows/new-milestone.md +63 -9
- package/.agent/skills/gsd/references/workflows/new-project.md +126 -22
- package/.agent/skills/gsd/references/workflows/new-workspace.md +6 -4
- package/.agent/skills/gsd/references/workflows/next.md +220 -153
- package/.agent/skills/gsd/references/workflows/note.md +2 -0
- package/.agent/skills/gsd/references/workflows/pause-work.md +11 -7
- package/.agent/skills/gsd/references/workflows/plan-milestone-gaps.md +273 -273
- package/.agent/skills/gsd/references/workflows/plan-phase.md +281 -62
- package/.agent/skills/gsd/references/workflows/plant-seed.md +4 -1
- package/.agent/skills/gsd/references/workflows/pr-branch.md +41 -13
- package/.agent/skills/gsd/references/workflows/profile-user.md +15 -13
- package/.agent/skills/gsd/references/workflows/progress.md +133 -21
- package/.agent/skills/gsd/references/workflows/quick.md +67 -27
- package/.agent/skills/gsd/references/workflows/remove-phase.md +155 -155
- package/.agent/skills/gsd/references/workflows/remove-workspace.md +4 -2
- package/.agent/skills/gsd/references/workflows/research-phase.md +3 -3
- package/.agent/skills/gsd/references/workflows/resume-project.md +3 -3
- package/.agent/skills/gsd/references/workflows/review.md +71 -8
- package/.agent/skills/gsd/references/workflows/scan.md +102 -102
- package/.agent/skills/gsd/references/workflows/secure-phase.md +7 -5
- package/.agent/skills/gsd/references/workflows/settings.md +24 -7
- package/.agent/skills/gsd/references/workflows/ship.md +71 -6
- package/.agent/skills/gsd/references/workflows/sketch-wrap-up.md +283 -0
- package/.agent/skills/gsd/references/workflows/sketch.md +263 -0
- package/.agent/skills/gsd/references/workflows/spec-phase.md +262 -0
- package/.agent/skills/gsd/references/workflows/spike-wrap-up.md +273 -0
- package/.agent/skills/gsd/references/workflows/spike.md +270 -0
- package/.agent/skills/gsd/references/workflows/stats.md +60 -60
- package/.agent/skills/gsd/references/workflows/transition.md +671 -671
- package/.agent/skills/gsd/references/workflows/ui-phase.md +33 -12
- package/.agent/skills/gsd/references/workflows/ui-review.md +6 -4
- package/.agent/skills/gsd/references/workflows/undo.md +3 -1
- package/.agent/skills/gsd/references/workflows/update.md +113 -2
- package/.agent/skills/gsd/references/workflows/validate-phase.md +7 -5
- package/.agent/skills/gsd/references/workflows/verify-phase.md +93 -10
- package/.agent/skills/gsd/references/workflows/verify-work.md +50 -10
- package/.agent/skills/gsd-converter/references/mapping.md +1 -1
- package/.agent/skills/gsd-converter/scripts/convert.py +36 -17
- package/.agent/skills/gsd-converter/scripts/regression_test.py +68 -33
- package/README.md +3 -2
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|