@opengsd/gsd-core 1.6.0-rc.2 → 1.6.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/.claude-plugin/plugin.json +2 -1
- package/agents/gsd-advisor-researcher.md +2 -0
- package/agents/gsd-ai-researcher.md +2 -0
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-doc-classifier.md +2 -0
- package/agents/gsd-doc-synthesizer.md +2 -0
- package/agents/gsd-domain-researcher.md +2 -0
- package/agents/gsd-eval-auditor.md +6 -9
- package/agents/gsd-phase-researcher.md +2 -0
- package/agents/gsd-planner.md +8 -57
- package/agents/gsd-project-researcher.md +2 -0
- package/agents/gsd-research-synthesizer.md +2 -0
- package/agents/gsd-security-auditor.md +37 -18
- package/agents/gsd-ui-researcher.md +2 -0
- package/bin/install.js +370 -18
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +46 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +52 -14
- package/gsd-core/bin/lib/capability-lifecycle.cjs +30 -7
- package/gsd-core/bin/lib/capability-registry.cjs +96 -83
- package/gsd-core/bin/lib/capability-validator.cjs +22 -0
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +40 -2
- package/gsd-core/bin/lib/command-aliases.cjs +10 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +10 -3
- package/gsd-core/bin/lib/config-schema.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +67 -24
- package/gsd-core/bin/lib/coverage.cjs +464 -0
- package/gsd-core/bin/lib/decisions.cjs +27 -0
- package/gsd-core/bin/lib/eval-command-router.cjs +21 -0
- package/gsd-core/bin/lib/eval.cjs +60 -0
- package/gsd-core/bin/lib/frontmatter.cjs +132 -13
- package/gsd-core/bin/lib/graphify-command-router.cjs +53 -36
- package/gsd-core/bin/lib/init.cjs +139 -30
- package/gsd-core/bin/lib/install-profiles.cjs +6 -3
- package/gsd-core/bin/lib/intel-command-router.cjs +79 -60
- package/gsd-core/bin/lib/io.cjs +1 -0
- package/gsd-core/bin/lib/phase.cjs +16 -2
- package/gsd-core/bin/lib/plan-scan.cjs +2 -2
- package/gsd-core/bin/lib/planning-workspace.cjs +157 -13
- package/gsd-core/bin/lib/profile-output.cjs +18 -6
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +53 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +21 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +15 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +47 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -0
- package/gsd-core/bin/lib/state.cjs +398 -60
- package/gsd-core/bin/lib/surface.cjs +42 -10
- package/gsd-core/bin/lib/uat-predicate.cjs +13 -6
- package/gsd-core/bin/lib/update-context.cjs +2 -2
- package/gsd-core/bin/lib/verification.cjs +67 -6
- package/gsd-core/bin/lib/verify.cjs +8 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/references/planner-guidance.md +66 -0
- package/gsd-core/references/planning-config.md +2 -2
- package/gsd-core/references/security-asvs-levels.md +27 -0
- package/gsd-core/references/untrusted-input-boundary.md +13 -0
- package/gsd-core/templates/SECURITY.md +6 -4
- package/gsd-core/templates/summary-complex.md +4 -0
- package/gsd-core/templates/summary-minimal.md +3 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +41 -0
- package/gsd-core/workflows/autonomous.md +53 -46
- package/gsd-core/workflows/complete-milestone.md +27 -8
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/execute-plan.md +5 -0
- package/gsd-core/workflows/manager.md +17 -7
- package/gsd-core/workflows/new-project.md +82 -16
- package/gsd-core/workflows/plan-phase.md +15 -0
- package/gsd-core/workflows/profile-user.md +6 -2
- package/gsd-core/workflows/progress.md +37 -4
- package/gsd-core/workflows/quick.md +3 -1
- package/gsd-core/workflows/secure-phase.md +13 -7
- package/gsd-core/workflows/ship.md +3 -1
- package/gsd-core/workflows/spec-phase.md +3 -1
- package/gsd-core/workflows/transition.md +14 -12
- package/gsd-core/workflows/ui-review.md +2 -6
- package/gsd-core/workflows/verify-work.md +74 -1
- package/hooks/dist/gsd-read-injection-scanner.js +49 -25
- package/hooks/gsd-read-injection-scanner.js +49 -25
- package/hooks/hooks.json +1 -1
- package/package.json +4 -2
- package/scripts/check-alias-drift.cjs +5 -0
- package/scripts/gen-plugin-skills.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/prompt-injection-scan.sh +9 -0
- package/scripts/release-notes/conventional-title.cjs +88 -0
- package/scripts/release-notes/format-github-release-notes.cjs +4 -3
- package/skills/gsd-add-tests/SKILL.md +38 -0
- package/skills/gsd-ai-integration-phase/SKILL.md +37 -0
- package/skills/gsd-audit-fix/SKILL.md +33 -0
- package/skills/gsd-audit-milestone/SKILL.md +37 -0
- package/skills/gsd-audit-uat/SKILL.md +25 -0
- package/skills/gsd-autonomous/SKILL.md +51 -0
- package/skills/gsd-capture/SKILL.md +67 -0
- package/skills/gsd-cleanup/SKILL.md +24 -0
- package/skills/gsd-code-review/SKILL.md +59 -0
- package/skills/gsd-complete-milestone/SKILL.md +142 -0
- package/skills/gsd-config/SKILL.md +56 -0
- package/skills/gsd-debug/SKILL.md +53 -0
- package/skills/gsd-discuss-phase/SKILL.md +77 -0
- package/skills/gsd-docs-update/SKILL.md +49 -0
- package/skills/gsd-eval-review/SKILL.md +33 -0
- package/skills/gsd-execute-phase/SKILL.md +65 -0
- package/skills/gsd-explore/SKILL.md +28 -0
- package/skills/gsd-extract-learnings/SKILL.md +22 -0
- package/skills/gsd-fast/SKILL.md +31 -0
- package/skills/gsd-forensics/SKILL.md +56 -0
- package/skills/gsd-graphify/SKILL.md +204 -0
- package/skills/gsd-health/SKILL.md +31 -0
- package/skills/gsd-help/SKILL.md +29 -0
- package/skills/gsd-import/SKILL.md +46 -0
- package/skills/gsd-inbox/SKILL.md +39 -0
- package/skills/gsd-ingest-docs/SKILL.md +43 -0
- package/skills/gsd-manager/SKILL.md +45 -0
- package/skills/gsd-map-codebase/SKILL.md +83 -0
- package/skills/gsd-mempalace-capture/SKILL.md +71 -0
- package/skills/gsd-mempalace-recall/SKILL.md +102 -0
- package/skills/gsd-milestone-summary/SKILL.md +51 -0
- package/skills/gsd-mvp-phase/SKILL.md +45 -0
- package/skills/gsd-new-milestone/SKILL.md +45 -0
- package/skills/gsd-new-project/SKILL.md +47 -0
- package/skills/gsd-ns-context/SKILL.md +24 -0
- package/skills/gsd-ns-ideate/SKILL.md +23 -0
- package/skills/gsd-ns-manage/SKILL.md +35 -0
- package/skills/gsd-ns-project/SKILL.md +26 -0
- package/skills/gsd-ns-review/SKILL.md +28 -0
- package/skills/gsd-ns-workflow/SKILL.md +33 -0
- package/skills/gsd-pause-work/SKILL.md +43 -0
- package/skills/gsd-phase/SKILL.md +57 -0
- package/skills/gsd-plan-phase/SKILL.md +63 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +60 -0
- package/skills/gsd-pr-branch/SKILL.md +26 -0
- package/skills/gsd-profile-user/SKILL.md +47 -0
- package/skills/gsd-progress/SKILL.md +49 -0
- package/skills/gsd-quick/SKILL.md +174 -0
- package/skills/gsd-resume-work/SKILL.md +31 -0
- package/skills/gsd-review/SKILL.md +42 -0
- package/skills/gsd-review-backlog/SKILL.md +63 -0
- package/skills/gsd-secure-phase/SKILL.md +36 -0
- package/skills/gsd-settings/SKILL.md +29 -0
- package/skills/gsd-ship/SKILL.md +24 -0
- package/skills/gsd-sketch/SKILL.md +60 -0
- package/skills/gsd-spec-phase/SKILL.md +63 -0
- package/skills/gsd-spike/SKILL.md +57 -0
- package/skills/gsd-stats/SKILL.md +20 -0
- package/skills/gsd-surface/SKILL.md +162 -0
- package/skills/gsd-thread/SKILL.md +24 -0
- package/skills/gsd-ui-phase/SKILL.md +35 -0
- package/skills/gsd-ui-review/SKILL.md +33 -0
- package/skills/gsd-ultraplan-phase/SKILL.md +34 -0
- package/skills/gsd-undo/SKILL.md +35 -0
- package/skills/gsd-update/SKILL.md +50 -0
- package/skills/gsd-validate-phase/SKILL.md +36 -0
- package/skills/gsd-verify-work/SKILL.md +39 -0
- package/skills/gsd-workspace/SKILL.md +53 -0
- package/skills/gsd-workstreams/SKILL.md +70 -0
package/bin/install.js
CHANGED
|
@@ -1517,11 +1517,17 @@ function convertGeminiToolName(claudeTool) {
|
|
|
1517
1517
|
// Task/Agent: exclude — agents are auto-registered as callable tools.
|
|
1518
1518
|
// AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
|
|
1519
1519
|
// emitting it causes frontmatter validation errors (#3362).
|
|
1520
|
+
// Skill/SlashCommand: exclude — Gemini CLI has no 'skill' built-in tool;
|
|
1521
|
+
// the lowercase fallback would emit an invalid 'skill'/'slashcommand' name
|
|
1522
|
+
// that fails frontmatter validation (tools.N: Invalid tool name) and aborts
|
|
1523
|
+
// the entire agent load (#1394).
|
|
1520
1524
|
if (
|
|
1521
1525
|
claudeTool === 'Task' ||
|
|
1522
1526
|
claudeTool === 'Agent' ||
|
|
1523
1527
|
claudeTool === 'AskUserQuestion' ||
|
|
1524
|
-
claudeTool === 'ask_user'
|
|
1528
|
+
claudeTool === 'ask_user' ||
|
|
1529
|
+
claudeTool === 'Skill' ||
|
|
1530
|
+
claudeTool === 'SlashCommand'
|
|
1525
1531
|
) {
|
|
1526
1532
|
return null;
|
|
1527
1533
|
}
|
|
@@ -2451,20 +2457,18 @@ function convertClaudeToWindsurfMarkdown(content) {
|
|
|
2451
2457
|
// Replace subagent_type from Claude to Windsurf format
|
|
2452
2458
|
converted = converted.replace(/subagent_type="general-purpose"/g, 'subagent_type="generalPurpose"');
|
|
2453
2459
|
converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
|
|
2454
|
-
// Replace project-level Claude conventions with Windsurf
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
converted = converted.replace(
|
|
2458
|
-
converted = converted.replace(
|
|
2459
|
-
converted = converted.replace(
|
|
2460
|
-
converted = converted.replace(
|
|
2461
|
-
converted = converted.replace(/\.claude
|
|
2462
|
-
converted = converted.replace(/\.\/\.claude\//g, './.devin/');
|
|
2463
|
-
converted = converted.replace(/\.claude\//g, '.devin/');
|
|
2460
|
+
// Replace project-level Claude conventions with Windsurf equivalents.
|
|
2461
|
+
converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.windsurf/rules`');
|
|
2462
|
+
converted = converted.replace(/\.\/CLAUDE\.md/g, '.windsurf/rules');
|
|
2463
|
+
converted = converted.replace(/`CLAUDE\.md`/g, '`.windsurf/rules`');
|
|
2464
|
+
converted = converted.replace(/\bCLAUDE\.md\b/g, '.windsurf/rules');
|
|
2465
|
+
converted = converted.replace(/\.claude\/skills\//g, '.windsurf/skills/');
|
|
2466
|
+
converted = converted.replace(/\.\/\.claude\//g, './.windsurf/');
|
|
2467
|
+
converted = converted.replace(/\.claude\//g, '.windsurf/');
|
|
2464
2468
|
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
|
|
2465
2469
|
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .claudeignore.
|
|
2466
|
-
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.
|
|
2467
|
-
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.
|
|
2470
|
+
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.windsurf');
|
|
2471
|
+
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.windsurf');
|
|
2468
2472
|
// Environment variable name rewrite
|
|
2469
2473
|
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'WINDSURF_CONFIG_DIR');
|
|
2470
2474
|
// Remove Claude Code-specific bug workarounds before brand replacement
|
|
@@ -2518,6 +2522,33 @@ function convertClaudeCommandToWindsurfSkill(content, skillName) {
|
|
|
2518
2522
|
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
2519
2523
|
}
|
|
2520
2524
|
|
|
2525
|
+
function convertClaudeCommandToWindsurfWorkflow(content, commandName) {
|
|
2526
|
+
// #1615 security: commandName flows unsanitized into a markdown body that
|
|
2527
|
+
// Windsurf loads as an LLM-readable workflow. Validate at entry to prevent
|
|
2528
|
+
// (a) prompt injection via newlines / markdown structure in the filename,
|
|
2529
|
+
// (b) path-component injection via .., /, \ in stem → @-reference target.
|
|
2530
|
+
// Pattern: optional gsd- prefix + lowercase alphanumeric + dashes; rejects
|
|
2531
|
+
// everything else. See DEFECT.PROMPT-INJECTION-SCAN-COLLISION and the
|
|
2532
|
+
// PR #1622 security review.
|
|
2533
|
+
if (typeof commandName !== 'string' || !/^(?:gsd-)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(commandName)) {
|
|
2534
|
+
const preview = typeof commandName === 'string' ? JSON.stringify(commandName.slice(0, 60)) : String(commandName);
|
|
2535
|
+
throw new Error(
|
|
2536
|
+
`convertClaudeCommandToWindsurfWorkflow: rejected commandName ${preview}; ` +
|
|
2537
|
+
'must match /^(?:gsd-)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/ (no slashes, backslashes, spaces, dots, trailing dash, or control chars — prevents prompt injection and path-component injection into the workflow body)'
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
const converted = convertClaudeToWindsurfMarkdown(content);
|
|
2541
|
+
const { frontmatter } = extractFrontmatterAndBody(converted);
|
|
2542
|
+
const description = frontmatter ? extractFrontmatterField(frontmatter, 'description') : '';
|
|
2543
|
+
const stem = commandName.startsWith('gsd-') ? commandName.slice(4) : commandName;
|
|
2544
|
+
const workflow = `# ${commandName}\n\n${toSingleLine(description || `Run ${commandName}.`)}\n\nRead and execute the GSD command at @~/.claude/gsd-core/commands/gsd/${stem}.md end-to-end. Treat the user's message after /${commandName} as the command arguments.`;
|
|
2545
|
+
const byteLength = Buffer.byteLength(workflow, 'utf8');
|
|
2546
|
+
if (byteLength > 12000) {
|
|
2547
|
+
throw new Error(`Windsurf workflow ${commandName} exceeds 12000 bytes (${byteLength}); extract references before installing`);
|
|
2548
|
+
}
|
|
2549
|
+
return workflow;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2521
2552
|
/**
|
|
2522
2553
|
* Convert Claude Code agent markdown to Windsurf agent format.
|
|
2523
2554
|
* Strips frontmatter fields Windsurf doesn't support (color, skills),
|
|
@@ -3223,6 +3254,66 @@ function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
|
3223
3254
|
}
|
|
3224
3255
|
}
|
|
3225
3256
|
|
|
3257
|
+
/**
|
|
3258
|
+
* Remove legacy Windsurf skill artifacts from .devin/skills/gsd- directories.
|
|
3259
|
+
*
|
|
3260
|
+
* Pre-#1615 Windsurf installs wrote skills under .devin/ (Devin Desktop
|
|
3261
|
+
* preferred dir, #1085). #1615 moved Windsurf to .windsurf/workflows/.
|
|
3262
|
+
* Old .devin/skills/gsd- dirs linger on disk indefinitely and confuse
|
|
3263
|
+
* users who see two GSD trees.
|
|
3264
|
+
*
|
|
3265
|
+
* Preserves user-owned content:
|
|
3266
|
+
* - non-gsd-* dirs under .devin/skills/ (user-authored skills)
|
|
3267
|
+
* - gsd-dev-preferences/ (user-owned per #2973)
|
|
3268
|
+
* - any files (not dirs) under .devin/skills/
|
|
3269
|
+
*
|
|
3270
|
+
* @param {string} workspaceDir - workspace root (process.cwd() for local installs)
|
|
3271
|
+
* @returns {number} count of removed legacy gsd-* skill directories
|
|
3272
|
+
*/
|
|
3273
|
+
function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
3274
|
+
const legacySkillsDir = path.join(workspaceDir, '.devin', 'skills');
|
|
3275
|
+
if (!fs.existsSync(legacySkillsDir)) return 0;
|
|
3276
|
+
|
|
3277
|
+
// Mirror the user-owned list from cleanupCodexSkillMetadataSidecars (#2973).
|
|
3278
|
+
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
3279
|
+
let removed = 0;
|
|
3280
|
+
|
|
3281
|
+
for (const entry of fs.readdirSync(legacySkillsDir, { withFileTypes: true })) {
|
|
3282
|
+
if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue;
|
|
3283
|
+
if (_userOwnedSkillDirs.has(entry.name)) continue;
|
|
3284
|
+
|
|
3285
|
+
const dirToRemove = path.join(legacySkillsDir, entry.name);
|
|
3286
|
+
try {
|
|
3287
|
+
// Symlink guard: if the gsd-* dir is itself a symlink pointing outside
|
|
3288
|
+
// the .devin tree, deleting through it could escape the tree. Skip.
|
|
3289
|
+
const stat = fs.lstatSync(dirToRemove);
|
|
3290
|
+
if (stat.isSymbolicLink()) continue;
|
|
3291
|
+
|
|
3292
|
+
fs.rmSync(dirToRemove, { recursive: true, force: true });
|
|
3293
|
+
removed++;
|
|
3294
|
+
} catch (_err) {
|
|
3295
|
+
// Fail open — a single bad dir must not block the install.
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
// If .devin/skills/ is now empty, prune it. If .devin/ itself is then empty,
|
|
3300
|
+
// prune that too — leaves the workspace clean for the new .windsurf/ layout.
|
|
3301
|
+
// Never remove non-empty containers (user may have other Devin content).
|
|
3302
|
+
try {
|
|
3303
|
+
if (fs.existsSync(legacySkillsDir) && fs.readdirSync(legacySkillsDir).length === 0) {
|
|
3304
|
+
fs.rmdirSync(legacySkillsDir);
|
|
3305
|
+
const devinDir = path.join(workspaceDir, '.devin');
|
|
3306
|
+
if (fs.existsSync(devinDir) && fs.readdirSync(devinDir).length === 0) {
|
|
3307
|
+
fs.rmdirSync(devinDir);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
} catch (_err) {
|
|
3311
|
+
// best-effort container cleanup
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
return removed;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3226
3317
|
/**
|
|
3227
3318
|
* Generate the GSD config block for Codex config.toml.
|
|
3228
3319
|
* @param {Array<{name: string, description: string}>} agents
|
|
@@ -9592,6 +9683,17 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9592
9683
|
cleanupCodexSkillMetadataSidecars(path.join(targetDir, 'skills'));
|
|
9593
9684
|
}
|
|
9594
9685
|
|
|
9686
|
+
// #1629 Finding B: Windsurf local only — remove legacy .devin/skills/gsd-*
|
|
9687
|
+
// dirs from pre-#1615 installs. #1615 moved Windsurf to .windsurf/workflows/
|
|
9688
|
+
// but never cleaned up the old .devin/skills/ layout (#1085). User-owned
|
|
9689
|
+
// content is preserved (non-gsd- dirs, gsd-dev-preferences, symlinks).
|
|
9690
|
+
if (isWindsurf && !isGlobal) {
|
|
9691
|
+
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd());
|
|
9692
|
+
if (removedCount > 0) {
|
|
9693
|
+
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
|
|
9595
9697
|
// Hermes only: write DESCRIPTION.md for the gsd/ category after layout install
|
|
9596
9698
|
if (isHermes) {
|
|
9597
9699
|
writeHermesCategoryDescription(path.join(targetDir, 'skills', 'gsd'));
|
|
@@ -9632,6 +9734,23 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9632
9734
|
} else {
|
|
9633
9735
|
failures.push('agents/gsd.yaml');
|
|
9634
9736
|
}
|
|
9737
|
+
} else if (isWindsurf) {
|
|
9738
|
+
if (isGlobal) {
|
|
9739
|
+
console.log(` ${green}✓${reset} Windsurf global install skipped workflow artifacts (workspace-only)`);
|
|
9740
|
+
} else {
|
|
9741
|
+
const workflowsDir = path.join(targetDir, 'workflows');
|
|
9742
|
+
if (fs.existsSync(workflowsDir)) {
|
|
9743
|
+
const workflowCount = fs.readdirSync(workflowsDir)
|
|
9744
|
+
.filter(f => f.startsWith('gsd-') && f.endsWith('.md')).length;
|
|
9745
|
+
if (workflowCount > 0) {
|
|
9746
|
+
console.log(` ${green}✓${reset} Installed ${workflowCount} workflows to workflows/`);
|
|
9747
|
+
} else {
|
|
9748
|
+
failures.push('workflows/gsd-*');
|
|
9749
|
+
}
|
|
9750
|
+
} else {
|
|
9751
|
+
failures.push('workflows/gsd-*');
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9635
9754
|
} else {
|
|
9636
9755
|
const skillsDir = path.join(targetDir, 'skills');
|
|
9637
9756
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -9857,6 +9976,23 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9857
9976
|
failures.push('gsd-core');
|
|
9858
9977
|
}
|
|
9859
9978
|
|
|
9979
|
+
// #1629 critical fix: Windsurf workflow wrappers (convertClaudeCommandToWindsurfWorkflow)
|
|
9980
|
+
// delegate to command bodies at <targetDir>/gsd-core/commands/gsd/${stem}.md via a
|
|
9981
|
+
// hardcoded @~/.claude/gsd-core/commands/gsd/ path that _applyRuntimeRewrites rewrites
|
|
9982
|
+
// to the install target. The source gsd-core/ dir does NOT ship with commands/ —
|
|
9983
|
+
// the canonical command source lives at the package root (commands/gsd/). Without
|
|
9984
|
+
// this copy, every /gsd-* workflow in Cascade references a missing file and the LLM
|
|
9985
|
+
// cannot execute the command body. Surfaced by the #1629 regression test after the
|
|
9986
|
+
// original adversarial review of #1622 missed it.
|
|
9987
|
+
if (isWindsurf && !isGlobal) {
|
|
9988
|
+
const commandsSrc = path.join(src, 'commands', 'gsd');
|
|
9989
|
+
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
|
9990
|
+
if (fs.existsSync(commandsSrc)) {
|
|
9991
|
+
copyWithPathReplacement(commandsSrc, commandsDest, pathPrefix, runtime, true, isGlobal);
|
|
9992
|
+
console.log(` ${green}✓${reset} Installed command bodies to gsd-core/commands/gsd/ (workflow delegation targets)`);
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9995
|
+
|
|
9860
9996
|
// Copy shared manifests into the gsd-core payload
|
|
9861
9997
|
// at the co-located path that CJS modules resolve first:
|
|
9862
9998
|
// gsd-core/bin/shared/*.json
|
|
@@ -11171,10 +11307,14 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11171
11307
|
configureKiloPermissions(isGlobal, configDir);
|
|
11172
11308
|
}
|
|
11173
11309
|
|
|
11174
|
-
// For non-Claude runtimes,
|
|
11175
|
-
// so resolveModelInternal() returns '' instead of Claude
|
|
11176
|
-
//
|
|
11177
|
-
//
|
|
11310
|
+
// For non-Claude runtimes, DEFAULT resolve_model_ids to "omit" in ~/.gsd/defaults.json
|
|
11311
|
+
// when it is absent or falsy, so resolveModelInternal() returns '' instead of Claude
|
|
11312
|
+
// aliases (opus/sonnet/haiku) the runtime can't resolve. An explicit `true` opt-in
|
|
11313
|
+
// (resolveModelInternal returns full materialized model IDs) MUST be preserved —
|
|
11314
|
+
// rewriting it to "omit" would make generated agent manifests inherit the active
|
|
11315
|
+
// chat model instead of pinning the resolved model. See #1156 (default-to-omit
|
|
11316
|
+
// intent) and #1569 (preserve explicit true). Guard matches the #130-class pattern
|
|
11317
|
+
// on configureOpencodePermissions above.
|
|
11178
11318
|
if (runtime !== 'claude' && !process.env.GSD_TEST_MODE) {
|
|
11179
11319
|
const gsdDir = path.join(os.homedir(), '.gsd');
|
|
11180
11320
|
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
@@ -11182,7 +11322,22 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11182
11322
|
fs.mkdirSync(gsdDir, { recursive: true });
|
|
11183
11323
|
let defaults = {};
|
|
11184
11324
|
try { defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8')); } catch { /* new file */ }
|
|
11185
|
-
|
|
11325
|
+
// Recover a malformed (valid-JSON-but-non-object) defaults.json to a fresh object so
|
|
11326
|
+
// the write below succeeds and the file is no longer broken. Without this, `null` /
|
|
11327
|
+
// `[]` / a number / a string bypass the parse catch and either throw a TypeError on
|
|
11328
|
+
// property access (swallowed by the outer try/catch, leaving the file broken) or get
|
|
11329
|
+
// a property set that won't round-trip through JSON.stringify. (#1657)
|
|
11330
|
+
if (defaults === null || typeof defaults !== 'object' || Array.isArray(defaults)) {
|
|
11331
|
+
defaults = {};
|
|
11332
|
+
}
|
|
11333
|
+
// Three-valued domain: false/absent → aliases; true → full IDs; "omit" → ''.
|
|
11334
|
+
// Honor ONLY an explicit canonical `true` opt-in (full model IDs) and an existing
|
|
11335
|
+
// "omit"; default everything else — absent, falsy, OR any non-canonical value — to
|
|
11336
|
+
// "omit", the safe non-Claude default. Allowlist-based so malformed values
|
|
11337
|
+
// (0, "", "yes", {}, …) don't leak Claude aliases the runtime can't resolve (#1569).
|
|
11338
|
+
const existing = defaults.resolve_model_ids;
|
|
11339
|
+
const shouldDefaultToOmit = existing !== true && existing !== 'omit';
|
|
11340
|
+
if (shouldDefaultToOmit) {
|
|
11186
11341
|
defaults.resolve_model_ids = 'omit';
|
|
11187
11342
|
fs.writeFileSync(defaultsPath, JSON.stringify(defaults, null, 2) + '\n');
|
|
11188
11343
|
console.log(` ${green}✓${reset} Set resolve_model_ids: "omit" in ~/.gsd/defaults.json`);
|
|
@@ -11638,6 +11793,189 @@ function homePathCoveredByRc(globalBin, homeDir, rcFileNames) {
|
|
|
11638
11793
|
return false;
|
|
11639
11794
|
}
|
|
11640
11795
|
|
|
11796
|
+
/**
|
|
11797
|
+
* Decode fish's universal-variable value escaping (the inverse of fish's
|
|
11798
|
+
* `full_escape`). fish serializes every non-`[A-Za-z0-9/_]` byte in
|
|
11799
|
+
* `fish_variables` — e.g. space -> `\x20`, hyphen -> `\x2d`, dot -> `\x2e` —
|
|
11800
|
+
* and joins list elements with the literal 4-char token `\x1e` (NOT a raw
|
|
11801
|
+
* 0x1e byte). Callers split on `\x1e` first, then decode each element here.
|
|
11802
|
+
*
|
|
11803
|
+
* Pure and total: any unrecognised `\`-sequence is passed through verbatim,
|
|
11804
|
+
* so `decode(fishEscape(p)) === p` holds for every path string. Exported for
|
|
11805
|
+
* a fast-check round-trip property test (#323).
|
|
11806
|
+
*
|
|
11807
|
+
* @param {string} s A single (already `\x1e`-split) escaped value.
|
|
11808
|
+
* @returns {string} The decoded literal.
|
|
11809
|
+
*/
|
|
11810
|
+
function decodeFishUniversalValue(s) {
|
|
11811
|
+
let out = '';
|
|
11812
|
+
for (let i = 0; i < s.length; i++) {
|
|
11813
|
+
const c = s[i];
|
|
11814
|
+
if (c !== '\\') { out += c; continue; }
|
|
11815
|
+
const n = s[i + 1];
|
|
11816
|
+
if (n === 'n') { out += '\n'; i += 1; }
|
|
11817
|
+
else if (n === 'r') { out += '\r'; i += 1; }
|
|
11818
|
+
else if (n === 't') { out += '\t'; i += 1; }
|
|
11819
|
+
else if (n === '\\') { out += '\\'; i += 1; }
|
|
11820
|
+
else if (n === 'x' || n === 'X') {
|
|
11821
|
+
const hex = s.slice(i + 2, i + 4);
|
|
11822
|
+
if (/^[0-9a-fA-F]{2}$/.test(hex)) { out += String.fromCharCode(parseInt(hex, 16)); i += 3; }
|
|
11823
|
+
else { out += c; }
|
|
11824
|
+
} else if (n === 'u') {
|
|
11825
|
+
const hex = s.slice(i + 2, i + 6);
|
|
11826
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) { out += String.fromCharCode(parseInt(hex, 16)); i += 5; }
|
|
11827
|
+
else { out += c; }
|
|
11828
|
+
} else if (n === 'U') {
|
|
11829
|
+
const hex = s.slice(i + 2, i + 10);
|
|
11830
|
+
if (/^[0-9a-fA-F]{8}$/.test(hex)) { out += String.fromCodePoint(parseInt(hex, 16)); i += 9; }
|
|
11831
|
+
else { out += c; }
|
|
11832
|
+
} else { out += c; }
|
|
11833
|
+
}
|
|
11834
|
+
return out;
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11837
|
+
/**
|
|
11838
|
+
* Check whether fish's configuration already places `globalBin` on PATH (#323).
|
|
11839
|
+
*
|
|
11840
|
+
* fish does not use the sh-style `export PATH=` rc files that
|
|
11841
|
+
* `homePathCoveredByRc()` parses, so a fish user whose `fish_user_paths`
|
|
11842
|
+
* already covers the global bin would otherwise see a false-positive
|
|
11843
|
+
* "not on your PATH" warning on every install. Two detection routes,
|
|
11844
|
+
* mirroring how `fish_add_path` actually persists:
|
|
11845
|
+
*
|
|
11846
|
+
* 1. The universal-variable store `fish_variables` — a
|
|
11847
|
+
* `SETUVAR fish_user_paths:<a>\x1e<b>…` line whose `\x1e`-separated
|
|
11848
|
+
* entries are absolute paths (fish does not HOME-expand them here).
|
|
11849
|
+
* 2. `config.fish` — explicit `fish_add_path …`, `set -gx PATH …`, or
|
|
11850
|
+
* `set -Ux fish_user_paths …` lines that name the directory after
|
|
11851
|
+
* HOME expansion.
|
|
11852
|
+
*
|
|
11853
|
+
* Best-effort and side-effect-free: any unreadable / missing file is ignored
|
|
11854
|
+
* (no fish subprocess is spawned). Honours `$XDG_CONFIG_HOME` and always also
|
|
11855
|
+
* checks `~/.config/fish`. Pass `fishConfigDir` to override the lookup
|
|
11856
|
+
* directory (tests).
|
|
11857
|
+
*
|
|
11858
|
+
* @param {string} globalBin Absolute path to npm's global bin directory.
|
|
11859
|
+
* @param {string} homeDir Absolute path used to substitute HOME / ~.
|
|
11860
|
+
* @param {string} [fishConfigDir] Override the fish config directory.
|
|
11861
|
+
* @returns {boolean} true iff fish config adds globalBin to PATH.
|
|
11862
|
+
*/
|
|
11863
|
+
function homePathCoveredByFishConfig(globalBin, homeDir, fishConfigDir) {
|
|
11864
|
+
if (!globalBin || !homeDir) return false;
|
|
11865
|
+
const path = require('path');
|
|
11866
|
+
const fs = require('fs');
|
|
11867
|
+
|
|
11868
|
+
const normalise = (p) => {
|
|
11869
|
+
if (!p) return '';
|
|
11870
|
+
let n = p.replace(/[\\/]+$/g, '');
|
|
11871
|
+
if (n === '') n = p.startsWith('/') ? '/' : p;
|
|
11872
|
+
return n;
|
|
11873
|
+
};
|
|
11874
|
+
|
|
11875
|
+
const targetAbs = normalise(path.resolve(globalBin));
|
|
11876
|
+
const homeAbs = path.resolve(homeDir);
|
|
11877
|
+
|
|
11878
|
+
const baseDirs = [];
|
|
11879
|
+
if (fishConfigDir) {
|
|
11880
|
+
baseDirs.push(fishConfigDir);
|
|
11881
|
+
} else {
|
|
11882
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
11883
|
+
baseDirs.push(path.join(process.env.XDG_CONFIG_HOME, 'fish'));
|
|
11884
|
+
}
|
|
11885
|
+
baseDirs.push(path.join(homeAbs, '.config', 'fish'));
|
|
11886
|
+
}
|
|
11887
|
+
|
|
11888
|
+
const expandHome = (segment) => {
|
|
11889
|
+
let s = segment;
|
|
11890
|
+
s = s.replace(/\$\{HOME\}/g, homeAbs).replace(/\$HOME/g, homeAbs);
|
|
11891
|
+
if (s.startsWith('~/') || s === '~') {
|
|
11892
|
+
s = s === '~' ? homeAbs : path.join(homeAbs, s.slice(2));
|
|
11893
|
+
}
|
|
11894
|
+
return s;
|
|
11895
|
+
};
|
|
11896
|
+
|
|
11897
|
+
// Compare an already-resolved absolute literal (a decoded fish_user_paths
|
|
11898
|
+
// entry — fish stores these resolved, never as `$VAR`/`~`). A literal `$`
|
|
11899
|
+
// here is part of the directory name, so it must NOT be treated as an
|
|
11900
|
+
// unexpanded variable.
|
|
11901
|
+
const matchesLiteral = (segment) => {
|
|
11902
|
+
if (!segment || !path.isAbsolute(segment)) return false;
|
|
11903
|
+
try {
|
|
11904
|
+
return normalise(path.resolve(segment)) === targetAbs;
|
|
11905
|
+
} catch {
|
|
11906
|
+
return false;
|
|
11907
|
+
}
|
|
11908
|
+
};
|
|
11909
|
+
|
|
11910
|
+
// Compare a config.fish shell token: strip surrounding quotes, expand the
|
|
11911
|
+
// common HOME forms, and skip anything still holding a `$` (an unexpanded
|
|
11912
|
+
// variable such as `$PATH` / `$fish_user_paths`) or still relative.
|
|
11913
|
+
const matchesTarget = (rawSegment) => {
|
|
11914
|
+
if (!rawSegment) return false;
|
|
11915
|
+
let seg = rawSegment.trim();
|
|
11916
|
+
if ((seg.startsWith('"') && seg.endsWith('"')) ||
|
|
11917
|
+
(seg.startsWith("'") && seg.endsWith("'"))) {
|
|
11918
|
+
seg = seg.slice(1, -1);
|
|
11919
|
+
}
|
|
11920
|
+
const expanded = expandHome(seg);
|
|
11921
|
+
if (expanded.includes('$')) return false;
|
|
11922
|
+
return matchesLiteral(expanded);
|
|
11923
|
+
};
|
|
11924
|
+
|
|
11925
|
+
const readLines = (filePath) => {
|
|
11926
|
+
try {
|
|
11927
|
+
return fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
11928
|
+
} catch {
|
|
11929
|
+
return null;
|
|
11930
|
+
}
|
|
11931
|
+
};
|
|
11932
|
+
|
|
11933
|
+
for (const baseDir of baseDirs) {
|
|
11934
|
+
// Route 1: universal variable store.
|
|
11935
|
+
const uvarLines = readLines(path.join(baseDir, 'fish_variables'));
|
|
11936
|
+
if (uvarLines) {
|
|
11937
|
+
for (const rawLine of uvarLines) {
|
|
11938
|
+
const m = /^SETUVAR(?:\s+--\S+)*\s+fish_user_paths:(.*)$/.exec(rawLine);
|
|
11939
|
+
if (!m) continue;
|
|
11940
|
+
// Elements are joined by the literal `\x1e` token; decode each. The
|
|
11941
|
+
// decoded entry is an absolute literal — compare it directly.
|
|
11942
|
+
for (const entry of m[1].split('\\x1e')) {
|
|
11943
|
+
if (matchesLiteral(decodeFishUniversalValue(entry))) return true;
|
|
11944
|
+
}
|
|
11945
|
+
}
|
|
11946
|
+
}
|
|
11947
|
+
|
|
11948
|
+
// Route 2: config.fish explicit PATH mutations.
|
|
11949
|
+
const configLines = readLines(path.join(baseDir, 'config.fish'));
|
|
11950
|
+
if (configLines) {
|
|
11951
|
+
for (const rawLine of configLines) {
|
|
11952
|
+
const line = rawLine.replace(/^\s+/, '');
|
|
11953
|
+
if (line.startsWith('#')) continue;
|
|
11954
|
+
|
|
11955
|
+
let rest = null;
|
|
11956
|
+
let m;
|
|
11957
|
+
if ((m = /^fish_add_path\s+(.+)$/.exec(line))) {
|
|
11958
|
+
rest = m[1];
|
|
11959
|
+
} else if ((m = /^set\s+(?:-\S+\s+)*PATH\s+(.+)$/.exec(line))) {
|
|
11960
|
+
rest = m[1];
|
|
11961
|
+
} else if ((m = /^set\s+(?:-\S+\s+)*fish_user_paths\s+(.+)$/.exec(line))) {
|
|
11962
|
+
rest = m[1];
|
|
11963
|
+
}
|
|
11964
|
+
if (rest === null) continue;
|
|
11965
|
+
|
|
11966
|
+
// Tokens are whitespace-separated; flag tokens (`-g`, `--path`) and
|
|
11967
|
+
// variable references are skipped by matchesTarget / the `-` guard.
|
|
11968
|
+
for (const tok of rest.split(/\s+/)) {
|
|
11969
|
+
if (!tok || tok.startsWith('-')) continue;
|
|
11970
|
+
if (matchesTarget(tok)) return true;
|
|
11971
|
+
}
|
|
11972
|
+
}
|
|
11973
|
+
}
|
|
11974
|
+
}
|
|
11975
|
+
|
|
11976
|
+
return false;
|
|
11977
|
+
}
|
|
11978
|
+
|
|
11641
11979
|
/**
|
|
11642
11980
|
* Emit a PATH-export suggestion if globalBin is not already on PATH AND
|
|
11643
11981
|
* the user's shell rc files do not already cover it via a HOME-relative
|
|
@@ -11680,6 +12018,16 @@ function maybeSuggestPathExport(globalBin, homeDir) {
|
|
|
11680
12018
|
return;
|
|
11681
12019
|
}
|
|
11682
12020
|
|
|
12021
|
+
// Same idea for fish users: fish_user_paths / config.fish already covers the
|
|
12022
|
+
// dir, the current session just predates it. fish has no sh-style rc file so
|
|
12023
|
+
// homePathCoveredByRc never sees it — check the fish config explicitly (#323).
|
|
12024
|
+
if (homePathCoveredByFishConfig(globalBin, homeDir)) {
|
|
12025
|
+
console.log('');
|
|
12026
|
+
console.log(` ${yellow}⚠${reset} ${bold}${globalBin}${reset}'s directory is already on your PATH via fish's universal variables — open a new fish session (or run ${cyan}exec fish${reset}).`);
|
|
12027
|
+
console.log('');
|
|
12028
|
+
return;
|
|
12029
|
+
}
|
|
12030
|
+
|
|
11683
12031
|
console.log('');
|
|
11684
12032
|
console.log(` ${yellow}⚠${reset} ${bold}${globalBin}${reset} is not on your PATH.`);
|
|
11685
12033
|
console.log(` Add it with one of:`);
|
|
@@ -11913,6 +12261,7 @@ module.exports = {
|
|
|
11913
12261
|
convertClaudeAgentToCodexAgent,
|
|
11914
12262
|
generateCodexAgentToml,
|
|
11915
12263
|
cleanupCodexSkillMetadataSidecars,
|
|
12264
|
+
cleanupWindsurfLegacyDevinSkills,
|
|
11916
12265
|
generateCodexConfigBlock,
|
|
11917
12266
|
stripGsdFromCodexConfig,
|
|
11918
12267
|
migrateCodexHooksMapFormat,
|
|
@@ -11974,6 +12323,7 @@ module.exports = {
|
|
|
11974
12323
|
skillFrontmatterName,
|
|
11975
12324
|
convertClaudeToWindsurfMarkdown,
|
|
11976
12325
|
convertClaudeCommandToWindsurfSkill,
|
|
12326
|
+
convertClaudeCommandToWindsurfWorkflow,
|
|
11977
12327
|
convertClaudeAgentToWindsurfAgent,
|
|
11978
12328
|
convertClaudeToAugmentMarkdown,
|
|
11979
12329
|
convertClaudeCommandToAugmentSkill,
|
|
@@ -12015,6 +12365,8 @@ module.exports = {
|
|
|
12015
12365
|
USER_OWNED_ARTIFACTS,
|
|
12016
12366
|
finishInstall,
|
|
12017
12367
|
homePathCoveredByRc,
|
|
12368
|
+
homePathCoveredByFishConfig,
|
|
12369
|
+
decodeFishUniversalValue,
|
|
12018
12370
|
maybeSuggestPathExport,
|
|
12019
12371
|
runtimeMap,
|
|
12020
12372
|
allRuntimes,
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
|
-
"version": "1.6.0
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
* UAT Audit:
|
|
86
86
|
* audit-uat Scan all phases for unresolved UAT/verification items
|
|
87
87
|
* uat render-checkpoint --file <path> Render the current UAT checkpoint block
|
|
88
|
+
* uat classify-coverage --summary <path> Classify a SUMMARY coverage block into auto-passed vs human-UAT (#1602)
|
|
88
89
|
*
|
|
89
90
|
* Open Artifact Audit:
|
|
90
91
|
* audit-open [--json] Scan all .planning/ artifact types for unresolved items
|
|
@@ -221,6 +222,8 @@ const learnings = require('./lib/learnings.cjs');
|
|
|
221
222
|
const gapChecker = require('./lib/gap-checker.cjs');
|
|
222
223
|
const { routeStateCommand } = require('./lib/state-command-router.cjs');
|
|
223
224
|
const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
|
|
225
|
+
const { routeEvalCommand } = require('./lib/eval-command-router.cjs');
|
|
226
|
+
const evalMod = require('./lib/eval.cjs');
|
|
224
227
|
const { routeVerificationCommand } = require('./lib/verification-command-router.cjs');
|
|
225
228
|
const verification = require('./lib/verification.cjs');
|
|
226
229
|
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
|
@@ -638,8 +641,8 @@ async function main() {
|
|
|
638
641
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
639
642
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
640
643
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
641
|
-
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
642
|
-
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
644
|
+
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
645
|
+
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
643
646
|
'Global flags:\n' +
|
|
644
647
|
' --raw Emit raw output without post-processing\n' +
|
|
645
648
|
' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
|
|
@@ -689,6 +692,13 @@ async function main() {
|
|
|
689
692
|
'worktree', 'prompt-budget',
|
|
690
693
|
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
|
691
694
|
'user-story', // pure string validation — no .planning/ access needed
|
|
695
|
+
// #1529: pure runtime→filename projection via getProjectInstructionFile; no
|
|
696
|
+
// .planning/ access needed, and resolving project root would break workflow
|
|
697
|
+
// invocations that run before .planning/ exists (new-project Step 1).
|
|
698
|
+
'project-instruction-file',
|
|
699
|
+
// #1579: eval.score is pure arithmetic (covered/total + infra weights); it
|
|
700
|
+
// needs no .planning/ access, so skip the findProjectRoot traversal.
|
|
701
|
+
'eval',
|
|
692
702
|
]);
|
|
693
703
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
694
704
|
cwd = findProjectRoot(cwd);
|
|
@@ -1057,6 +1067,11 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1057
1067
|
break;
|
|
1058
1068
|
}
|
|
1059
1069
|
|
|
1070
|
+
case 'eval': {
|
|
1071
|
+
routeEvalCommand({ evalMod, args, cwd, raw, error });
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1060
1075
|
// ─── Verification Status ───────────────────────────────────────────────
|
|
1061
1076
|
//
|
|
1062
1077
|
// verification status <phaseDir>
|
|
@@ -1103,6 +1118,29 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1103
1118
|
break;
|
|
1104
1119
|
}
|
|
1105
1120
|
|
|
1121
|
+
case 'project-instruction-file': {
|
|
1122
|
+
// #1529: pure runtime→filename projection. Backs the
|
|
1123
|
+
// `gsd_run query project-instruction-file --runtime <r>` call in
|
|
1124
|
+
// new-project.md so the bash workflow and profile-output.cjs share one
|
|
1125
|
+
// source of truth (getProjectInstructionFile in runtime-name-policy.cjs).
|
|
1126
|
+
// No SDK bridge — pure local lookup, runs before .planning/ exists.
|
|
1127
|
+
const { getProjectInstructionFile } = require('./lib/runtime-name-policy.cjs');
|
|
1128
|
+
// Parse --runtime <value> (space or = form); default to empty so the
|
|
1129
|
+
// safe AGENTS.md cross-agent default applies.
|
|
1130
|
+
const pifArgs = args.slice(1);
|
|
1131
|
+
let pifRuntime = '';
|
|
1132
|
+
for (let i = 0; i < pifArgs.length; i++) {
|
|
1133
|
+
const a = pifArgs[i];
|
|
1134
|
+
if (a === '--runtime' && pifArgs[i + 1] !== undefined) { pifRuntime = pifArgs[++i]; continue; }
|
|
1135
|
+
if (a.startsWith('--runtime=')) { pifRuntime = a.slice('--runtime='.length); continue; }
|
|
1136
|
+
// First positional that isn't a flag also works (lenient); otherwise ignore unknown flags.
|
|
1137
|
+
if (!a.startsWith('-') && !pifRuntime) { pifRuntime = a; }
|
|
1138
|
+
}
|
|
1139
|
+
const filename = getProjectInstructionFile(pifRuntime);
|
|
1140
|
+
process.stdout.write(filename + '\n');
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1106
1144
|
case 'list-todos': {
|
|
1107
1145
|
commands.cmdListTodos(cwd, args[1], raw);
|
|
1108
1146
|
break;
|
|
@@ -1333,12 +1371,16 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1333
1371
|
|
|
1334
1372
|
case 'uat': {
|
|
1335
1373
|
const subcommand = args[1];
|
|
1336
|
-
const uat = require('./lib/uat.cjs');
|
|
1337
1374
|
if (subcommand === 'render-checkpoint') {
|
|
1375
|
+
const uat = require('./lib/uat.cjs');
|
|
1338
1376
|
const options = parseNamedArgs(args, ['file']);
|
|
1339
1377
|
uat.cmdRenderCheckpoint(cwd, options, raw);
|
|
1378
|
+
} else if (subcommand === 'classify-coverage') {
|
|
1379
|
+
const coverage = require('./lib/coverage.cjs');
|
|
1380
|
+
const options = parseNamedArgs(args, ['summary', 'file']);
|
|
1381
|
+
coverage.cmdClassify(cwd, options, raw);
|
|
1340
1382
|
} else {
|
|
1341
|
-
error('Unknown uat subcommand. Available: render-checkpoint', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1383
|
+
error('Unknown uat subcommand. Available: render-checkpoint, classify-coverage', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1342
1384
|
}
|
|
1343
1385
|
break;
|
|
1344
1386
|
}
|