gsd-antigravity-kit 1.27.3 → 1.30.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 +2 -2
- package/.agent/skills/gsd/assets/templates/config.json +2 -1
- package/.agent/skills/gsd/bin/gsd-tools.cjs +20 -1
- package/.agent/skills/gsd/bin/help-manifest.json +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-check-update.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-context-monitor.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-prompt-guard.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-statusline.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-workflow-guard.js +1 -1
- package/.agent/skills/gsd/bin/lib/config.cjs +24 -3
- package/.agent/skills/gsd/bin/lib/core.cjs +67 -3
- package/.agent/skills/gsd/bin/lib/frontmatter.cjs +56 -27
- package/.agent/skills/gsd/bin/lib/init.cjs +109 -3
- package/.agent/skills/gsd/bin/lib/security.cjs +26 -0
- package/.agent/skills/gsd/bin/lib/state.cjs +67 -5
- package/.agent/skills/gsd/bin/lib/uat.cjs +94 -1
- package/.agent/skills/gsd/bin/lib/verify.cjs +38 -1
- package/.agent/skills/gsd/references/agents/gsd-debugger.md +1 -0
- package/.agent/skills/gsd/references/agents/gsd-executor.md +1 -0
- package/.agent/skills/gsd/references/agents/gsd-verifier.md +1 -1
- package/.agent/skills/gsd/references/commands/debug.md +5 -0
- package/.agent/skills/gsd/references/commands/research-phase.md +5 -0
- package/.agent/skills/gsd/references/workflows/add-tests.md +2 -2
- package/.agent/skills/gsd/references/workflows/add-todo.md +1 -1
- package/.agent/skills/gsd/references/workflows/audit-milestone.md +9 -1
- package/.agent/skills/gsd/references/workflows/autonomous.md +85 -9
- package/.agent/skills/gsd/references/workflows/cleanup.md +2 -2
- package/.agent/skills/gsd/references/workflows/complete-milestone.md +4 -3
- package/.agent/skills/gsd/references/workflows/diagnose-issues.md +12 -1
- package/.agent/skills/gsd/references/workflows/discuss-phase-assumptions.md +14 -6
- package/.agent/skills/gsd/references/workflows/discuss-phase.md +13 -11
- package/.agent/skills/gsd/references/workflows/execute-phase.md +9 -1
- package/.agent/skills/gsd/references/workflows/execute-plan.md +10 -5
- package/.agent/skills/gsd/references/workflows/health.md +1 -1
- package/.agent/skills/gsd/references/workflows/manager.md +2 -0
- package/.agent/skills/gsd/references/workflows/map-codebase.md +16 -9
- package/.agent/skills/gsd/references/workflows/new-milestone.md +17 -0
- package/.agent/skills/gsd/references/workflows/new-project.md +24 -0
- package/.agent/skills/gsd/references/workflows/pause-work.md +2 -2
- package/.agent/skills/gsd/references/workflows/plan-milestone-gaps.md +1 -1
- package/.agent/skills/gsd/references/workflows/plan-phase.md +13 -2
- package/.agent/skills/gsd/references/workflows/plant-seed.md +1 -1
- package/.agent/skills/gsd/references/workflows/progress.md +5 -5
- package/.agent/skills/gsd/references/workflows/quick.md +25 -0
- package/.agent/skills/gsd/references/workflows/research-phase.md +9 -1
- package/.agent/skills/gsd/references/workflows/resume-project.md +5 -4
- package/.agent/skills/gsd/references/workflows/review.md +1 -1
- package/.agent/skills/gsd/references/workflows/transition.md +7 -7
- package/.agent/skills/gsd/references/workflows/ui-phase.md +12 -0
- package/.agent/skills/gsd/references/workflows/ui-review.md +8 -0
- package/.agent/skills/gsd/references/workflows/update.md +1 -1
- package/.agent/skills/gsd/references/workflows/validate-phase.md +8 -1
- package/.agent/skills/gsd/references/workflows/verify-phase.md +3 -3
- package/.agent/skills/gsd/references/workflows/verify-work.md +29 -15
- package/README.md +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.30.0
|
|
4
4
|
description: "Antigravity GSD (Get Stuff Done) - A spec-driven hierarchical planning and execution system. Triggers on project planning, phase management, and GSD slash commands."
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -183,4 +183,4 @@ General documentation on the GSD philosophy, usage patterns, and configuration.
|
|
|
183
183
|
5. **CLI Invocation**: `gsd-tools` is **NOT** a global command. Always invoke it with the full node path: `node .agent/skills/gsd/bin/gsd-tools.cjs <command> [args]`. Never run `gsd-tools` bare.
|
|
184
184
|
|
|
185
185
|
---
|
|
186
|
-
*Generated by gsd-converter on 2026-03-
|
|
186
|
+
*Generated by gsd-converter on 2026-03-28*
|
|
@@ -458,6 +458,11 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
458
458
|
break;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
case 'agent-skills': {
|
|
462
|
+
init.cmdAgentSkills(cwd, args[1], raw);
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
|
|
461
466
|
case 'history-digest': {
|
|
462
467
|
commands.cmdHistoryDigest(cwd, raw);
|
|
463
468
|
break;
|
|
@@ -553,8 +558,10 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
553
558
|
} else if (subcommand === 'health') {
|
|
554
559
|
const repairFlag = args.includes('--repair');
|
|
555
560
|
verify.cmdValidateHealth(cwd, { repair: repairFlag }, raw);
|
|
561
|
+
} else if (subcommand === 'agents') {
|
|
562
|
+
verify.cmdValidateAgents(cwd, raw);
|
|
556
563
|
} else {
|
|
557
|
-
error('Unknown validate subcommand. Available: consistency, health');
|
|
564
|
+
error('Unknown validate subcommand. Available: consistency, health, agents');
|
|
558
565
|
}
|
|
559
566
|
break;
|
|
560
567
|
}
|
|
@@ -571,6 +578,18 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
571
578
|
break;
|
|
572
579
|
}
|
|
573
580
|
|
|
581
|
+
case 'uat': {
|
|
582
|
+
const subcommand = args[1];
|
|
583
|
+
const uat = require('./lib/uat.cjs');
|
|
584
|
+
if (subcommand === 'render-checkpoint') {
|
|
585
|
+
const options = parseNamedArgs(args, ['file']);
|
|
586
|
+
uat.cmdRenderCheckpoint(cwd, options, raw);
|
|
587
|
+
} else {
|
|
588
|
+
error('Unknown uat subcommand. Available: render-checkpoint');
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
|
|
574
593
|
case 'stats': {
|
|
575
594
|
const subcommand = args[1] || 'json';
|
|
576
595
|
commands.cmdStats(cwd, subcommand, raw);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// gsd-hook-version: 1.
|
|
2
|
+
// gsd-hook-version: 1.30.0
|
|
3
3
|
// Context Monitor - PostToolUse/AfterTool hook (Gemini uses AfterTool)
|
|
4
4
|
// Reads context metrics from the statusline bridge file and injects
|
|
5
5
|
// warnings when context usage is high. This makes the AGENT aware of
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// gsd-hook-version: 1.
|
|
2
|
+
// gsd-hook-version: 1.30.0
|
|
3
3
|
// GSD Prompt Injection Guard — PreToolUse hook
|
|
4
4
|
// Scans file content being written to .planning/ for prompt injection patterns.
|
|
5
5
|
// Defense-in-depth: catches injected instructions before they enter agent context.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// gsd-hook-version: 1.
|
|
2
|
+
// gsd-hook-version: 1.30.0
|
|
3
3
|
// GSD Workflow Guard — PreToolUse hook
|
|
4
4
|
// Detects when Antigravity attempts file edits outside a GSD workflow context
|
|
5
5
|
// (no active /gsd: command or Task subagent) and injects an advisory warning.
|
|
@@ -27,6 +27,18 @@ const VALID_CONFIG_KEYS = new Set([
|
|
|
27
27
|
'hooks.context_warnings',
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Check whether a config key path is valid.
|
|
32
|
+
* Supports exact matches from VALID_CONFIG_KEYS plus dynamic patterns
|
|
33
|
+
* like `agent_skills.<agent-type>` where the sub-key is freeform.
|
|
34
|
+
*/
|
|
35
|
+
function isValidConfigKey(keyPath) {
|
|
36
|
+
if (VALID_CONFIG_KEYS.has(keyPath)) return true;
|
|
37
|
+
// Allow agent_skills.<agent-type> with any agent type string
|
|
38
|
+
if (/^agent_skills\.[a-zA-Z0-9_-]+$/.test(keyPath)) return true;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
const CONFIG_KEY_SUGGESTIONS = {
|
|
31
43
|
'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
|
32
44
|
'agents.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
|
@@ -120,6 +132,7 @@ function buildNewProjectConfig(userChoices) {
|
|
|
120
132
|
hooks: {
|
|
121
133
|
context_warnings: true,
|
|
122
134
|
},
|
|
135
|
+
agent_skills: {},
|
|
123
136
|
};
|
|
124
137
|
|
|
125
138
|
// Three-level deep merge: hardcoded <- userDefaults <- choices
|
|
@@ -142,6 +155,11 @@ function buildNewProjectConfig(userChoices) {
|
|
|
142
155
|
...(userDefaults.hooks || {}),
|
|
143
156
|
...(choices.hooks || {}),
|
|
144
157
|
},
|
|
158
|
+
agent_skills: {
|
|
159
|
+
...hardcoded.agent_skills,
|
|
160
|
+
...(userDefaults.agent_skills || {}),
|
|
161
|
+
...(choices.agent_skills || {}),
|
|
162
|
+
},
|
|
145
163
|
};
|
|
146
164
|
}
|
|
147
165
|
|
|
@@ -298,15 +316,18 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
298
316
|
|
|
299
317
|
validateKnownConfigKeyPath(keyPath);
|
|
300
318
|
|
|
301
|
-
if (!
|
|
302
|
-
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}
|
|
319
|
+
if (!isValidConfigKey(keyPath)) {
|
|
320
|
+
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>`);
|
|
303
321
|
}
|
|
304
322
|
|
|
305
|
-
// Parse value (handle booleans and
|
|
323
|
+
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
|
306
324
|
let parsedValue = value;
|
|
307
325
|
if (value === 'true') parsedValue = true;
|
|
308
326
|
else if (value === 'false') parsedValue = false;
|
|
309
327
|
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
|
328
|
+
else if (typeof value === 'string' && (value.startsWith('[') || value.startsWith('{'))) {
|
|
329
|
+
try { parsedValue = JSON.parse(value); } catch { /* keep as string */ }
|
|
330
|
+
}
|
|
310
331
|
|
|
311
332
|
const setConfigValueResult = setConfigValue(cwd, keyPath, parsedValue);
|
|
312
333
|
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
|
@@ -58,13 +58,22 @@ function findProjectRoot(startDir) {
|
|
|
58
58
|
const root = path.parse(resolved).root;
|
|
59
59
|
const homedir = require('os').homedir();
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// If startDir already contains .planning/, it IS the project root.
|
|
62
|
+
// Do not walk up to a parent workspace that also has .planning/ (#1362).
|
|
63
|
+
const ownPlanning = path.join(resolved, '.planning');
|
|
64
|
+
if (fs.existsSync(ownPlanning) && fs.statSync(ownPlanning).isDirectory()) {
|
|
65
|
+
return startDir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if startDir or any of its ancestors (up to AND including the
|
|
62
69
|
// candidate project root) contains a .git directory. This handles both
|
|
63
|
-
// `backend/` (direct sub-repo) and `backend/src/modules/` (nested inside)
|
|
70
|
+
// `backend/` (direct sub-repo) and `backend/src/modules/` (nested inside),
|
|
71
|
+
// as well as the common case where .git lives at the same level as .planning/.
|
|
64
72
|
function isInsideGitRepo(candidateParent) {
|
|
65
73
|
let d = resolved;
|
|
66
|
-
while (d !==
|
|
74
|
+
while (d !== root) {
|
|
67
75
|
if (fs.existsSync(path.join(d, '.git'))) return true;
|
|
76
|
+
if (d === candidateParent) break;
|
|
68
77
|
d = path.dirname(d);
|
|
69
78
|
}
|
|
70
79
|
return false;
|
|
@@ -355,6 +364,7 @@ function loadConfig(cwd) {
|
|
|
355
364
|
context_window: get('context_window') ?? defaults.context_window,
|
|
356
365
|
phase_naming: get('phase_naming') ?? defaults.phase_naming,
|
|
357
366
|
model_overrides: parsed.model_overrides || null,
|
|
367
|
+
agent_skills: parsed.agent_skills || {},
|
|
358
368
|
};
|
|
359
369
|
} catch {
|
|
360
370
|
return defaults;
|
|
@@ -977,6 +987,58 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
977
987
|
}
|
|
978
988
|
}
|
|
979
989
|
|
|
990
|
+
// ─── Agent installation validation (#1371) ───────────────────────────────────
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Resolve the agents directory from the GSD install location.
|
|
994
|
+
* gsd-tools.cjs lives at <configDir>/get-shit-done/bin/gsd-tools.cjs,
|
|
995
|
+
* so agents/ is at <configDir>/agents/.
|
|
996
|
+
*
|
|
997
|
+
* @returns {string} Absolute path to the agents directory
|
|
998
|
+
*/
|
|
999
|
+
function getAgentsDir() {
|
|
1000
|
+
// __dirname is get-shit-done/bin/lib/ → go up 3 levels to configDir
|
|
1001
|
+
return path.join(__dirname, '..', '..', '..', 'agents');
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Check which GSD agents are installed on disk.
|
|
1006
|
+
* Returns an object with installation status and details.
|
|
1007
|
+
*
|
|
1008
|
+
* @returns {{ agents_installed: boolean, missing_agents: string[], installed_agents: string[], agents_dir: string }}
|
|
1009
|
+
*/
|
|
1010
|
+
function checkAgentsInstalled() {
|
|
1011
|
+
const agentsDir = getAgentsDir();
|
|
1012
|
+
const expectedAgents = Object.keys(MODEL_PROFILES);
|
|
1013
|
+
const installed = [];
|
|
1014
|
+
const missing = [];
|
|
1015
|
+
|
|
1016
|
+
if (!fs.existsSync(agentsDir)) {
|
|
1017
|
+
return {
|
|
1018
|
+
agents_installed: false,
|
|
1019
|
+
missing_agents: expectedAgents,
|
|
1020
|
+
installed_agents: [],
|
|
1021
|
+
agents_dir: agentsDir,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
for (const agent of expectedAgents) {
|
|
1026
|
+
const agentFile = path.join(agentsDir, `${agent}.md`);
|
|
1027
|
+
if (fs.existsSync(agentFile)) {
|
|
1028
|
+
installed.push(agent);
|
|
1029
|
+
} else {
|
|
1030
|
+
missing.push(agent);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return {
|
|
1035
|
+
agents_installed: installed.length > 0 && missing.length === 0,
|
|
1036
|
+
missing_agents: missing,
|
|
1037
|
+
installed_agents: installed,
|
|
1038
|
+
agents_dir: agentsDir,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
980
1042
|
// ─── Model alias resolution ───────────────────────────────────────────────────
|
|
981
1043
|
|
|
982
1044
|
/**
|
|
@@ -1222,4 +1284,6 @@ module.exports = {
|
|
|
1222
1284
|
filterSummaryFiles,
|
|
1223
1285
|
getPhaseFileStats,
|
|
1224
1286
|
readSubdirectories,
|
|
1287
|
+
getAgentsDir,
|
|
1288
|
+
checkAgentsInstalled,
|
|
1225
1289
|
};
|
|
@@ -167,57 +167,86 @@ function parseMustHavesBlock(content, blockName) {
|
|
|
167
167
|
if (!fmMatch) return [];
|
|
168
168
|
|
|
169
169
|
const yaml = fmMatch[1];
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
170
|
+
|
|
171
|
+
// Find must_haves: first to detect its indentation level
|
|
172
|
+
const mustHavesMatch = yaml.match(/^(\s*)must_haves:\s*$/m);
|
|
173
|
+
if (!mustHavesMatch) return [];
|
|
174
|
+
const mustHavesIndent = mustHavesMatch[1].length;
|
|
175
|
+
|
|
176
|
+
// Find the block (e.g., "truths:", "artifacts:", "key_links:") under must_haves
|
|
177
|
+
// It must be indented more than must_haves but we detect the actual indent dynamically
|
|
178
|
+
const blockPattern = new RegExp(`^(\\s+)${blockName}:\\s*$`, 'm');
|
|
179
|
+
const blockMatch = yaml.match(blockPattern);
|
|
180
|
+
if (!blockMatch) return [];
|
|
181
|
+
|
|
182
|
+
const blockIndent = blockMatch[1].length;
|
|
183
|
+
// The block must be nested under must_haves (more indented)
|
|
184
|
+
if (blockIndent <= mustHavesIndent) return [];
|
|
185
|
+
|
|
186
|
+
// Find where the block starts in the yaml string
|
|
187
|
+
const blockStart = yaml.indexOf(blockMatch[0]);
|
|
173
188
|
if (blockStart === -1) return [];
|
|
174
189
|
|
|
175
190
|
const afterBlock = yaml.slice(blockStart);
|
|
176
191
|
const blockLines = afterBlock.split(/\r?\n/).slice(1); // skip the header line
|
|
177
192
|
|
|
193
|
+
// List items are indented one level deeper than blockIndent
|
|
194
|
+
// Continuation KVs are indented one level deeper than list items
|
|
178
195
|
const items = [];
|
|
179
196
|
let current = null;
|
|
197
|
+
let listItemIndent = -1; // detected from first "- " line
|
|
180
198
|
|
|
181
199
|
for (const line of blockLines) {
|
|
182
|
-
//
|
|
200
|
+
// Skip empty lines
|
|
183
201
|
if (line.trim() === '') continue;
|
|
184
202
|
const indent = line.match(/^(\s*)/)[1].length;
|
|
185
|
-
|
|
203
|
+
// Stop at same or lower indent level than the block header
|
|
204
|
+
if (indent <= blockIndent && line.trim() !== '') break;
|
|
205
|
+
|
|
206
|
+
const trimmed = line.trim();
|
|
186
207
|
|
|
187
|
-
if (
|
|
188
|
-
//
|
|
208
|
+
if (trimmed.startsWith('- ')) {
|
|
209
|
+
// Detect list item indent from the first occurrence
|
|
210
|
+
if (listItemIndent === -1) listItemIndent = indent;
|
|
211
|
+
|
|
212
|
+
// Only treat as a top-level list item if at the expected indent
|
|
213
|
+
if (indent === listItemIndent) {
|
|
189
214
|
if (current) items.push(current);
|
|
190
215
|
current = {};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
216
|
+
const afterDash = trimmed.slice(2);
|
|
217
|
+
// Check if it's a simple string item (no colon means not a key-value)
|
|
218
|
+
if (!afterDash.includes(':')) {
|
|
219
|
+
current = afterDash.replace(/^["']|["']$/g, '');
|
|
195
220
|
} else {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
221
|
+
// Key-value on same line as dash: "- path: value"
|
|
222
|
+
const kvMatch = afterDash.match(/^(\w+):\s*"?([^"]*)"?\s*$/);
|
|
223
|
+
if (kvMatch) {
|
|
199
224
|
current = {};
|
|
200
225
|
current[kvMatch[1]] = kvMatch[2];
|
|
226
|
+
}
|
|
201
227
|
}
|
|
228
|
+
continue;
|
|
202
229
|
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
// Array items under a key
|
|
212
|
-
const arrMatch = line.match(/^\s{10,}-\s+"?([^"]+)"?\s*$/);
|
|
213
|
-
if (arrMatch) {
|
|
214
|
-
// Find the last key added and convert to array
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (current && typeof current === 'object' && indent > listItemIndent) {
|
|
233
|
+
// Continuation key-value or nested array item
|
|
234
|
+
if (trimmed.startsWith('- ')) {
|
|
235
|
+
// Array item under a key
|
|
236
|
+
const arrVal = trimmed.slice(2).replace(/^["']|["']$/g, '');
|
|
215
237
|
const keys = Object.keys(current);
|
|
216
238
|
const lastKey = keys[keys.length - 1];
|
|
217
239
|
if (lastKey && !Array.isArray(current[lastKey])) {
|
|
218
240
|
current[lastKey] = current[lastKey] ? [current[lastKey]] : [];
|
|
219
241
|
}
|
|
220
|
-
if (lastKey) current[lastKey].push(
|
|
242
|
+
if (lastKey) current[lastKey].push(arrVal);
|
|
243
|
+
} else {
|
|
244
|
+
const kvMatch = trimmed.match(/^(\w+):\s*"?([^"]*)"?\s*$/);
|
|
245
|
+
if (kvMatch) {
|
|
246
|
+
const val = kvMatch[2];
|
|
247
|
+
// Try to parse as number
|
|
248
|
+
current[kvMatch[1]] = /^\d+$/.test(val) ? parseInt(val, 10) : val;
|
|
249
|
+
}
|
|
221
250
|
}
|
|
222
251
|
}
|
|
223
252
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
|
-
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, normalizePhaseName, planningPaths, planningDir, planningRoot, toPosixPath, output, error , buildPhaseBase, applyIncludes } = require('./core.cjs');
|
|
8
|
+
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, normalizePhaseName, planningPaths, planningDir, planningRoot, toPosixPath, output, error, checkAgentsInstalled , buildPhaseBase, applyIncludes } = require('./core.cjs');
|
|
9
9
|
|
|
10
10
|
function getLatestCompletedMilestone(cwd) {
|
|
11
11
|
const milestonesPath = path.join(planningRoot(cwd), 'MILESTONES.md');
|
|
@@ -31,6 +31,12 @@ function getLatestCompletedMilestone(cwd) {
|
|
|
31
31
|
*/
|
|
32
32
|
function withProjectRoot(cwd, result) {
|
|
33
33
|
result.project_root = cwd;
|
|
34
|
+
// Inject agent installation status into all init outputs (#1371).
|
|
35
|
+
// Workflows that spawn named subagents use this to detect when agents
|
|
36
|
+
// are missing and would silently fall back to general-purpose.
|
|
37
|
+
const agentStatus = checkAgentsInstalled();
|
|
38
|
+
result.agents_installed = agentStatus.agents_installed;
|
|
39
|
+
result.missing_agents = agentStatus.missing_agents;
|
|
34
40
|
return result;
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -242,7 +248,23 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
242
248
|
let hasCode = false;
|
|
243
249
|
let hasPackageFile = false;
|
|
244
250
|
try {
|
|
245
|
-
const codeExtensions = new Set([
|
|
251
|
+
const codeExtensions = new Set([
|
|
252
|
+
'.ts', '.js', '.py', '.go', '.rs', '.swift', '.java',
|
|
253
|
+
'.kt', '.kts', // Kotlin (Android, server-side)
|
|
254
|
+
'.c', '.cpp', '.h', // C/C++
|
|
255
|
+
'.cs', // C#
|
|
256
|
+
'.rb', // Ruby
|
|
257
|
+
'.php', // PHP
|
|
258
|
+
'.dart', // Dart (Flutter)
|
|
259
|
+
'.m', '.mm', // Objective-C / Objective-C++
|
|
260
|
+
'.scala', // Scala
|
|
261
|
+
'.groovy', // Groovy (Gradle build scripts)
|
|
262
|
+
'.lua', // Lua
|
|
263
|
+
'.r', '.R', // R
|
|
264
|
+
'.zig', // Zig
|
|
265
|
+
'.ex', '.exs', // Elixir
|
|
266
|
+
'.clj', // Clojure
|
|
267
|
+
]);
|
|
246
268
|
const skipDirs = new Set(['node_modules', '.git', '.planning', '.antigravity', '__pycache__', 'target', 'dist', 'build']);
|
|
247
269
|
function findCodeFiles(dir, depth) {
|
|
248
270
|
if (depth > 3) return false;
|
|
@@ -263,7 +285,18 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
263
285
|
pathExistsInternal(cwd, 'requirements.txt') ||
|
|
264
286
|
pathExistsInternal(cwd, 'Cargo.toml') ||
|
|
265
287
|
pathExistsInternal(cwd, 'go.mod') ||
|
|
266
|
-
pathExistsInternal(cwd, 'Package.swift')
|
|
288
|
+
pathExistsInternal(cwd, 'Package.swift') ||
|
|
289
|
+
pathExistsInternal(cwd, 'build.gradle') ||
|
|
290
|
+
pathExistsInternal(cwd, 'build.gradle.kts') ||
|
|
291
|
+
pathExistsInternal(cwd, 'pom.xml') ||
|
|
292
|
+
pathExistsInternal(cwd, 'Gemfile') ||
|
|
293
|
+
pathExistsInternal(cwd, 'composer.json') ||
|
|
294
|
+
pathExistsInternal(cwd, 'pubspec.yaml') ||
|
|
295
|
+
pathExistsInternal(cwd, 'CMakeLists.txt') ||
|
|
296
|
+
pathExistsInternal(cwd, 'Makefile') ||
|
|
297
|
+
pathExistsInternal(cwd, 'build.zig') ||
|
|
298
|
+
pathExistsInternal(cwd, 'mix.exs') ||
|
|
299
|
+
pathExistsInternal(cwd, 'project.clj');
|
|
267
300
|
|
|
268
301
|
const result = {
|
|
269
302
|
// Models
|
|
@@ -1301,6 +1334,77 @@ function cmdInitRemoveWorkspace(cwd, name, raw) {
|
|
|
1301
1334
|
output(result, raw);
|
|
1302
1335
|
}
|
|
1303
1336
|
|
|
1337
|
+
/**
|
|
1338
|
+
* Build a formatted agent skills block for injection into Task() prompts.
|
|
1339
|
+
*
|
|
1340
|
+
* Reads `config.agent_skills[agentType]` and validates each skill path exists
|
|
1341
|
+
* within the project root. Returns a formatted `<agent_skills>` block or empty
|
|
1342
|
+
* string if no skills are configured.
|
|
1343
|
+
*
|
|
1344
|
+
* @param {object} config - Loaded project config
|
|
1345
|
+
* @param {string} agentType - The agent type (e.g., 'gsd-executor', 'gsd-planner')
|
|
1346
|
+
* @param {string} projectRoot - Absolute path to project root (for path validation)
|
|
1347
|
+
* @returns {string} Formatted skills block or empty string
|
|
1348
|
+
*/
|
|
1349
|
+
function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
1350
|
+
const { validatePath } = require('./security.cjs');
|
|
1351
|
+
|
|
1352
|
+
if (!config || !config.agent_skills || !agentType) return '';
|
|
1353
|
+
|
|
1354
|
+
let skillPaths = config.agent_skills[agentType];
|
|
1355
|
+
if (!skillPaths) return '';
|
|
1356
|
+
|
|
1357
|
+
// Normalize single string to array
|
|
1358
|
+
if (typeof skillPaths === 'string') skillPaths = [skillPaths];
|
|
1359
|
+
if (!Array.isArray(skillPaths) || skillPaths.length === 0) return '';
|
|
1360
|
+
|
|
1361
|
+
const validPaths = [];
|
|
1362
|
+
for (const skillPath of skillPaths) {
|
|
1363
|
+
if (typeof skillPath !== 'string') continue;
|
|
1364
|
+
|
|
1365
|
+
// Validate path safety — must resolve within project root
|
|
1366
|
+
const pathCheck = validatePath(skillPath, projectRoot);
|
|
1367
|
+
if (!pathCheck.safe) {
|
|
1368
|
+
process.stderr.write(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}": ${pathCheck.error}\n`);
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Check that the skill directory and SKILL.md exist
|
|
1373
|
+
const skillMdPath = path.join(projectRoot, skillPath, 'SKILL.md');
|
|
1374
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
1375
|
+
process.stderr.write(`[agent-skills] WARNING: Skill not found at "${skillPath}/SKILL.md" — skipping\n`);
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
validPaths.push(skillPath);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (validPaths.length === 0) return '';
|
|
1383
|
+
|
|
1384
|
+
const lines = validPaths.map(p => `- @${p}/SKILL.md`).join('\n');
|
|
1385
|
+
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Command: output the agent skills block for a given agent type.
|
|
1390
|
+
* Used by workflows: SKILLS=$(node "$TOOLS" agent-skills gsd-executor 2>/dev/null)
|
|
1391
|
+
*/
|
|
1392
|
+
function cmdAgentSkills(cwd, agentType, raw) {
|
|
1393
|
+
if (!agentType) {
|
|
1394
|
+
// No agent type — output empty string silently
|
|
1395
|
+
output('', raw, '');
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const config = loadConfig(cwd);
|
|
1400
|
+
const block = buildAgentSkillsBlock(config, agentType, cwd);
|
|
1401
|
+
// Output raw text (not JSON) so workflows can embed it directly
|
|
1402
|
+
if (block) {
|
|
1403
|
+
process.stdout.write(block);
|
|
1404
|
+
}
|
|
1405
|
+
process.exit(0);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1304
1408
|
module.exports = {
|
|
1305
1409
|
cmdInitExecutePhase,
|
|
1306
1410
|
cmdInitPlanPhase,
|
|
@@ -1319,4 +1423,6 @@ module.exports = {
|
|
|
1319
1423
|
cmdInitListWorkspaces,
|
|
1320
1424
|
cmdInitRemoveWorkspace,
|
|
1321
1425
|
detectChildRepos,
|
|
1426
|
+
buildAgentSkillsBlock,
|
|
1427
|
+
cmdAgentSkills,
|
|
1322
1428
|
};
|
|
@@ -223,6 +223,31 @@ function sanitizeForPrompt(text) {
|
|
|
223
223
|
return sanitized;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Sanitize text that will be displayed back to the user.
|
|
228
|
+
* Removes protocol-like leak markers that should never surface in checkpoints.
|
|
229
|
+
*
|
|
230
|
+
* @param {string} text - Text to sanitize
|
|
231
|
+
* @returns {string} Sanitized text
|
|
232
|
+
*/
|
|
233
|
+
function sanitizeForDisplay(text) {
|
|
234
|
+
if (!text || typeof text !== 'string') return text;
|
|
235
|
+
|
|
236
|
+
let sanitized = sanitizeForPrompt(text);
|
|
237
|
+
|
|
238
|
+
const protocolLeakPatterns = [
|
|
239
|
+
/^\s*(?:assistant|user|system)\s+to=[^:\s]+:[^\n]+$/i,
|
|
240
|
+
/^\s*<\|(?:assistant|user|system)[^|]*\|>\s*$/i,
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
sanitized = sanitized
|
|
244
|
+
.split('\n')
|
|
245
|
+
.filter(line => !protocolLeakPatterns.some(pattern => pattern.test(line)))
|
|
246
|
+
.join('\n');
|
|
247
|
+
|
|
248
|
+
return sanitized;
|
|
249
|
+
}
|
|
250
|
+
|
|
226
251
|
// ─── Shell Safety ───────────────────────────────────────────────────────────
|
|
227
252
|
|
|
228
253
|
/**
|
|
@@ -343,6 +368,7 @@ module.exports = {
|
|
|
343
368
|
INJECTION_PATTERNS,
|
|
344
369
|
scanForInjection,
|
|
345
370
|
sanitizeForPrompt,
|
|
371
|
+
sanitizeForDisplay,
|
|
346
372
|
|
|
347
373
|
// Shell safety
|
|
348
374
|
validateShellArg,
|