@opengsd/gsd-core 1.7.0-rc.3 → 1.7.0-rc.4
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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ja-JP.md +4 -3
- package/README.ko-KR.md +4 -3
- package/README.md +4 -3
- package/README.pt-BR.md +4 -3
- package/README.zh-CN.md +4 -3
- package/bin/install.js +33 -15
- package/commands/gsd/map-codebase.md +4 -4
- package/commands/gsd/ns-project.md +2 -1
- package/commands/gsd/onboard.md +46 -0
- package/gsd-core/bin/gsd-tools.cjs +47 -2
- package/gsd-core/bin/lib/capability-registry.cjs +467 -49
- package/gsd-core/bin/lib/capability-state.cjs +117 -18
- package/gsd-core/bin/lib/capability-writer.cjs +11 -1
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +136 -0
- package/gsd-core/bin/lib/claude-orchestration.cjs +404 -0
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/init-command-router.cjs +4 -0
- package/gsd-core/bin/lib/init.cjs +25 -69
- package/gsd-core/bin/lib/install-engine.cjs +8 -5
- package/gsd-core/bin/lib/install-profiles.cjs +2 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +7 -1
- package/gsd-core/bin/lib/model-resolver.cjs +69 -4
- package/gsd-core/bin/lib/onboard-projection.cjs +309 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/surface.cjs +51 -30
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/references/gsd-run-resolver.md +8 -0
- package/gsd-core/templates/project.md +1 -1
- package/gsd-core/workflows/do.md +4 -3
- package/gsd-core/workflows/help/modes/brief.md +2 -1
- package/gsd-core/workflows/help/modes/default.md +2 -1
- package/gsd-core/workflows/help/modes/full.md +11 -1
- package/gsd-core/workflows/help/modes/topic.md +1 -1
- package/gsd-core/workflows/onboard.md +277 -0
- package/gsd-core/workflows/quick.md +20 -0
- package/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +77 -0
- package/scripts/run-tests.cjs +5 -3
- package/scripts/sync-runtime-launcher.cjs +20 -2
- package/skills/gsd-map-codebase/SKILL.md +3 -3
- package/skills/gsd-ns-project/SKILL.md +1 -0
- package/skills/gsd-onboard/SKILL.md +46 -0
|
@@ -78,6 +78,61 @@ function warnModelPolicyUnmappable(agentType, policyModel, tier) {
|
|
|
78
78
|
function _resetModelPolicyWarningCacheForTests() {
|
|
79
79
|
_modelPolicyUnmappableWarned.clear();
|
|
80
80
|
}
|
|
81
|
+
// Dedupe stderr warnings for unmappable model_overrides Claude IDs (#2041).
|
|
82
|
+
const _modelOverrideUnmappableWarned = new Set();
|
|
83
|
+
function warnModelOverrideUnmappable(agentType, overrideValue) {
|
|
84
|
+
const key = `${agentType}::${overrideValue}`;
|
|
85
|
+
if (_modelOverrideUnmappableWarned.has(key))
|
|
86
|
+
return;
|
|
87
|
+
_modelOverrideUnmappableWarned.add(key);
|
|
88
|
+
// Cap emission length so an oversized or secret-shaped value cannot leak in
|
|
89
|
+
// full to stderr/logs (#2041 security review). MUST go to stderr — resolve-
|
|
90
|
+
// model's JSON result is parsed from stdout.
|
|
91
|
+
const safe = overrideValue.length > 64 ? overrideValue.slice(0, 64) + '…' : overrideValue;
|
|
92
|
+
process.stderr.write(`gsd: warning — model_overrides value "${safe}" for ${agentType} ` +
|
|
93
|
+
`has no Claude agent alias; falling through to tier resolution.\n`);
|
|
94
|
+
}
|
|
95
|
+
// Test-only: reset the model_overrides warn-dedupe cache between cases (#2041).
|
|
96
|
+
function _resetModelOverrideWarningCacheForTests() {
|
|
97
|
+
_modelOverrideUnmappableWarned.clear();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* #2041 — Map a `model_overrides` value to its Claude Agent-tool alias on the
|
|
101
|
+
* claude runtime, mirroring the `model_policy` path (#1144). Claude Code's
|
|
102
|
+
* Agent tool `model` parameter documents only tier aliases (opus/sonnet/haiku/
|
|
103
|
+
* fable); a full Claude model ID returned verbatim is silently dropped by the
|
|
104
|
+
* spawner. Returns the value to return verbatim, or null to signal "fall
|
|
105
|
+
* through to normal tier/dynamic-routing resolution" (used when a Claude full
|
|
106
|
+
* ID has no alias — matches model_policy's warn-and-fall-through). Non-Claude
|
|
107
|
+
* runtimes and non-Claude values always pass through verbatim.
|
|
108
|
+
*
|
|
109
|
+
* Hardening (code+security review): a `typeof` guard preserves the pre-fix
|
|
110
|
+
* no-crash behavior if a malformed config surfaces a non-string value, and an
|
|
111
|
+
* `Object.hasOwn` lookup defeats `__proto__`/`constructor` lookups on the plain
|
|
112
|
+
* object literal so those reserved keys cannot return a truthy non-string.
|
|
113
|
+
*/
|
|
114
|
+
function mapClaudeOverrideForRuntime(override, configRuntime, agentType) {
|
|
115
|
+
// Defensive: model_overrides is typed Record<string,string> but a malformed
|
|
116
|
+
// config could surface a non-string; pass through verbatim (preserving the
|
|
117
|
+
// pre-fix no-crash behaviour) and let the downstream Agent tool reject it.
|
|
118
|
+
if (typeof override !== 'string')
|
|
119
|
+
return override;
|
|
120
|
+
const onClaude = !configRuntime || configRuntime === 'claude';
|
|
121
|
+
if (!onClaude)
|
|
122
|
+
return override;
|
|
123
|
+
// Object.hasOwn guards against __proto__/constructor returning a truthy
|
|
124
|
+
// non-string from the plain object literal (#2041 security review).
|
|
125
|
+
if (Object.hasOwn(CLAUDE_POLICY_ID_TO_ALIAS, override)) {
|
|
126
|
+
return CLAUDE_POLICY_ID_TO_ALIAS[override];
|
|
127
|
+
}
|
|
128
|
+
if (CLAUDE_AGENT_ALIASES.has(override))
|
|
129
|
+
return override;
|
|
130
|
+
if (override.startsWith('claude-')) {
|
|
131
|
+
warnModelOverrideUnmappable(agentType, override);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return override;
|
|
135
|
+
}
|
|
81
136
|
/**
|
|
82
137
|
* #49 — Provider-neutral model policy preset resolution.
|
|
83
138
|
*/
|
|
@@ -134,11 +189,16 @@ function resolveModelPolicy(policy, tier) {
|
|
|
134
189
|
}
|
|
135
190
|
function resolveModelInternal(cwd, agentType) {
|
|
136
191
|
const config = loadConfig(cwd);
|
|
137
|
-
// 1. Per-agent override
|
|
192
|
+
// 1. Per-agent override (#2041: map Claude full IDs → Agent-tool aliases on
|
|
193
|
+
// the claude runtime, mirroring the model_policy path #1144; non-Claude
|
|
194
|
+
// runtimes and non-Claude values pass through verbatim).
|
|
138
195
|
const modelOverrides = config['model_overrides'];
|
|
139
196
|
const override = modelOverrides?.[agentType];
|
|
140
197
|
if (override) {
|
|
141
|
-
|
|
198
|
+
const mapped = mapClaudeOverrideForRuntime(override, config['runtime'], agentType);
|
|
199
|
+
if (mapped !== null)
|
|
200
|
+
return mapped;
|
|
201
|
+
// Unmappable Claude ID — fall through to tier resolution (matches model_policy).
|
|
142
202
|
}
|
|
143
203
|
// 2. Compute the tier
|
|
144
204
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
@@ -252,8 +312,12 @@ function resolveModelForTier(cwd, agentType, attempt) {
|
|
|
252
312
|
const attemptN = Number.isInteger(attempt) && attempt > 0 ? attempt : 0;
|
|
253
313
|
const modelOverrides = config['model_overrides'];
|
|
254
314
|
const override = modelOverrides?.[agentType];
|
|
255
|
-
if (override)
|
|
256
|
-
|
|
315
|
+
if (override) {
|
|
316
|
+
const mapped = mapClaudeOverrideForRuntime(override, config['runtime'], agentType);
|
|
317
|
+
if (mapped !== null)
|
|
318
|
+
return mapped;
|
|
319
|
+
// Unmappable Claude ID — fall through to dynamic_routing / model_policy resolution.
|
|
320
|
+
}
|
|
257
321
|
if (config['model_policy'] && config['runtime'] && config['runtime'] !== 'claude') {
|
|
258
322
|
return resolveModelInternal(cwd, agentType);
|
|
259
323
|
}
|
|
@@ -454,6 +518,7 @@ module.exports = {
|
|
|
454
518
|
resolveModelPolicy,
|
|
455
519
|
resolveModelInternal,
|
|
456
520
|
_resetModelPolicyWarningCacheForTests,
|
|
521
|
+
_resetModelOverrideWarningCacheForTests,
|
|
457
522
|
VALID_GRANULARITIES,
|
|
458
523
|
resolveGranularityInternal,
|
|
459
524
|
assertValidGranularityOverride,
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
6
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
8
|
+
const coreUtils = require("./core-utils.cjs");
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
10
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
11
|
+
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
12
|
+
const { pathExistsInternal, toPosixPath } = coreUtils;
|
|
13
|
+
const { planningDir, planningRoot } = planningWorkspace;
|
|
14
|
+
const CODE_EXTENSIONS = new Set([
|
|
15
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.swift', '.java',
|
|
16
|
+
'.kt', '.kts', '.c', '.cpp', '.cc', '.h', '.hpp', '.cs', '.rb', '.php', '.dart',
|
|
17
|
+
'.m', '.mm', '.scala', '.groovy', '.lua', '.r', '.R', '.zig', '.ex', '.exs', '.clj',
|
|
18
|
+
]);
|
|
19
|
+
const CODE_SCAN_SKIP_DIRS = new Set([
|
|
20
|
+
'node_modules', '.git', '.planning', '.claude', '.codex', '__pycache__', 'target',
|
|
21
|
+
'dist', 'build', '.next', '.nuxt', '.svelte-kit', 'coverage', 'vendor', '.venv', 'venv',
|
|
22
|
+
]);
|
|
23
|
+
const PACKAGE_FILES = [
|
|
24
|
+
'package.json', 'requirements.txt', 'pyproject.toml', 'Cargo.toml', 'go.mod',
|
|
25
|
+
'Package.swift', 'build.gradle', 'build.gradle.kts', 'pom.xml', 'Gemfile',
|
|
26
|
+
'composer.json', 'pubspec.yaml', 'CMakeLists.txt', 'Makefile', 'build.zig',
|
|
27
|
+
'mix.exs', 'project.clj',
|
|
28
|
+
];
|
|
29
|
+
const REQUIRED_CODEBASE_MAP_FILES = [
|
|
30
|
+
'STACK.md', 'ARCHITECTURE.md', 'STRUCTURE.md', 'CONVENTIONS.md', 'TESTING.md',
|
|
31
|
+
'INTEGRATIONS.md', 'CONCERNS.md',
|
|
32
|
+
];
|
|
33
|
+
const FAST_CODEBASE_MAP_FILES = [
|
|
34
|
+
'STACK.md', 'INTEGRATIONS.md', 'ARCHITECTURE.md', 'STRUCTURE.md',
|
|
35
|
+
];
|
|
36
|
+
const PLANNING_DOC_SEGMENTS = new Set([
|
|
37
|
+
'adr', 'adrs', 'prd', 'prds', 'spec', 'specs', 'rfc', 'rfcs',
|
|
38
|
+
]);
|
|
39
|
+
function hasCodeFilesInternal(dir, depth = 0) {
|
|
40
|
+
if (depth > 3)
|
|
41
|
+
return false;
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isFile() && CODE_EXTENSIONS.has(node_path_1.default.extname(entry.name)))
|
|
51
|
+
return true;
|
|
52
|
+
if (entry.isDirectory() && !CODE_SCAN_SKIP_DIRS.has(entry.name)) {
|
|
53
|
+
if (hasCodeFilesInternal(node_path_1.default.join(dir, entry.name), depth + 1))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function hasPackageFileInternal(cwd) {
|
|
60
|
+
return PACKAGE_FILES.some((file) => pathExistsInternal(cwd, file));
|
|
61
|
+
}
|
|
62
|
+
function listPlanningDocCandidates(cwd) {
|
|
63
|
+
const roots = ['docs', 'adr', 'adrs', 'prd', 'prds', 'spec', 'specs', 'rfc', 'rfcs'];
|
|
64
|
+
const candidates = new Set();
|
|
65
|
+
function isPlanningDocCandidate(rel, name) {
|
|
66
|
+
const upperName = name.toUpperCase();
|
|
67
|
+
const relLower = rel.toLowerCase();
|
|
68
|
+
const pathSegments = relLower.split('/');
|
|
69
|
+
return (/(^|[-_ ])(ADR|PRD|SPEC|RFC)([-_ ]|\.)/i.test(name) ||
|
|
70
|
+
/^\d{4}[-_].+\.md$/i.test(name) ||
|
|
71
|
+
pathSegments.some((segment) => PLANNING_DOC_SEGMENTS.has(segment)) ||
|
|
72
|
+
upperName === 'REQUIREMENTS.MD');
|
|
73
|
+
}
|
|
74
|
+
function addCandidate(rel, name) {
|
|
75
|
+
if (name.toLowerCase().endsWith('.md') && isPlanningDocCandidate(rel, name)) {
|
|
76
|
+
candidates.add(toPosixPath(rel));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function visit(dir, relDir, depth) {
|
|
80
|
+
if (depth > 3)
|
|
81
|
+
return;
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
if (!CODE_SCAN_SKIP_DIRS.has(entry.name)) {
|
|
93
|
+
visit(node_path_1.default.join(dir, entry.name), rel, depth + 1);
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (entry.isFile())
|
|
98
|
+
addCandidate(rel, entry.name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let rootEntries = [];
|
|
102
|
+
try {
|
|
103
|
+
rootEntries = node_fs_1.default.readdirSync(cwd, { withFileTypes: true });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
rootEntries = [];
|
|
107
|
+
}
|
|
108
|
+
for (const entry of rootEntries) {
|
|
109
|
+
if (entry.isFile())
|
|
110
|
+
addCandidate(entry.name, entry.name);
|
|
111
|
+
}
|
|
112
|
+
for (const root of roots) {
|
|
113
|
+
const full = node_path_1.default.join(cwd, root);
|
|
114
|
+
if (node_fs_1.default.existsSync(full))
|
|
115
|
+
visit(full, root, 0);
|
|
116
|
+
}
|
|
117
|
+
return [...candidates].sort();
|
|
118
|
+
}
|
|
119
|
+
function listCodebaseMapFiles(cwd) {
|
|
120
|
+
const codebaseDir = node_path_1.default.join(planningRoot(cwd), 'codebase');
|
|
121
|
+
if (!node_fs_1.default.existsSync(codebaseDir))
|
|
122
|
+
return [];
|
|
123
|
+
return REQUIRED_CODEBASE_MAP_FILES.filter((file) => node_fs_1.default.existsSync(node_path_1.default.join(codebaseDir, file)));
|
|
124
|
+
}
|
|
125
|
+
function getMapReadiness(hasCompleteMap, hasFastMap) {
|
|
126
|
+
if (hasCompleteMap)
|
|
127
|
+
return 'complete';
|
|
128
|
+
if (hasFastMap)
|
|
129
|
+
return 'fast';
|
|
130
|
+
return 'none';
|
|
131
|
+
}
|
|
132
|
+
function mapSummaryStatus(mapReadiness) {
|
|
133
|
+
if (mapReadiness === 'complete')
|
|
134
|
+
return '.planning/codebase/ (complete codebase map)';
|
|
135
|
+
if (mapReadiness === 'fast')
|
|
136
|
+
return '.planning/codebase/ (fast/partial codebase map; complete map still required for project setup)';
|
|
137
|
+
return 'missing';
|
|
138
|
+
}
|
|
139
|
+
function mapFinalStatus(mapReadiness) {
|
|
140
|
+
if (mapReadiness === 'complete')
|
|
141
|
+
return 'complete';
|
|
142
|
+
if (mapReadiness === 'fast')
|
|
143
|
+
return 'fast/partial; complete map still required for project setup';
|
|
144
|
+
return 'missing';
|
|
145
|
+
}
|
|
146
|
+
function planningMissing(projectExists, requirementsExists, roadmapExists, stateExists) {
|
|
147
|
+
const missing = [];
|
|
148
|
+
if (!projectExists)
|
|
149
|
+
missing.push('PROJECT.md');
|
|
150
|
+
if (!requirementsExists)
|
|
151
|
+
missing.push('REQUIREMENTS.md');
|
|
152
|
+
if (!roadmapExists)
|
|
153
|
+
missing.push('ROADMAP.md');
|
|
154
|
+
if (!stateExists)
|
|
155
|
+
missing.push('STATE.md');
|
|
156
|
+
return missing;
|
|
157
|
+
}
|
|
158
|
+
function nextAction(params) {
|
|
159
|
+
if (params.isBrownfield && params.needsOnboardCodebaseMap) {
|
|
160
|
+
return {
|
|
161
|
+
kind: 'map-codebase',
|
|
162
|
+
command: params.fastMode ? params.handoffCommands.map_codebase_fast : params.handoffCommands.map_codebase,
|
|
163
|
+
reason: 'Existing code was detected, but the required .planning/codebase/ map is missing.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (params.hasPlanningArtifacts && params.missingPlanningFiles.length > 0) {
|
|
167
|
+
return {
|
|
168
|
+
kind: 'partial-planning',
|
|
169
|
+
missing: params.missingPlanningFiles,
|
|
170
|
+
reason: 'Project planning exists but required planning files are missing.',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (params.fastMode && params.mapReadiness === 'fast' && !params.projectExists) {
|
|
174
|
+
return {
|
|
175
|
+
kind: 'complete-map-before-new-project',
|
|
176
|
+
command: params.handoffCommands.map_codebase,
|
|
177
|
+
reason: 'The fast map is enough for lightweight onboarding, but project setup still requires the complete codebase map.',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (params.hasDocsCandidates && !params.projectExists) {
|
|
181
|
+
return {
|
|
182
|
+
kind: 'ingest-docs',
|
|
183
|
+
command: params.handoffCommands.ingest_docs,
|
|
184
|
+
reason: 'Detected existing ADR/PRD/SPEC/RFC document(s) before project setup.',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (!params.isBrownfield && !params.projectExists && !params.hasDocsCandidates) {
|
|
188
|
+
return {
|
|
189
|
+
kind: 'new-project',
|
|
190
|
+
command: params.handoffCommands.new_project,
|
|
191
|
+
reason: 'No existing code or planning docs were detected.',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (!params.projectExists) {
|
|
195
|
+
return {
|
|
196
|
+
kind: 'new-project',
|
|
197
|
+
command: params.handoffCommands.new_project,
|
|
198
|
+
reason: 'Codebase context is ready for project initialization.',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!params.onboardingSummaryExists) {
|
|
202
|
+
return {
|
|
203
|
+
kind: 'write-summary',
|
|
204
|
+
summary_path: params.onboardingSummaryPath,
|
|
205
|
+
reason: 'Onboarding summary is missing.',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
kind: 'ready',
|
|
210
|
+
reason: 'Onboarding summary already exists.',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function buildHandoffCommands(cwd) {
|
|
214
|
+
const runtime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
215
|
+
return {
|
|
216
|
+
ingest_docs: (0, runtime_slash_cjs_1.formatGsdSlash)('ingest-docs', runtime),
|
|
217
|
+
manager: (0, runtime_slash_cjs_1.formatGsdSlash)('manager', runtime),
|
|
218
|
+
map_codebase: (0, runtime_slash_cjs_1.formatGsdSlash)('map-codebase', runtime),
|
|
219
|
+
map_codebase_fast: (0, runtime_slash_cjs_1.formatGsdSlash)('map-codebase --fast', runtime),
|
|
220
|
+
new_project: (0, runtime_slash_cjs_1.formatGsdSlash)('new-project', runtime),
|
|
221
|
+
onboard: (0, runtime_slash_cjs_1.formatGsdSlash)('onboard', runtime),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function buildOnboardProjection(cwd, options) {
|
|
225
|
+
const handoffCommands = buildHandoffCommands(cwd);
|
|
226
|
+
const codebaseMapFiles = listCodebaseMapFiles(cwd);
|
|
227
|
+
const missingCodebaseMapFiles = REQUIRED_CODEBASE_MAP_FILES.filter((file) => !codebaseMapFiles.includes(file));
|
|
228
|
+
const missingFastCodebaseMapFiles = FAST_CODEBASE_MAP_FILES.filter((file) => !codebaseMapFiles.includes(file));
|
|
229
|
+
const docCandidates = listPlanningDocCandidates(cwd);
|
|
230
|
+
const hasCode = hasCodeFilesInternal(cwd);
|
|
231
|
+
const hasPackageFile = hasPackageFileInternal(cwd);
|
|
232
|
+
const isBrownfield = hasCode || hasPackageFile;
|
|
233
|
+
const hasCodebaseMap = codebaseMapFiles.length === REQUIRED_CODEBASE_MAP_FILES.length;
|
|
234
|
+
const hasFastCodebaseMap = missingFastCodebaseMapFiles.length === 0;
|
|
235
|
+
const mapReadinessValue = getMapReadiness(hasCodebaseMap, hasFastCodebaseMap);
|
|
236
|
+
const needsCodebaseMap = isBrownfield && !hasCodebaseMap;
|
|
237
|
+
const needsFastCodebaseMap = isBrownfield && !hasFastCodebaseMap;
|
|
238
|
+
const needsOnboardCodebaseMap = options.fast ? needsFastCodebaseMap : needsCodebaseMap;
|
|
239
|
+
const projectRootPath = node_path_1.default.join(planningRoot(cwd), 'PROJECT.md');
|
|
240
|
+
const projectScopedPath = node_path_1.default.join(planningDir(cwd), 'PROJECT.md');
|
|
241
|
+
const projectExists = node_fs_1.default.existsSync(projectRootPath) || node_fs_1.default.existsSync(projectScopedPath);
|
|
242
|
+
const requirementsExists = node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md'));
|
|
243
|
+
const roadmapExists = node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'));
|
|
244
|
+
const stateExists = node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md'));
|
|
245
|
+
const onboardingSummaryPath = node_path_1.default.join(planningRoot(cwd), 'onboarding', 'SUMMARY.md');
|
|
246
|
+
const onboardingSummaryExists = node_fs_1.default.existsSync(onboardingSummaryPath);
|
|
247
|
+
const hasPlanningArtifacts = projectExists || requirementsExists || roadmapExists || stateExists;
|
|
248
|
+
const missingPlanningFiles = planningMissing(projectExists, requirementsExists, roadmapExists, stateExists);
|
|
249
|
+
return {
|
|
250
|
+
commit_docs: options.commitDocs,
|
|
251
|
+
text_mode: options.textMode,
|
|
252
|
+
project_exists: projectExists,
|
|
253
|
+
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
254
|
+
requirements_exists: requirementsExists,
|
|
255
|
+
roadmap_exists: roadmapExists,
|
|
256
|
+
state_exists: stateExists,
|
|
257
|
+
config_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
258
|
+
handoff_commands: handoffCommands,
|
|
259
|
+
has_existing_code: hasCode,
|
|
260
|
+
has_package_file: hasPackageFile,
|
|
261
|
+
is_brownfield: isBrownfield,
|
|
262
|
+
fast_mode: options.fast,
|
|
263
|
+
map_readiness: mapReadinessValue,
|
|
264
|
+
next_action: nextAction({
|
|
265
|
+
fastMode: options.fast,
|
|
266
|
+
isBrownfield,
|
|
267
|
+
needsOnboardCodebaseMap,
|
|
268
|
+
hasDocsCandidates: docCandidates.length > 0,
|
|
269
|
+
projectExists,
|
|
270
|
+
mapReadiness: mapReadinessValue,
|
|
271
|
+
onboardingSummaryExists,
|
|
272
|
+
onboardingSummaryPath: toPosixPath(node_path_1.default.relative(cwd, onboardingSummaryPath)),
|
|
273
|
+
hasPlanningArtifacts,
|
|
274
|
+
missingPlanningFiles,
|
|
275
|
+
handoffCommands,
|
|
276
|
+
}),
|
|
277
|
+
needs_codebase_map: needsCodebaseMap,
|
|
278
|
+
needs_fast_codebase_map: needsFastCodebaseMap,
|
|
279
|
+
has_codebase_map: hasCodebaseMap,
|
|
280
|
+
has_fast_codebase_map: hasFastCodebaseMap,
|
|
281
|
+
codebase_dir_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningRoot(cwd), 'codebase')),
|
|
282
|
+
fast_codebase_map_files_required: FAST_CODEBASE_MAP_FILES,
|
|
283
|
+
codebase_map_files_present: codebaseMapFiles,
|
|
284
|
+
missing_codebase_map_files: missingCodebaseMapFiles,
|
|
285
|
+
missing_fast_codebase_map_files: missingFastCodebaseMapFiles,
|
|
286
|
+
codebase_map_summary_status: mapSummaryStatus(mapReadinessValue),
|
|
287
|
+
codebase_map_final_status: mapFinalStatus(mapReadinessValue),
|
|
288
|
+
has_docs_candidates: docCandidates.length > 0,
|
|
289
|
+
doc_candidate_count: docCandidates.length,
|
|
290
|
+
doc_candidates: docCandidates,
|
|
291
|
+
onboarding_summary_exists: onboardingSummaryExists,
|
|
292
|
+
onboarding_summary_path: toPosixPath(node_path_1.default.relative(cwd, onboardingSummaryPath)),
|
|
293
|
+
project_path: toPosixPath(node_path_1.default.relative(cwd, node_fs_1.default.existsSync(projectRootPath) ? projectRootPath : projectScopedPath)),
|
|
294
|
+
requirements_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md'))),
|
|
295
|
+
roadmap_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'))),
|
|
296
|
+
state_path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'STATE.md'))),
|
|
297
|
+
codebase_dir: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningRoot(cwd), 'codebase'))),
|
|
298
|
+
onboarding_dir: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningRoot(cwd), 'onboarding'))),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
module.exports = {
|
|
302
|
+
REQUIRED_CODEBASE_MAP_FILES,
|
|
303
|
+
FAST_CODEBASE_MAP_FILES,
|
|
304
|
+
buildOnboardProjection,
|
|
305
|
+
hasCodeFilesInternal,
|
|
306
|
+
hasPackageFileInternal,
|
|
307
|
+
listCodebaseMapFiles,
|
|
308
|
+
listPlanningDocCandidates,
|
|
309
|
+
};
|
|
@@ -209,6 +209,7 @@ const RUNTIME_LABELS = {
|
|
|
209
209
|
kimi: 'Kimi CLI',
|
|
210
210
|
codebuddy: 'CodeBuddy',
|
|
211
211
|
cline: 'Cline',
|
|
212
|
+
zcode: 'ZCode',
|
|
212
213
|
};
|
|
213
214
|
/**
|
|
214
215
|
* Map a canonical runtime id to its short display label for the
|
|
@@ -255,6 +256,7 @@ const GLOBAL_CONFIG_HOME_FRAGMENTS = {
|
|
|
255
256
|
codebuddy: "'.codebuddy'",
|
|
256
257
|
cline: "'.cline'",
|
|
257
258
|
kimi: "'.config', 'agents'",
|
|
259
|
+
zcode: "'.zcode'",
|
|
258
260
|
};
|
|
259
261
|
/**
|
|
260
262
|
* Return the global config-home path-fragment source snippet for a runtime
|
|
@@ -277,7 +279,7 @@ function getGlobalConfigHomeFragment(runtime) {
|
|
|
277
279
|
*/
|
|
278
280
|
const RUNTIME_FLAG_IDS = Object.freeze([
|
|
279
281
|
'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor',
|
|
280
|
-
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi',
|
|
282
|
+
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'zcode',
|
|
281
283
|
]);
|
|
282
284
|
/**
|
|
283
285
|
* Return a frozen map of `is<Runtime>` boolean predicates for the given runtime
|
|
@@ -32,6 +32,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
32
32
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
33
33
|
};
|
|
34
34
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
35
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
35
36
|
const node_path_1 = __importDefault(require("node:path"));
|
|
36
37
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
37
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -261,32 +262,52 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
|
|
|
261
262
|
* Re-stage the active surface using the resolved layout.
|
|
262
263
|
* Iterates layout.kinds and syncs each artifact kind to its destination.
|
|
263
264
|
*/
|
|
264
|
-
function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry) {
|
|
265
|
+
function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts) {
|
|
265
266
|
if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
|
|
266
267
|
throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
|
|
267
268
|
}
|
|
268
269
|
const skillManifest = normalizeSkillManifest(layout.configDir, manifest);
|
|
269
270
|
const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap, registry);
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// the returned dir and clean it up.
|
|
271
|
+
// #1575: agents kind now mirrors createRuntimeArtifactInstallPlan — build
|
|
272
|
+
// agentCtx (pathPrefix + attribution) and pass it to kind.stage() so
|
|
273
|
+
// stageAgentsForRuntimeWithConverter applies the full inline-loop pipeline
|
|
274
|
+
// (pathRewrites -> attribution -> converter -> normalize). Without this,
|
|
275
|
+
// surface-path agents lack path-prefix rewrites and Co-Authored-By trailers,
|
|
276
|
+
// diverging from a fresh install.
|
|
277
|
+
const _homedirFn = opts?.homedir ?? (() => node_os_1.default.homedir());
|
|
278
|
+
const _resolvedTarget = node_path_1.default.resolve(layout.configDir).replace(/\\/g, '/');
|
|
279
|
+
const _homeDir = _homedirFn().replace(/\\/g, '/');
|
|
280
|
+
const _isGlobal = (layout.scope ?? 'global') === 'global';
|
|
281
|
+
const _isOpencode = layout.runtime === 'opencode';
|
|
282
|
+
const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
|
|
283
|
+
const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir });
|
|
284
|
+
const _attribution = opts?.resolveAttribution ? opts.resolveAttribution(layout.runtime) : undefined;
|
|
285
|
+
const agentCtx = { runtime: layout.runtime, pathPrefix: _pathPrefix, attribution: _attribution };
|
|
286
286
|
const tempDirsToClean = [];
|
|
287
|
+
// #1575: When the surface has no state modifications AND the base profile is
|
|
288
|
+
// 'full', pass the '*' sentinel for agents staging so ALL agents are staged —
|
|
289
|
+
// matching the install path which uses { skills: '*' }. Without this, agents
|
|
290
|
+
// not referenced by any skill's _calls_agents_ manifest entry would be silently
|
|
291
|
+
// dropped from the surface path. For tiered profiles (core/standard) or when
|
|
292
|
+
// surface mods exist, pass the resolved set so only the filtered subset stages.
|
|
293
|
+
const _surfaceState = readSurface(layout.configDir);
|
|
294
|
+
const _baseProfileName = (_surfaceState && _surfaceState.baseProfile)
|
|
295
|
+
? _surfaceState.baseProfile
|
|
296
|
+
: (readActiveProfile(layout.configDir) || 'full');
|
|
297
|
+
const _hasSurfaceMods = !!_surfaceState && (_surfaceState.disabledClusters.length > 0 ||
|
|
298
|
+
_surfaceState.explicitAdds.length > 0 ||
|
|
299
|
+
_surfaceState.explicitRemoves.length > 0);
|
|
300
|
+
const _isUnmodifiedFull = _baseProfileName === 'full' && !_hasSurfaceMods;
|
|
287
301
|
try {
|
|
288
302
|
for (const kind of layout.kinds) {
|
|
289
|
-
let staged
|
|
303
|
+
let staged;
|
|
304
|
+
if (kind.kind === 'agents') {
|
|
305
|
+
const agentProfile = _isUnmodifiedFull ? { ...resolved, skills: '*' } : resolved;
|
|
306
|
+
staged = kind.stage(agentProfile, agentCtx);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
staged = kind.stage(resolved);
|
|
310
|
+
}
|
|
290
311
|
if (kind.kind === 'skills') {
|
|
291
312
|
runtimeArtifactConversion.rewriteStagedSkillBodies(staged, {
|
|
292
313
|
runtime: layout.runtime,
|
|
@@ -306,7 +327,7 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry)
|
|
|
306
327
|
}
|
|
307
328
|
}
|
|
308
329
|
const dest = assertDestWithinConfigHome(layout.configDir, kind.destSubpath);
|
|
309
|
-
_syncGsdDir(staged, dest, kind, skillManifest);
|
|
330
|
+
_syncGsdDir(staged, dest, kind, skillManifest, layout.runtime);
|
|
310
331
|
}
|
|
311
332
|
}
|
|
312
333
|
finally {
|
|
@@ -414,13 +435,17 @@ function pruneSkillDirs(skillsDir, retainedNames, prefix, manifest) {
|
|
|
414
435
|
* user-owned dirs. GSD-owned = stem in manifest; removal targets = in manifest AND
|
|
415
436
|
* not in staged set. User-owned (not in manifest) are always preserved.
|
|
416
437
|
*/
|
|
417
|
-
function _syncGsdDir(stagedDir, destDir, kind, manifest) {
|
|
438
|
+
function _syncGsdDir(stagedDir, destDir, kind, manifest, runtime) {
|
|
418
439
|
if (!node_fs_1.default.existsSync(stagedDir))
|
|
419
440
|
return;
|
|
420
441
|
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
421
442
|
// Normalize: allow legacy string context for backward-compat with internal callers
|
|
422
443
|
const kindName = (typeof kind === 'string') ? kind : kind.kind;
|
|
423
444
|
const kindPrefix = (typeof kind === 'object' && kind !== null) ? kind.prefix : 'gsd-';
|
|
445
|
+
// #1575: copilot agents are renamed .md -> .agent.md at copy time, mirroring
|
|
446
|
+
// the inline agent loop in bin/install.js (line ~9118). Other runtimes keep
|
|
447
|
+
// the staged filename verbatim.
|
|
448
|
+
const isCopilotAgents = runtime === 'copilot' && kindName === 'agents';
|
|
424
449
|
if (kindName === 'skills') {
|
|
425
450
|
// Skills kind: work with directories, not files.
|
|
426
451
|
// Each staged entry is a directory named ${prefix}${stem}.
|
|
@@ -455,23 +480,19 @@ function _syncGsdDir(stagedDir, destDir, kind, manifest) {
|
|
|
455
480
|
const stagedFiles = node_fs_1.default.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
|
|
456
481
|
const stagedDestNames = new Set();
|
|
457
482
|
for (const file of stagedFiles) {
|
|
458
|
-
const destName =
|
|
459
|
-
? file
|
|
460
|
-
:
|
|
483
|
+
const destName = isCopilotAgents
|
|
484
|
+
? file.replace(/\.md$/, '.agent.md')
|
|
485
|
+
: (kindName === 'agents' || namespacedByDir)
|
|
486
|
+
? file
|
|
487
|
+
: `${kindPrefix}${file.slice(0, -3)}.md`;
|
|
461
488
|
node_fs_1.default.copyFileSync(node_path_1.default.join(stagedDir, file), node_path_1.default.join(destDir, destName));
|
|
462
489
|
stagedDestNames.add(destName);
|
|
463
490
|
}
|
|
464
491
|
// Prune stale GSD-owned files not in the staged set, preserving user-owned files
|
|
465
492
|
// (mirrors install's prefix-scoped _removeGsdEntries):
|
|
466
|
-
// - agents: only gsd-* are GSD-owned
|
|
493
|
+
// - agents: only gsd-* are GSD-owned (copilot: gsd-*.agent.md)
|
|
467
494
|
// - flat command dirs: only `${kindPrefix}`-prefixed are GSD-owned
|
|
468
495
|
// - namespaced command dirs: the whole dir is GSD-owned
|
|
469
|
-
//
|
|
470
|
-
// Manifest gate (#2018): when the manifest is empty/absent (e.g. an unresolvable
|
|
471
|
-
// install source root yields an empty staged dir), the staged set is untrustworthy.
|
|
472
|
-
// Skills are guarded by pruneSkillDirs' manifest-membership check; agents must be
|
|
473
|
-
// guarded here — skip the prune loop entirely so an empty manifest never deletes
|
|
474
|
-
// every gsd-* agent. Copying (above) still runs so genuinely new agents are added.
|
|
475
496
|
const shouldPruneAgents = !(kindName === 'agents' && (!manifest || manifest.size === 0));
|
|
476
497
|
if (shouldPruneAgents) {
|
|
477
498
|
for (const file of node_fs_1.default.readdirSync(destDir).filter(f => f.endsWith('.md'))) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Standard `gsd_run` Resolver
|
|
2
|
+
|
|
3
|
+
Use this bootstrap block when a workflow must call `gsd-tools` from the active worktree/runtime.
|
|
4
|
+
Keep this resolver centralized; workflows should reference this file instead of copying the shell probe.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
8
|
+
```
|
|
@@ -166,7 +166,7 @@ and implemented by workflows/transition.md and workflows/complete-milestone.md.
|
|
|
166
166
|
|
|
167
167
|
For existing codebases:
|
|
168
168
|
|
|
169
|
-
1. **
|
|
169
|
+
1. **Onboard or map codebase first** via `/gsd:onboard` (recommended first-time path) or `/gsd:map-codebase`
|
|
170
170
|
|
|
171
171
|
2. **Infer Validated requirements** from existing code:
|
|
172
172
|
- What does the codebase actually do?
|
package/gsd-core/workflows/do.md
CHANGED
|
@@ -40,8 +40,9 @@ Evaluate `$ARGUMENTS` against these routing rules. Apply the **first matching**
|
|
|
40
40
|
|
|
41
41
|
| If the text describes... | Route to | Why |
|
|
42
42
|
|--------------------------|----------|-----|
|
|
43
|
-
| Starting a new project, "set up", "initialize" | `/gsd:new-project` | Needs full project initialization |
|
|
44
|
-
|
|
|
43
|
+
| Starting a new greenfield project, "set up", "initialize" | `/gsd:new-project` | Needs full project initialization |
|
|
44
|
+
| First-time setup for an existing codebase, brownfield onboarding | `/gsd:onboard` | Safe map → docs ingest → project setup sequence |
|
|
45
|
+
| Mapping or analyzing an existing codebase map | `/gsd:map-codebase` | Codebase discovery or refresh |
|
|
45
46
|
| A bug, error, crash, failure, or something broken | `/gsd:debug` | Needs systematic investigation |
|
|
46
47
|
| Spiking, "test if", "will this work", "experiment", "prove this out", validate feasibility | `/gsd:spike` | Throwaway experiment to validate feasibility |
|
|
47
48
|
| Sketching, "mockup", "what would this look like", "prototype the UI", "design this", explore visual direction | `/gsd:sketch` | Throwaway HTML mockups to explore design |
|
|
@@ -61,7 +62,7 @@ Evaluate `$ARGUMENTS` against these routing rules. Apply the **first matching**
|
|
|
61
62
|
| Completing a milestone, shipping, releasing | `/gsd:complete-milestone` | Milestone lifecycle |
|
|
62
63
|
| A specific, actionable, small task (add feature, fix typo, update config) | `/gsd:quick` | Self-contained, single executor |
|
|
63
64
|
|
|
64
|
-
**Requires `.planning/` directory:** All routes except `/gsd:new-project`, `/gsd:map-codebase`, `/gsd:spike`, `/gsd:sketch`, and `/gsd:help`. If the project doesn't exist and the route requires it, suggest `/gsd:new-project`
|
|
65
|
+
**Requires `.planning/` directory:** All routes except `/gsd:new-project`, `/gsd:onboard`, `/gsd:map-codebase`, `/gsd:spike`, `/gsd:sketch`, and `/gsd:help`. If the project doesn't exist and the route requires it, suggest `/gsd:onboard` for existing codebases or `/gsd:new-project` for greenfield projects.
|
|
65
66
|
|
|
66
67
|
**Ambiguity handling:** If the text could reasonably match multiple routes, ask the user via AskUserQuestion with the top 2-3 options. For example:
|
|
67
68
|
|