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
|
@@ -313,11 +313,19 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
// Stage files
|
|
316
|
-
const
|
|
316
|
+
const explicitFiles = files && files.length > 0;
|
|
317
|
+
const filesToStage = explicitFiles ? files : ['.planning/'];
|
|
317
318
|
for (const file of filesToStage) {
|
|
318
319
|
const fullPath = path.join(cwd, file);
|
|
319
320
|
if (!fs.existsSync(fullPath)) {
|
|
320
|
-
|
|
321
|
+
if (explicitFiles) {
|
|
322
|
+
// Caller passed an explicit --files list: missing files are skipped.
|
|
323
|
+
// Staging a deletion here would silently remove tracked planning files
|
|
324
|
+
// (e.g. STATE.md, ROADMAP.md) when they are temporarily absent (#2014).
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
// Default mode (staging all of .planning/): stage the deletion so
|
|
328
|
+
// removed planning files are not left dangling in the index.
|
|
321
329
|
execGit(cwd, ['rm', '--cached', '--ignore-unmatch', file]);
|
|
322
330
|
} else {
|
|
323
331
|
execGit(cwd, ['add', file]);
|
|
@@ -823,8 +831,9 @@ function cmdStats(cwd, format, raw) {
|
|
|
823
831
|
const headingPattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
824
832
|
let match;
|
|
825
833
|
while ((match = headingPattern.exec(roadmapContent)) !== null) {
|
|
826
|
-
|
|
827
|
-
|
|
834
|
+
const key = normalizePhaseName(match[1]);
|
|
835
|
+
phasesByNumber.set(key, {
|
|
836
|
+
number: key,
|
|
828
837
|
name: match[2].replace(/\(INSERTED\)/i, '').trim(),
|
|
829
838
|
plans: 0,
|
|
830
839
|
summaries: 0,
|
|
@@ -854,9 +863,10 @@ function cmdStats(cwd, format, raw) {
|
|
|
854
863
|
|
|
855
864
|
const status = determinePhaseStatus(plans, summaries, path.join(phasesDir, dir), 'Not Started');
|
|
856
865
|
|
|
857
|
-
const
|
|
858
|
-
phasesByNumber.
|
|
859
|
-
|
|
866
|
+
const normalizedNum = normalizePhaseName(phaseNum);
|
|
867
|
+
const existing = phasesByNumber.get(normalizedNum);
|
|
868
|
+
phasesByNumber.set(normalizedNum, {
|
|
869
|
+
number: normalizedNum,
|
|
860
870
|
name: existing?.name || phaseName,
|
|
861
871
|
plans: (existing?.plans || 0) + plans,
|
|
862
872
|
summaries: (existing?.summaries || 0) + summaries,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { output, error,
|
|
7
|
+
const { output, error, planningDir, withPlanningLock, CONFIG_DEFAULTS, atomicWriteFileSync } = require('./core.cjs');
|
|
8
8
|
const {
|
|
9
9
|
VALID_PROFILES,
|
|
10
10
|
getAgentToModelMapForProfile,
|
|
@@ -15,19 +15,28 @@ const VALID_CONFIG_KEYS = new Set([
|
|
|
15
15
|
'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
|
|
16
16
|
'search_gitignored', 'brave_search', 'firecrawl', 'exa_search',
|
|
17
17
|
'workflow.research', 'workflow.plan_check', 'workflow.verifier',
|
|
18
|
-
'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate',
|
|
18
|
+
'workflow.nyquist_validation', 'workflow.ai_integration_phase', 'workflow.ui_phase', 'workflow.ui_safety_gate',
|
|
19
19
|
'workflow.auto_advance', 'workflow.node_repair', 'workflow.node_repair_budget',
|
|
20
|
+
'workflow.tdd_mode',
|
|
20
21
|
'workflow.text_mode',
|
|
21
22
|
'workflow.research_before_questions',
|
|
22
23
|
'workflow.discuss_mode',
|
|
23
24
|
'workflow.skip_discuss',
|
|
25
|
+
'workflow.auto_prune_state',
|
|
24
26
|
'workflow._auto_chain_active',
|
|
25
27
|
'workflow.use_worktrees',
|
|
26
28
|
'workflow.code_review',
|
|
27
29
|
'workflow.code_review_depth',
|
|
30
|
+
'workflow.code_review_command',
|
|
31
|
+
'workflow.pattern_mapper',
|
|
32
|
+
'workflow.plan_bounce',
|
|
33
|
+
'workflow.plan_bounce_script',
|
|
34
|
+
'workflow.plan_bounce_passes',
|
|
28
35
|
'git.branching_strategy', 'git.base_branch', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template',
|
|
29
36
|
'planning.commit_docs', 'planning.search_gitignored',
|
|
37
|
+
'workflow.cross_ai_execution', 'workflow.cross_ai_command', 'workflow.cross_ai_timeout',
|
|
30
38
|
'workflow.subagent_timeout',
|
|
39
|
+
'workflow.inline_plan_threshold',
|
|
31
40
|
'hooks.context_warnings',
|
|
32
41
|
'features.thinking_partner',
|
|
33
42
|
'context',
|
|
@@ -36,6 +45,10 @@ const VALID_CONFIG_KEYS = new Set([
|
|
|
36
45
|
'project_code', 'phase_naming',
|
|
37
46
|
'manager.flags.discuss', 'manager.flags.plan', 'manager.flags.execute',
|
|
38
47
|
'response_language',
|
|
48
|
+
'intel.enabled',
|
|
49
|
+
'graphify.enabled',
|
|
50
|
+
'graphify.build_timeout',
|
|
51
|
+
'antigravity_md_path',
|
|
39
52
|
]);
|
|
40
53
|
|
|
41
54
|
/**
|
|
@@ -47,6 +60,8 @@ function isValidConfigKey(keyPath) {
|
|
|
47
60
|
if (VALID_CONFIG_KEYS.has(keyPath)) return true;
|
|
48
61
|
// Allow agent_skills.<agent-type> with any agent type string
|
|
49
62
|
if (/^agent_skills\.[a-zA-Z0-9_-]+$/.test(keyPath)) return true;
|
|
63
|
+
// Allow review.models.<cli-name> for per-CLI model selection in /gsd-review
|
|
64
|
+
if (/^review\.models\.[a-zA-Z0-9_-]+$/.test(keyPath)) return true;
|
|
50
65
|
// Allow features.<feature_name> — dynamic namespace for feature flags.
|
|
51
66
|
// Intentionally open-ended so new flags (e.g., features.global_learnings) work
|
|
52
67
|
// without updating VALID_CONFIG_KEYS each time.
|
|
@@ -61,9 +76,11 @@ const CONFIG_KEY_SUGGESTIONS = {
|
|
|
61
76
|
'hooks.research_questions': 'workflow.research_before_questions',
|
|
62
77
|
'workflow.research_questions': 'workflow.research_before_questions',
|
|
63
78
|
'workflow.codereview': 'workflow.code_review',
|
|
79
|
+
'workflow.review_command': 'workflow.code_review_command',
|
|
64
80
|
'workflow.review': 'workflow.code_review',
|
|
65
81
|
'workflow.code_review_level': 'workflow.code_review_depth',
|
|
66
82
|
'workflow.review_depth': 'workflow.code_review_depth',
|
|
83
|
+
'review.model': 'review.models.<cli-name>',
|
|
67
84
|
};
|
|
68
85
|
|
|
69
86
|
function validateKnownConfigKeyPath(keyPath) {
|
|
@@ -143,12 +160,20 @@ function buildNewProjectConfig(userChoices) {
|
|
|
143
160
|
node_repair_budget: 2,
|
|
144
161
|
ui_phase: true,
|
|
145
162
|
ui_safety_gate: true,
|
|
163
|
+
ai_integration_phase: true,
|
|
164
|
+
tdd_mode: false,
|
|
146
165
|
text_mode: false,
|
|
147
166
|
research_before_questions: false,
|
|
148
167
|
discuss_mode: 'discuss',
|
|
149
168
|
skip_discuss: false,
|
|
150
169
|
code_review: true,
|
|
151
170
|
code_review_depth: 'standard',
|
|
171
|
+
code_review_command: null,
|
|
172
|
+
pattern_mapper: true,
|
|
173
|
+
plan_bounce: false,
|
|
174
|
+
plan_bounce_script: null,
|
|
175
|
+
plan_bounce_passes: 2,
|
|
176
|
+
auto_prune_state: false,
|
|
152
177
|
},
|
|
153
178
|
hooks: {
|
|
154
179
|
context_warnings: true,
|
|
@@ -156,6 +181,7 @@ function buildNewProjectConfig(userChoices) {
|
|
|
156
181
|
project_code: null,
|
|
157
182
|
phase_naming: 'sequential',
|
|
158
183
|
agent_skills: {},
|
|
184
|
+
antigravity_md_path: './ANTIGRAVITY.md',
|
|
159
185
|
};
|
|
160
186
|
|
|
161
187
|
// Three-level deep merge: hardcoded <- userDefaults <- choices
|
|
@@ -196,7 +222,7 @@ function buildNewProjectConfig(userChoices) {
|
|
|
196
222
|
* Idempotent: if config.json already exists, returns { created: false }.
|
|
197
223
|
*/
|
|
198
224
|
function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
199
|
-
const planningBase =
|
|
225
|
+
const planningBase = planningDir(cwd);
|
|
200
226
|
const configPath = path.join(planningBase, 'config.json');
|
|
201
227
|
|
|
202
228
|
// Idempotent: don't overwrite existing config
|
|
@@ -227,7 +253,7 @@ function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
|
227
253
|
const config = buildNewProjectConfig(userChoices);
|
|
228
254
|
|
|
229
255
|
try {
|
|
230
|
-
|
|
256
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
231
257
|
output({ created: true, path: '.planning/config.json' }, raw, 'created');
|
|
232
258
|
} catch (err) {
|
|
233
259
|
error('Failed to write config.json: ' + err.message);
|
|
@@ -241,7 +267,7 @@ function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
|
241
267
|
* the happy path. But note that `error()` will still `exit(1)` out of the process.
|
|
242
268
|
*/
|
|
243
269
|
function ensureConfigFile(cwd) {
|
|
244
|
-
const planningBase =
|
|
270
|
+
const planningBase = planningDir(cwd);
|
|
245
271
|
const configPath = path.join(planningBase, 'config.json');
|
|
246
272
|
|
|
247
273
|
// Ensure .planning directory exists
|
|
@@ -261,7 +287,7 @@ function ensureConfigFile(cwd) {
|
|
|
261
287
|
const config = buildNewProjectConfig({});
|
|
262
288
|
|
|
263
289
|
try {
|
|
264
|
-
|
|
290
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
265
291
|
return { created: true, path: '.planning/config.json' };
|
|
266
292
|
} catch (err) {
|
|
267
293
|
error('Failed to create config.json: ' + err.message);
|
|
@@ -291,38 +317,40 @@ function cmdConfigEnsureSection(cwd, raw) {
|
|
|
291
317
|
* the happy path. But note that `error()` will still `exit(1)` out of the process.
|
|
292
318
|
*/
|
|
293
319
|
function setConfigValue(cwd, keyPath, parsedValue) {
|
|
294
|
-
const configPath = path.join(
|
|
320
|
+
const configPath = path.join(planningDir(cwd), 'config.json');
|
|
295
321
|
|
|
322
|
+
return withPlanningLock(cwd, () => {
|
|
296
323
|
// Load existing config or start with empty object
|
|
297
324
|
let config = {};
|
|
298
325
|
try {
|
|
299
|
-
|
|
326
|
+
if (fs.existsSync(configPath)) {
|
|
300
327
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
301
|
-
|
|
328
|
+
}
|
|
302
329
|
} catch (err) {
|
|
303
|
-
|
|
330
|
+
error('Failed to read config.json: ' + err.message);
|
|
304
331
|
}
|
|
305
332
|
|
|
306
333
|
// Set nested value using dot notation (e.g., "workflow.research")
|
|
307
334
|
const keys = keyPath.split('.');
|
|
308
335
|
let current = config;
|
|
309
336
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
310
|
-
|
|
311
|
-
|
|
337
|
+
const key = keys[i];
|
|
338
|
+
if (current[key] === undefined || typeof current[key] !== 'object') {
|
|
312
339
|
current[key] = {};
|
|
313
|
-
|
|
314
|
-
|
|
340
|
+
}
|
|
341
|
+
current = current[key];
|
|
315
342
|
}
|
|
316
343
|
const previousValue = current[keys[keys.length - 1]]; // Capture previous value before overwriting
|
|
317
344
|
current[keys[keys.length - 1]] = parsedValue;
|
|
318
345
|
|
|
319
346
|
// Write back
|
|
320
347
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
348
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
349
|
+
return { updated: true, key: keyPath, value: parsedValue, previousValue };
|
|
323
350
|
} catch (err) {
|
|
324
|
-
|
|
351
|
+
error('Failed to write config.json: ' + err.message);
|
|
325
352
|
}
|
|
353
|
+
});
|
|
326
354
|
}
|
|
327
355
|
|
|
328
356
|
/**
|
|
@@ -361,17 +389,21 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
361
389
|
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
|
362
390
|
}
|
|
363
391
|
|
|
364
|
-
function cmdConfigGet(cwd, keyPath, raw) {
|
|
365
|
-
const configPath = path.join(
|
|
392
|
+
function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
393
|
+
const configPath = path.join(planningDir(cwd), 'config.json');
|
|
394
|
+
const hasDefault = defaultValue !== undefined;
|
|
366
395
|
|
|
367
396
|
if (!keyPath) {
|
|
368
|
-
error('Usage: config-get <key.path>');
|
|
397
|
+
error('Usage: config-get <key.path> [--default <value>]');
|
|
369
398
|
}
|
|
370
399
|
|
|
371
400
|
let config = {};
|
|
372
401
|
try {
|
|
373
402
|
if (fs.existsSync(configPath)) {
|
|
374
403
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
404
|
+
} else if (hasDefault) {
|
|
405
|
+
output(defaultValue, raw, String(defaultValue));
|
|
406
|
+
return;
|
|
375
407
|
} else {
|
|
376
408
|
error('No config.json found at ' + configPath);
|
|
377
409
|
}
|
|
@@ -385,12 +417,14 @@ function cmdConfigGet(cwd, keyPath, raw) {
|
|
|
385
417
|
let current = config;
|
|
386
418
|
for (const key of keys) {
|
|
387
419
|
if (current === undefined || current === null || typeof current !== 'object') {
|
|
420
|
+
if (hasDefault) { output(defaultValue, raw, String(defaultValue)); return; }
|
|
388
421
|
error(`Key not found: ${keyPath}`);
|
|
389
422
|
}
|
|
390
423
|
current = current[key];
|
|
391
424
|
}
|
|
392
425
|
|
|
393
426
|
if (current === undefined) {
|
|
427
|
+
if (hasDefault) { output(defaultValue, raw, String(defaultValue)); return; }
|
|
394
428
|
error(`Key not found: ${keyPath}`);
|
|
395
429
|
}
|
|
396
430
|
|
|
@@ -461,6 +495,17 @@ function getCmdConfigSetModelProfileResultMessage(
|
|
|
461
495
|
return paragraphs.join('\n\n');
|
|
462
496
|
}
|
|
463
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Print the resolved config.json path (workstream-aware). Used by settings.md
|
|
500
|
+
* so the workflow writes/reads the correct file when a workstream is active (#2282).
|
|
501
|
+
*/
|
|
502
|
+
function cmdConfigPath(cwd) {
|
|
503
|
+
// Always emit as plain text — a file path is used via shell substitution,
|
|
504
|
+
// never consumed as JSON. Passing raw=true forces plain-text output.
|
|
505
|
+
const configPath = path.join(planningDir(cwd), 'config.json');
|
|
506
|
+
output(configPath, true, configPath);
|
|
507
|
+
}
|
|
508
|
+
|
|
464
509
|
module.exports = {
|
|
465
510
|
VALID_CONFIG_KEYS,
|
|
466
511
|
cmdConfigEnsureSection,
|
|
@@ -468,4 +513,5 @@ module.exports = {
|
|
|
468
513
|
cmdConfigGet,
|
|
469
514
|
cmdConfigSetModelProfile,
|
|
470
515
|
cmdConfigNewProject,
|
|
516
|
+
cmdConfigPath,
|
|
471
517
|
};
|
|
@@ -27,6 +27,16 @@ const WORKSTREAM_SESSION_ENV_KEYS = [
|
|
|
27
27
|
let cachedControllingTtyToken = null;
|
|
28
28
|
let didProbeControllingTtyToken = false;
|
|
29
29
|
|
|
30
|
+
// Track all .planning/.lock files held by this process so they can be removed
|
|
31
|
+
// on exit. process.on('exit') fires even on process.exit(1), unlike try/finally
|
|
32
|
+
// which is skipped when error() calls process.exit(1) inside a locked region (#1916).
|
|
33
|
+
const _heldPlanningLocks = new Set();
|
|
34
|
+
process.on('exit', () => {
|
|
35
|
+
for (const lockPath of _heldPlanningLocks) {
|
|
36
|
+
try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
30
40
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
31
41
|
|
|
32
42
|
/** Normalize a relative path to always use forward slashes (cross-platform). */
|
|
@@ -149,14 +159,25 @@ function findProjectRoot(startDir) {
|
|
|
149
159
|
* @param {number} opts.maxAgeMs - max age in ms before removal (default: 5 min)
|
|
150
160
|
* @param {boolean} opts.dirsOnly - if true, only remove directories (default: false)
|
|
151
161
|
*/
|
|
162
|
+
/**
|
|
163
|
+
* Dedicated GSD temp directory: path.join(os.tmpdir(), 'gsd').
|
|
164
|
+
* Created on first use. Keeps GSD temp files isolated from the system
|
|
165
|
+
* temp directory so reap scans only GSD files (#1975).
|
|
166
|
+
*/
|
|
167
|
+
const GSD_TEMP_DIR = path.join(require('os').tmpdir(), 'gsd');
|
|
168
|
+
|
|
169
|
+
function ensureGsdTempDir() {
|
|
170
|
+
fs.mkdirSync(GSD_TEMP_DIR, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
|
|
152
173
|
function reapStaleTempFiles(prefix = 'gsd-', { maxAgeMs = 5 * 60 * 1000, dirsOnly = false } = {}) {
|
|
153
174
|
try {
|
|
154
|
-
|
|
175
|
+
ensureGsdTempDir();
|
|
155
176
|
const now = Date.now();
|
|
156
|
-
const entries = fs.readdirSync(
|
|
177
|
+
const entries = fs.readdirSync(GSD_TEMP_DIR);
|
|
157
178
|
for (const entry of entries) {
|
|
158
179
|
if (!entry.startsWith(prefix)) continue;
|
|
159
|
-
const fullPath = path.join(
|
|
180
|
+
const fullPath = path.join(GSD_TEMP_DIR, entry);
|
|
160
181
|
try {
|
|
161
182
|
const stat = fs.statSync(fullPath);
|
|
162
183
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
@@ -185,7 +206,8 @@ function output(result, raw, rawValue) {
|
|
|
185
206
|
// Write to tmpfile and output the path prefixed with @file: so callers can detect it.
|
|
186
207
|
if (json.length > 50000) {
|
|
187
208
|
reapStaleTempFiles();
|
|
188
|
-
|
|
209
|
+
ensureGsdTempDir();
|
|
210
|
+
const tmpPath = path.join(GSD_TEMP_DIR, `gsd-${Date.now()}.json`);
|
|
189
211
|
fs.writeFileSync(tmpPath, json, 'utf-8');
|
|
190
212
|
data = '@file:' + tmpPath;
|
|
191
213
|
} else {
|
|
@@ -284,6 +306,7 @@ const CONFIG_DEFAULTS = {
|
|
|
284
306
|
plan_checker: true,
|
|
285
307
|
verifier: true,
|
|
286
308
|
nyquist_validation: true,
|
|
309
|
+
ai_integration_phase: true,
|
|
287
310
|
parallelization: true,
|
|
288
311
|
brave_search: false,
|
|
289
312
|
firecrawl: false,
|
|
@@ -357,7 +380,7 @@ function loadConfig(cwd) {
|
|
|
357
380
|
// Section containers that hold nested sub-keys
|
|
358
381
|
'git', 'workflow', 'planning', 'hooks', 'features',
|
|
359
382
|
// Internal keys loadConfig reads but config-set doesn't expose
|
|
360
|
-
'model_overrides', 'agent_skills', 'context_window', 'resolve_model_ids',
|
|
383
|
+
'model_overrides', 'agent_skills', 'context_window', 'resolve_model_ids', 'antigravity_md_path',
|
|
361
384
|
// Deprecated keys (still accepted for migration, not in config-set)
|
|
362
385
|
'depth', 'multiRepo',
|
|
363
386
|
]);
|
|
@@ -407,7 +430,11 @@ function loadConfig(cwd) {
|
|
|
407
430
|
brave_search: get('brave_search') ?? defaults.brave_search,
|
|
408
431
|
firecrawl: get('firecrawl') ?? defaults.firecrawl,
|
|
409
432
|
exa_search: get('exa_search') ?? defaults.exa_search,
|
|
433
|
+
tdd_mode: get('tdd_mode', { section: 'workflow', field: 'tdd_mode' }) ?? false,
|
|
410
434
|
text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode,
|
|
435
|
+
auto_advance: get('auto_advance', { section: 'workflow', field: 'auto_advance' }) ?? false,
|
|
436
|
+
_auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false,
|
|
437
|
+
mode: get('mode') ?? 'interactive',
|
|
411
438
|
sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
|
|
412
439
|
resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
|
|
413
440
|
context_window: get('context_window') ?? defaults.context_window,
|
|
@@ -418,6 +445,7 @@ function loadConfig(cwd) {
|
|
|
418
445
|
agent_skills: parsed.agent_skills || {},
|
|
419
446
|
manager: parsed.manager || {},
|
|
420
447
|
response_language: get('response_language') || null,
|
|
448
|
+
antigravity_md_path: get('antigravity_md_path') || null,
|
|
421
449
|
};
|
|
422
450
|
} catch {
|
|
423
451
|
// Fall back to ~/.gsd/defaults.json only for truly pre-project contexts (#1683)
|
|
@@ -455,7 +483,11 @@ function loadConfig(cwd) {
|
|
|
455
483
|
|
|
456
484
|
// ─── Git utilities ────────────────────────────────────────────────────────────
|
|
457
485
|
|
|
486
|
+
const _gitIgnoredCache = new Map();
|
|
487
|
+
|
|
458
488
|
function isGitIgnored(cwd, targetPath) {
|
|
489
|
+
const key = cwd + '::' + targetPath;
|
|
490
|
+
if (_gitIgnoredCache.has(key)) return _gitIgnoredCache.get(key);
|
|
459
491
|
try {
|
|
460
492
|
// --no-index checks .gitignore rules regardless of whether the file is tracked.
|
|
461
493
|
// Without it, git check-ignore returns "not ignored" for tracked files even when
|
|
@@ -467,8 +499,10 @@ function isGitIgnored(cwd, targetPath) {
|
|
|
467
499
|
cwd,
|
|
468
500
|
stdio: 'pipe',
|
|
469
501
|
});
|
|
502
|
+
_gitIgnoredCache.set(key, true);
|
|
470
503
|
return true;
|
|
471
504
|
} catch {
|
|
505
|
+
_gitIgnoredCache.set(key, false);
|
|
472
506
|
return false;
|
|
473
507
|
}
|
|
474
508
|
}
|
|
@@ -630,6 +664,98 @@ function resolveWorktreeRoot(cwd) {
|
|
|
630
664
|
return cwd;
|
|
631
665
|
}
|
|
632
666
|
|
|
667
|
+
/**
|
|
668
|
+
* Parse `git worktree list --porcelain` output into an array of
|
|
669
|
+
* { path, branch } objects. Entries with a detached HEAD (no branch line)
|
|
670
|
+
* are skipped because we cannot safely reason about their merge status.
|
|
671
|
+
*
|
|
672
|
+
* @param {string} porcelain - raw output from git worktree list --porcelain
|
|
673
|
+
* @returns {{ path: string, branch: string }[]}
|
|
674
|
+
*/
|
|
675
|
+
function parseWorktreePorcelain(porcelain) {
|
|
676
|
+
const entries = [];
|
|
677
|
+
let current = null;
|
|
678
|
+
for (const line of porcelain.split('\n')) {
|
|
679
|
+
if (line.startsWith('worktree ')) {
|
|
680
|
+
current = { path: line.slice('worktree '.length).trim(), branch: null };
|
|
681
|
+
} else if (line.startsWith('branch refs/heads/') && current) {
|
|
682
|
+
current.branch = line.slice('branch refs/heads/'.length).trim();
|
|
683
|
+
} else if (line === '' && current) {
|
|
684
|
+
if (current.branch) entries.push(current);
|
|
685
|
+
current = null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// flush last entry if file doesn't end with blank line
|
|
689
|
+
if (current && current.branch) entries.push(current);
|
|
690
|
+
return entries;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Remove linked git worktrees whose branch has already been merged into the
|
|
695
|
+
* current HEAD of the main worktree. Also runs `git worktree prune` to clear
|
|
696
|
+
* any stale references left by manually-deleted worktree directories.
|
|
697
|
+
*
|
|
698
|
+
* Safe guards:
|
|
699
|
+
* - Never removes the main worktree (first entry in --porcelain output).
|
|
700
|
+
* - Never removes the worktree at process.cwd().
|
|
701
|
+
* - Never removes a worktree whose branch has unmerged commits.
|
|
702
|
+
* - Skips detached-HEAD worktrees (no branch name).
|
|
703
|
+
*
|
|
704
|
+
* @param {string} repoRoot - absolute path to the main (or any) worktree of
|
|
705
|
+
* the repository; used as `cwd` for git commands.
|
|
706
|
+
* @returns {string[]} list of worktree paths that were removed
|
|
707
|
+
*/
|
|
708
|
+
function pruneOrphanedWorktrees(repoRoot) {
|
|
709
|
+
const pruned = [];
|
|
710
|
+
const cwd = process.cwd();
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
// 1. Get all worktrees in porcelain format
|
|
714
|
+
const listResult = execGit(repoRoot, ['worktree', 'list', '--porcelain']);
|
|
715
|
+
if (listResult.exitCode !== 0) return pruned;
|
|
716
|
+
|
|
717
|
+
const worktrees = parseWorktreePorcelain(listResult.stdout);
|
|
718
|
+
if (worktrees.length === 0) {
|
|
719
|
+
execGit(repoRoot, ['worktree', 'prune']);
|
|
720
|
+
return pruned;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// 2. First entry is the main worktree — never touch it
|
|
724
|
+
const mainWorktreePath = worktrees[0].path;
|
|
725
|
+
|
|
726
|
+
// 3. Check each non-main worktree
|
|
727
|
+
for (let i = 1; i < worktrees.length; i++) {
|
|
728
|
+
const { path: wtPath, branch } = worktrees[i];
|
|
729
|
+
|
|
730
|
+
// Never remove the worktree for the current process directory
|
|
731
|
+
if (wtPath === cwd || cwd.startsWith(wtPath + path.sep)) continue;
|
|
732
|
+
|
|
733
|
+
// Check if the branch is fully merged into HEAD (main)
|
|
734
|
+
// git merge-base --is-ancestor <branch> HEAD exits 0 when merged
|
|
735
|
+
const ancestorCheck = execGit(repoRoot, [
|
|
736
|
+
'merge-base', '--is-ancestor', branch, 'HEAD',
|
|
737
|
+
]);
|
|
738
|
+
|
|
739
|
+
if (ancestorCheck.exitCode !== 0) {
|
|
740
|
+
// Not yet merged — leave it alone
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Remove the worktree and delete the branch
|
|
745
|
+
const removeResult = execGit(repoRoot, ['worktree', 'remove', '--force', wtPath]);
|
|
746
|
+
if (removeResult.exitCode === 0) {
|
|
747
|
+
execGit(repoRoot, ['branch', '-D', branch]);
|
|
748
|
+
pruned.push(wtPath);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} catch { /* never crash the caller */ }
|
|
752
|
+
|
|
753
|
+
// 4. Always run prune to clear stale references (e.g. manually-deleted dirs)
|
|
754
|
+
execGit(repoRoot, ['worktree', 'prune']);
|
|
755
|
+
|
|
756
|
+
return pruned;
|
|
757
|
+
}
|
|
758
|
+
|
|
633
759
|
/**
|
|
634
760
|
* Acquire a file-based lock for .planning/ writes.
|
|
635
761
|
* Prevents concurrent worktrees from corrupting shared planning files.
|
|
@@ -653,10 +779,15 @@ function withPlanningLock(cwd, fn) {
|
|
|
653
779
|
acquired: new Date().toISOString(),
|
|
654
780
|
}), { flag: 'wx' });
|
|
655
781
|
|
|
782
|
+
// Register for exit-time cleanup so process.exit(1) inside a locked region
|
|
783
|
+
// cannot leave a stale lock file (#1916).
|
|
784
|
+
_heldPlanningLocks.add(lockPath);
|
|
785
|
+
|
|
656
786
|
// Lock acquired — run the function
|
|
657
787
|
try {
|
|
658
788
|
return fn();
|
|
659
789
|
} finally {
|
|
790
|
+
_heldPlanningLocks.delete(lockPath);
|
|
660
791
|
try { fs.unlinkSync(lockPath); } catch { /* already released */ }
|
|
661
792
|
}
|
|
662
793
|
} catch (err) {
|
|
@@ -725,19 +856,23 @@ function planningRoot(cwd) {
|
|
|
725
856
|
}
|
|
726
857
|
|
|
727
858
|
/**
|
|
728
|
-
* Get common .planning file paths, workstream-aware.
|
|
729
|
-
*
|
|
730
|
-
*
|
|
859
|
+
* Get common .planning file paths, project-and-workstream-aware.
|
|
860
|
+
*
|
|
861
|
+
* All paths route through planningDir(cwd, ws), which honors the GSD_PROJECT
|
|
862
|
+
* env var and active workstream. This matches loadConfig() above (line 256),
|
|
863
|
+
* which has always read config.json via planningDir(cwd). Previously project
|
|
864
|
+
* and config were resolved against the unrouted .planning/ root, which broke
|
|
865
|
+
* `gsd-tools config-get` in multi-project layouts (the CRUD writers and the
|
|
866
|
+
* reader pointed at different files).
|
|
731
867
|
*/
|
|
732
868
|
function planningPaths(cwd, ws) {
|
|
733
869
|
const base = planningDir(cwd, ws);
|
|
734
|
-
const root = path.join(cwd, '.planning');
|
|
735
870
|
return {
|
|
736
871
|
planning: base,
|
|
737
872
|
state: path.join(base, 'STATE.md'),
|
|
738
873
|
roadmap: path.join(base, 'ROADMAP.md'),
|
|
739
|
-
project: path.join(
|
|
740
|
-
config: path.join(
|
|
874
|
+
project: path.join(base, 'PROJECT.md'),
|
|
875
|
+
config: path.join(base, 'config.json'),
|
|
741
876
|
phases: path.join(base, 'phases'),
|
|
742
877
|
requirements: path.join(base, 'REQUIREMENTS.md'),
|
|
743
878
|
};
|
|
@@ -934,7 +1069,10 @@ function normalizePhaseName(phase) {
|
|
|
934
1069
|
const match = stripped.match(/^(\d+)([A-Z])?((?:\.\d+)*)/i);
|
|
935
1070
|
if (match) {
|
|
936
1071
|
const padded = match[1].padStart(2, '0');
|
|
937
|
-
|
|
1072
|
+
// Preserve original case of letter suffix (#1962).
|
|
1073
|
+
// Uppercasing causes directory/roadmap mismatches on case-sensitive filesystems
|
|
1074
|
+
// (e.g., "16c" in ROADMAP.md → directory "16C-name" → progress can't match).
|
|
1075
|
+
const letter = match[2] || '';
|
|
938
1076
|
const decimal = match[3] || '';
|
|
939
1077
|
return padded + letter + decimal;
|
|
940
1078
|
}
|
|
@@ -1540,6 +1678,64 @@ function readSubdirectories(dirPath, sort = false) {
|
|
|
1540
1678
|
}
|
|
1541
1679
|
}
|
|
1542
1680
|
|
|
1681
|
+
// ─── Atomic file writes ───────────────────────────────────────────────────────
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* Write a file atomically using write-to-temp-then-rename.
|
|
1685
|
+
*
|
|
1686
|
+
* On POSIX systems, `fs.renameSync` is atomic when the source and destination
|
|
1687
|
+
* are on the same filesystem. This prevents a process killed mid-write from
|
|
1688
|
+
* leaving a truncated file that is unparseable on next read.
|
|
1689
|
+
*
|
|
1690
|
+
* The temp file is placed alongside the target so it is guaranteed to be on
|
|
1691
|
+
* the same filesystem (required for rename atomicity). The PID is embedded in
|
|
1692
|
+
* the temp file name so concurrent writers use distinct paths.
|
|
1693
|
+
*
|
|
1694
|
+
* If `renameSync` fails (e.g. cross-device move), the function falls back to a
|
|
1695
|
+
* direct `writeFileSync` so callers always get a best-effort write.
|
|
1696
|
+
*
|
|
1697
|
+
* @param {string} filePath Absolute path to write.
|
|
1698
|
+
* @param {string|Buffer} content File content.
|
|
1699
|
+
* @param {string} [encoding='utf-8'] Encoding passed to writeFileSync.
|
|
1700
|
+
*/
|
|
1701
|
+
function atomicWriteFileSync(filePath, content, encoding = 'utf-8') {
|
|
1702
|
+
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
1703
|
+
try {
|
|
1704
|
+
fs.writeFileSync(tmpPath, content, encoding);
|
|
1705
|
+
fs.renameSync(tmpPath, filePath);
|
|
1706
|
+
} catch (renameErr) {
|
|
1707
|
+
// Clean up the temp file if rename failed, then fall back to direct write.
|
|
1708
|
+
try { fs.unlinkSync(tmpPath); } catch { /* already gone or never created */ }
|
|
1709
|
+
fs.writeFileSync(filePath, content, encoding);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* Format a Date as a fuzzy relative time string (e.g. "5 minutes ago").
|
|
1715
|
+
* @param {Date} date
|
|
1716
|
+
* @returns {string}
|
|
1717
|
+
*/
|
|
1718
|
+
function timeAgo(date) {
|
|
1719
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
1720
|
+
if (seconds < 5) return 'just now';
|
|
1721
|
+
if (seconds < 60) return `${seconds} seconds ago`;
|
|
1722
|
+
const minutes = Math.floor(seconds / 60);
|
|
1723
|
+
if (minutes === 1) return '1 minute ago';
|
|
1724
|
+
if (minutes < 60) return `${minutes} minutes ago`;
|
|
1725
|
+
const hours = Math.floor(minutes / 60);
|
|
1726
|
+
if (hours === 1) return '1 hour ago';
|
|
1727
|
+
if (hours < 24) return `${hours} hours ago`;
|
|
1728
|
+
const days = Math.floor(hours / 24);
|
|
1729
|
+
if (days === 1) return '1 day ago';
|
|
1730
|
+
if (days < 30) return `${days} days ago`;
|
|
1731
|
+
const months = Math.floor(days / 30);
|
|
1732
|
+
if (months === 1) return '1 month ago';
|
|
1733
|
+
if (months < 12) return `${months} months ago`;
|
|
1734
|
+
const years = Math.floor(days / 365);
|
|
1735
|
+
if (years === 1) return '1 year ago';
|
|
1736
|
+
return `${years} years ago`;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1543
1739
|
module.exports = {
|
|
1544
1740
|
parseIncludeFlag,
|
|
1545
1741
|
discoverPhaseArtifacts,
|
|
@@ -1576,6 +1772,7 @@ module.exports = {
|
|
|
1576
1772
|
findProjectRoot,
|
|
1577
1773
|
detectSubRepos,
|
|
1578
1774
|
reapStaleTempFiles,
|
|
1775
|
+
GSD_TEMP_DIR,
|
|
1579
1776
|
MODEL_ALIAS_MAP,
|
|
1580
1777
|
CONFIG_DEFAULTS,
|
|
1581
1778
|
planningDir,
|
|
@@ -1589,4 +1786,7 @@ module.exports = {
|
|
|
1589
1786
|
readSubdirectories,
|
|
1590
1787
|
getAgentsDir,
|
|
1591
1788
|
checkAgentsInstalled,
|
|
1789
|
+
atomicWriteFileSync,
|
|
1790
|
+
timeAgo,
|
|
1791
|
+
pruneOrphanedWorktrees,
|
|
1592
1792
|
};
|