@opengsd/gsd-core 1.5.0-rc.3 → 1.5.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/plugin.json +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-writer.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -0
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.md +55 -2
- package/agents/gsd-security-auditor.md +1 -0
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +13 -2
- package/bin/install.js +36 -57
- package/commands/gsd/progress.md +2 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +167 -3
- package/gsd-core/bin/lib/active-workstream-store.cjs +6 -0
- package/gsd-core/bin/lib/capability-state.cjs +97 -3
- package/gsd-core/bin/lib/capability-writer.cjs +354 -0
- package/gsd-core/bin/lib/config.cjs +80 -24
- package/gsd-core/bin/lib/edge-probe.cjs +25 -2
- package/gsd-core/bin/lib/frontmatter.cjs +53 -1
- package/gsd-core/bin/lib/git-base-branch.cjs +194 -0
- package/gsd-core/bin/lib/init.cjs +28 -6
- package/gsd-core/bin/lib/install-profiles.cjs +55 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/phase.cjs +28 -12
- package/gsd-core/bin/lib/plan-drift-guard.cjs +117 -0
- package/gsd-core/bin/lib/probe-core.cjs +117 -1
- package/gsd-core/bin/lib/roadmap-parser.cjs +13 -3
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +246 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -2
- package/gsd-core/bin/lib/state.cjs +240 -59
- package/gsd-core/bin/lib/verify.cjs +73 -4
- package/gsd-core/bin/lib/worktree-safety.cjs +2 -1
- package/gsd-core/references/edge-probe.md +11 -0
- package/gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
- package/gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
- package/gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
- package/gsd-core/references/prohibition-probe.md +248 -0
- package/gsd-core/templates/spec.md +14 -0
- package/gsd-core/workflows/complete-milestone.md +1 -5
- package/gsd-core/workflows/execute-phase.md +4 -3
- package/gsd-core/workflows/execute-plan.md +21 -6
- package/gsd-core/workflows/help/modes/full.md +4 -0
- package/gsd-core/workflows/next.md +50 -2
- package/gsd-core/workflows/pause-work.md +7 -1
- package/gsd-core/workflows/plan-phase.md +2 -0
- package/gsd-core/workflows/plan-review-convergence.md +14 -4
- package/gsd-core/workflows/pr-branch.md +4 -2
- package/gsd-core/workflows/quick.md +3 -2
- package/gsd-core/workflows/resume-project.md +17 -1
- package/gsd-core/workflows/settings.md +27 -1
- package/gsd-core/workflows/ship.md +1 -5
- package/gsd-core/workflows/spec-phase.md +75 -0
- package/gsd-core/workflows/verify-phase.md +14 -4
- package/hooks/dist/gsd-ensure-canonical-path.js +305 -0
- package/hooks/dist/gsd-statusline.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-ensure-canonical-path.js +305 -0
- package/hooks/gsd-statusline.js +1 -1
- package/hooks/hooks.json +1 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +3 -3
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/new.cjs +17 -3
- package/scripts/fix-slash-commands.cjs +15 -3
- package/scripts/gen-capability-registry.cjs +14 -1
- package/scripts/lint-allow-test-rule-refs.allowlist.json +2 -1
- package/scripts/lint-test-file-count.allowlist.json +6 -0
- package/scripts/mutation-matrix.cjs +108 -7
- package/scripts/pr-target-policy.cjs +63 -0
- package/scripts/research-profiles.cjs +5 -5
- package/scripts/run-tests.cjs +107 -6
|
@@ -63,6 +63,13 @@
|
|
|
63
63
|
* Returns JSON { valid, errors[], slots: {role,capability,outcome} | null }
|
|
64
64
|
* --pick valid Emit bare boolean (for workflow boolean checks)
|
|
65
65
|
*
|
|
66
|
+
* Drift Guard (ADR-22):
|
|
67
|
+
* drift-guard authority Resolve effective source-grounding authority
|
|
68
|
+
* (reads plan_review.source_grounding_authority + intel.enabled from config)
|
|
69
|
+
* drift-guard severity --status <S> Classify a symbol verdict into { severity, hardBlock }
|
|
70
|
+
* [--authority <A>] Status: VERIFIED|MISSING|AMBIGUOUS|UNCHECKABLE
|
|
71
|
+
* Authority: grep|intel|treesitter|lsp|scip (default: config-resolved)
|
|
72
|
+
*
|
|
66
73
|
* Validation:
|
|
67
74
|
* validate consistency Check phase numbering, disk/roadmap sync
|
|
68
75
|
* validate health [--repair] Check .planning/ integrity, optionally repair
|
|
@@ -217,6 +224,7 @@ const verification = require('./lib/verification.cjs');
|
|
|
217
224
|
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
|
218
225
|
const loopResolver = require('./lib/loop-resolver.cjs');
|
|
219
226
|
const capabilityState = require('./lib/capability-state.cjs');
|
|
227
|
+
const capabilityWriter = require('./lib/capability-writer.cjs');
|
|
220
228
|
const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
|
221
229
|
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
|
222
230
|
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
|
|
@@ -225,6 +233,8 @@ const { routeAgentCommand } = require('./lib/agent-command-router.cjs');
|
|
|
225
233
|
const { routeCheckCommand } = require('./lib/check-command-router.cjs');
|
|
226
234
|
const { routeTaskCommand } = require('./lib/task-command-router.cjs');
|
|
227
235
|
const { parseNamedArgs, parseMultiwordArg } = require('./lib/command-arg-projection.cjs');
|
|
236
|
+
const { cmdGitBaseBranch } = require('./lib/git-base-branch.cjs');
|
|
237
|
+
const { getEffectiveAuthority, classifyDriftSeverity } = require('./lib/plan-drift-guard.cjs');
|
|
228
238
|
|
|
229
239
|
// ─── Bridge collapsed (Phase 4) ────────────────────────────────────────────────
|
|
230
240
|
// Non-family commands now run through their CJS handlers directly. Keep the
|
|
@@ -508,10 +518,10 @@ async function main() {
|
|
|
508
518
|
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
509
519
|
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
|
|
510
520
|
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
|
|
511
|
-
'current-timestamp, detect-custom-files, docs-init, effort, extract-messages, find-phase, ' +
|
|
521
|
+
'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
512
522
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
513
523
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
514
|
-
'capability, classify-confidence, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
524
|
+
'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
515
525
|
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
516
526
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
517
527
|
'Global flags:\n' +
|
|
@@ -1303,9 +1313,85 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1303
1313
|
}
|
|
1304
1314
|
const resolvedConfigDir = configDir ? path.resolve(configDir) : null;
|
|
1305
1315
|
capabilityState.cmdCapabilityState(cwd, resolvedConfigDir, raw, {});
|
|
1316
|
+
} else if (capSubcommand === 'set') {
|
|
1317
|
+
// capability set <id> [--on|--off|--enable|--disable] [--gate <key>=<bool>]... [--config-dir <dir>] [--runtime <r>] [--scope <s>]
|
|
1318
|
+
const capId = args[2];
|
|
1319
|
+
if (!capId || capId.startsWith('--')) {
|
|
1320
|
+
error('Missing capability id for: capability set <id>', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1321
|
+
}
|
|
1322
|
+
// Parse --config-dir
|
|
1323
|
+
const setConfigDirIdx = args.indexOf('--config-dir');
|
|
1324
|
+
let setConfigDir = null;
|
|
1325
|
+
if (setConfigDirIdx !== -1) {
|
|
1326
|
+
const setConfigDirVal = args[setConfigDirIdx + 1];
|
|
1327
|
+
if (!setConfigDirVal || setConfigDirVal.startsWith('--')) {
|
|
1328
|
+
error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1329
|
+
}
|
|
1330
|
+
setConfigDir = setConfigDirVal;
|
|
1331
|
+
}
|
|
1332
|
+
const resolvedSetConfigDir = setConfigDir ? path.resolve(setConfigDir) : null;
|
|
1333
|
+
// Parse --on/--enable and --off/--disable (mutually exclusive)
|
|
1334
|
+
const hasOn = args.includes('--on') || args.includes('--enable');
|
|
1335
|
+
const hasOff = args.includes('--off') || args.includes('--disable');
|
|
1336
|
+
if (hasOn && hasOff) {
|
|
1337
|
+
error('Conflicting flags: --on/--enable and --off/--disable cannot both be present', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1338
|
+
}
|
|
1339
|
+
let setEnabled;
|
|
1340
|
+
if (hasOn) {
|
|
1341
|
+
setEnabled = true;
|
|
1342
|
+
} else if (hasOff) {
|
|
1343
|
+
setEnabled = false;
|
|
1344
|
+
}
|
|
1345
|
+
// Parse --gate <key>=<bool> (repeatable)
|
|
1346
|
+
const setGates = {};
|
|
1347
|
+
for (let gi = 0; gi < args.length; gi++) {
|
|
1348
|
+
if (args[gi] === '--gate') {
|
|
1349
|
+
const gateVal = args[gi + 1];
|
|
1350
|
+
if (!gateVal || gateVal.startsWith('--')) {
|
|
1351
|
+
error('Missing value for --gate (expected <key>=<true|false>)', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1352
|
+
}
|
|
1353
|
+
const eqIdx = gateVal.indexOf('=');
|
|
1354
|
+
if (eqIdx === -1) {
|
|
1355
|
+
error(`Malformed --gate value "${gateVal}": expected <key>=<true|false>`, core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1356
|
+
}
|
|
1357
|
+
const gateKey = gateVal.slice(0, eqIdx);
|
|
1358
|
+
const gateBoolStr = gateVal.slice(eqIdx + 1);
|
|
1359
|
+
if (gateBoolStr !== 'true' && gateBoolStr !== 'false') {
|
|
1360
|
+
error(`Malformed --gate value "${gateVal}": bool must be true or false`, core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1361
|
+
}
|
|
1362
|
+
setGates[gateKey] = gateBoolStr === 'true';
|
|
1363
|
+
gi++; // skip consumed value
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
// Parse --runtime and --scope (validate that values are present and not flags)
|
|
1367
|
+
const runtimeIdx = args.indexOf('--runtime');
|
|
1368
|
+
let setRuntime;
|
|
1369
|
+
if (runtimeIdx !== -1) {
|
|
1370
|
+
const runtimeVal = args[runtimeIdx + 1];
|
|
1371
|
+
if (!runtimeVal || runtimeVal.startsWith('--')) {
|
|
1372
|
+
error('Missing value for --runtime', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1373
|
+
}
|
|
1374
|
+
setRuntime = runtimeVal;
|
|
1375
|
+
}
|
|
1376
|
+
const scopeIdx = args.indexOf('--scope');
|
|
1377
|
+
let setScope;
|
|
1378
|
+
if (scopeIdx !== -1) {
|
|
1379
|
+
const scopeVal = args[scopeIdx + 1];
|
|
1380
|
+
if (!scopeVal || scopeVal.startsWith('--')) {
|
|
1381
|
+
error('Missing value for --scope', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1382
|
+
}
|
|
1383
|
+
setScope = scopeVal;
|
|
1384
|
+
}
|
|
1385
|
+
capabilityWriter.cmdCapabilitySet(
|
|
1386
|
+
cwd,
|
|
1387
|
+
resolvedSetConfigDir,
|
|
1388
|
+
capId,
|
|
1389
|
+
{ enabled: setEnabled, gates: Object.keys(setGates).length > 0 ? setGates : undefined, runtime: setRuntime, scope: setScope },
|
|
1390
|
+
raw,
|
|
1391
|
+
);
|
|
1306
1392
|
} else {
|
|
1307
1393
|
error(
|
|
1308
|
-
`Unknown capability subcommand: ${capSubcommand}. Available: state`,
|
|
1394
|
+
`Unknown capability subcommand: ${capSubcommand}. Available: state, set`,
|
|
1309
1395
|
core.ERROR_REASON ? core.ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
|
|
1310
1396
|
);
|
|
1311
1397
|
}
|
|
@@ -1948,6 +2034,23 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1948
2034
|
// Each slot must be non-empty and contain non-whitespace content.
|
|
1949
2035
|
//
|
|
1950
2036
|
// No .planning/ access needed — pure string validation.
|
|
2037
|
+
|
|
2038
|
+
// #1146: single base-branch resolver for all forking workflows.
|
|
2039
|
+
// Workflows call `gsd_run query git.base-branch` (dotted form normalised to
|
|
2040
|
+
// command='git', args=['git','base-branch']).
|
|
2041
|
+
case 'git': {
|
|
2042
|
+
const subcommand = args[1];
|
|
2043
|
+
if (subcommand !== 'base-branch') {
|
|
2044
|
+
error(
|
|
2045
|
+
`Unknown git subcommand: ${subcommand || '(none)'}. Available: base-branch`,
|
|
2046
|
+
ERROR_REASON.SDK_UNKNOWN_COMMAND,
|
|
2047
|
+
);
|
|
2048
|
+
break;
|
|
2049
|
+
}
|
|
2050
|
+
cmdGitBaseBranch(cwd, args.slice(2));
|
|
2051
|
+
break;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1951
2054
|
case 'user-story': {
|
|
1952
2055
|
const subcommand = args[1];
|
|
1953
2056
|
if (subcommand !== 'validate') {
|
|
@@ -2002,6 +2105,67 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
2002
2105
|
break;
|
|
2003
2106
|
}
|
|
2004
2107
|
|
|
2108
|
+
case 'drift-guard': {
|
|
2109
|
+
// ADR-22: deterministic authority resolution + severity classification.
|
|
2110
|
+
// Subcommands:
|
|
2111
|
+
// drift-guard authority → effective authority string
|
|
2112
|
+
// drift-guard severity --status <S> [--authority <A>] → {severity, hardBlock}
|
|
2113
|
+
const subcommand = args[1];
|
|
2114
|
+
|
|
2115
|
+
// Read config.json directly for both plan_review.source_grounding_authority
|
|
2116
|
+
// and intel.enabled. Neither key is in the config-loader.cjs whitelist that
|
|
2117
|
+
// core.loadConfig() returns; plan_review is only in config.cjs's private
|
|
2118
|
+
// buildConfig(), and intel is a federated capability config key.
|
|
2119
|
+
let configuredAuthority = 'grep';
|
|
2120
|
+
let intelEnabled = false;
|
|
2121
|
+
try {
|
|
2122
|
+
const { planningDir } = require('./lib/planning-workspace.cjs');
|
|
2123
|
+
const cfgPath = require('path').join(planningDir(cwd), 'config.json');
|
|
2124
|
+
if (require('fs').existsSync(cfgPath)) {
|
|
2125
|
+
const rawCfg = JSON.parse(require('fs').readFileSync(cfgPath, 'utf-8'));
|
|
2126
|
+
if (rawCfg && rawCfg.plan_review && rawCfg.plan_review.source_grounding_authority) {
|
|
2127
|
+
configuredAuthority = String(rawCfg.plan_review.source_grounding_authority);
|
|
2128
|
+
}
|
|
2129
|
+
if (rawCfg && rawCfg.intel && rawCfg.intel.enabled === true) {
|
|
2130
|
+
intelEnabled = true;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
} catch {
|
|
2134
|
+
// not fatal — defaults apply
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
const effectiveAuthority = getEffectiveAuthority(configuredAuthority, intelEnabled);
|
|
2138
|
+
|
|
2139
|
+
if (subcommand === 'authority') {
|
|
2140
|
+
// Pass rawValue as 3rd arg so --raw returns unquoted string (not JSON)
|
|
2141
|
+
core.output(effectiveAuthority, raw, effectiveAuthority);
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
if (subcommand === 'severity') {
|
|
2146
|
+
const statusIdx = args.indexOf('--status');
|
|
2147
|
+
const statusVal = statusIdx !== -1 ? args[statusIdx + 1] : undefined;
|
|
2148
|
+
if (!statusVal || statusVal.startsWith('--')) {
|
|
2149
|
+
error('drift-guard severity requires --status <VERIFIED|MISSING|AMBIGUOUS|UNCHECKABLE>', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2150
|
+
break;
|
|
2151
|
+
}
|
|
2152
|
+
const authIdx = args.indexOf('--authority');
|
|
2153
|
+
const authVal = authIdx !== -1 ? args[authIdx + 1] : undefined;
|
|
2154
|
+
const authorityForClassify = (authVal && !authVal.startsWith('--'))
|
|
2155
|
+
? authVal
|
|
2156
|
+
: effectiveAuthority;
|
|
2157
|
+
const result = classifyDriftSeverity({ status: statusVal, authority: authorityForClassify });
|
|
2158
|
+
core.output(result, raw);
|
|
2159
|
+
break;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
error(
|
|
2163
|
+
`Unknown drift-guard subcommand: ${subcommand || '(none)'}. Available: authority, severity`,
|
|
2164
|
+
ERROR_REASON.SDK_UNKNOWN_COMMAND,
|
|
2165
|
+
);
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2005
2169
|
default: {
|
|
2006
2170
|
// ADR-959: try capability-registry dispatch before emitting the unknown-command error.
|
|
2007
2171
|
// An unmigrated command still hits its hardcoded `case` above — untouched.
|
|
@@ -47,6 +47,11 @@ function sanitizeWorkstreamSessionToken(value) {
|
|
|
47
47
|
const token = raw.trim().replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
48
48
|
return token ? token.slice(0, 160) : null;
|
|
49
49
|
}
|
|
50
|
+
/** Test-only seam: clear the memoized controlling-TTY probe cache (#1191). */
|
|
51
|
+
function _resetControllingTtyCacheForTests() {
|
|
52
|
+
cachedControllingTtyToken = null;
|
|
53
|
+
didProbeControllingTtyToken = false;
|
|
54
|
+
}
|
|
50
55
|
function probeControllingTtyToken() {
|
|
51
56
|
if (didProbeControllingTtyToken)
|
|
52
57
|
return cachedControllingTtyToken;
|
|
@@ -288,4 +293,5 @@ module.exports = {
|
|
|
288
293
|
parseCliWorkstream,
|
|
289
294
|
resolveActiveWorkstream,
|
|
290
295
|
applyResolvedWorkstreamEnv,
|
|
296
|
+
_resetControllingTtyCacheForTests,
|
|
291
297
|
};
|
|
@@ -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_path_1 = __importDefault(require("node:path"));
|
|
35
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
35
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
37
|
const core = require("./core.cjs");
|
|
37
38
|
const { output: coreOutput, error: coreError } = core;
|
|
@@ -43,7 +44,7 @@ const configLoaderMod = require("./config-loader.cjs");
|
|
|
43
44
|
const { loadConfig } = configLoaderMod;
|
|
44
45
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
45
46
|
const installProfilesMod = require("./install-profiles.cjs");
|
|
46
|
-
const { readActiveProfile, loadSkillsManifest, resolveProfile } = installProfilesMod;
|
|
47
|
+
const { readActiveProfile, loadSkillsManifest, resolveProfile, parseRequires } = installProfilesMod;
|
|
47
48
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
49
|
const surfaceMod = require("./surface.cjs");
|
|
49
50
|
const { resolveSurface } = surfaceMod;
|
|
@@ -193,6 +194,93 @@ function _resolveCommandsGsdDir() {
|
|
|
193
194
|
const repoRoot = node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
194
195
|
return node_path_1.default.join(repoRoot, 'commands', 'gsd');
|
|
195
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Build a skill dependency manifest from an INSTALLED runtime's skills directory.
|
|
199
|
+
*
|
|
200
|
+
* In an installed runtime (e.g. Codex at ~/.codex), gsd skills live as
|
|
201
|
+
* configDir/skills/gsd-STEM/SKILL.md. There is no commands/gsd source tree.
|
|
202
|
+
* This function scans that installed layout and builds the same
|
|
203
|
+
* Map shape that loadSkillsManifest produces from sources.
|
|
204
|
+
*
|
|
205
|
+
* Stem extraction: a directory named gsd-secure-phase maps to stem secure-phase.
|
|
206
|
+
* Only directories whose names start with gsd- are included so user-created
|
|
207
|
+
* skills (without the gsd- prefix) are not accidentally pulled in.
|
|
208
|
+
*
|
|
209
|
+
* The requires: field is parsed via the shared parseRequires helper (the same
|
|
210
|
+
* parser loadSkillsManifest uses), so the two paths cannot drift.
|
|
211
|
+
*
|
|
212
|
+
* Returns an empty Map when the skills dir does not exist.
|
|
213
|
+
*/
|
|
214
|
+
function _loadInstalledSkillsManifest(configDir) {
|
|
215
|
+
const manifest = new Map();
|
|
216
|
+
const skillsDir = node_path_1.default.join(configDir, 'skills');
|
|
217
|
+
if (!node_fs_1.default.existsSync(skillsDir))
|
|
218
|
+
return manifest;
|
|
219
|
+
let entries;
|
|
220
|
+
try {
|
|
221
|
+
entries = node_fs_1.default.readdirSync(skillsDir, { withFileTypes: true });
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return manifest;
|
|
225
|
+
}
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
if (!entry.isDirectory())
|
|
228
|
+
continue;
|
|
229
|
+
if (!entry.name.startsWith('gsd-'))
|
|
230
|
+
continue;
|
|
231
|
+
// Strip the 'gsd-' prefix to get the skill stem
|
|
232
|
+
const stem = entry.name.slice(4); // 'gsd-'.length === 4
|
|
233
|
+
if (!stem)
|
|
234
|
+
continue;
|
|
235
|
+
const skillMdPath = node_path_1.default.join(skillsDir, entry.name, 'SKILL.md');
|
|
236
|
+
// Parity with loadSkillsManifest: a stem exists only when its artifact
|
|
237
|
+
// file is present. loadSkillsManifest registers a stem per .md FILE (and
|
|
238
|
+
// tolerates an unreadable file as []), but never invents a stem for which
|
|
239
|
+
// no file exists. Mirror that here: a stale gsd-<stem>/ directory with no
|
|
240
|
+
// SKILL.md must NOT register the stem — otherwise the capability would be
|
|
241
|
+
// wrongly reported surfaced/enabled and a verify:post hook would render
|
|
242
|
+
// for a skill that cannot run.
|
|
243
|
+
if (!node_fs_1.default.existsSync(skillMdPath))
|
|
244
|
+
continue;
|
|
245
|
+
let content = '';
|
|
246
|
+
try {
|
|
247
|
+
content = node_fs_1.default.readFileSync(skillMdPath, 'utf8');
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// SKILL.md present but unreadable — register with no deps (parity with
|
|
251
|
+
// loadSkillsManifest's readFileSync catch branch).
|
|
252
|
+
}
|
|
253
|
+
// Parse requires: via the SAME shared parser loadSkillsManifest uses, so
|
|
254
|
+
// installed-runtime dependency resolution can never silently diverge from
|
|
255
|
+
// the source-tree path (single source of truth — no duplicated regex).
|
|
256
|
+
manifest.set(stem, content ? parseRequires(content) : []);
|
|
257
|
+
// Mirror loadSkillsManifest's Map shape: it always sets a companion
|
|
258
|
+
// `_calls_agents_<stem>` key. Installed SKILL.md bodies carry no
|
|
259
|
+
// recoverable agent-call refs, so [] (the no-agents case) keeps the two
|
|
260
|
+
// manifest shapes identical and prevents undefined-vs-[] drift for any
|
|
261
|
+
// consumer that reads the agent-refs companion key.
|
|
262
|
+
manifest.set(`_calls_agents_${stem}`, []);
|
|
263
|
+
}
|
|
264
|
+
return manifest;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Resolve the skill dependency manifest for capability-state resolution.
|
|
268
|
+
*
|
|
269
|
+
* Resolution order (fixes #1160 — installed-runtime capability surface):
|
|
270
|
+
* 1. If commandsGsdDir exists, load from source (repo-checkout behavior).
|
|
271
|
+
* 2. Otherwise, fall back to installed skills at configDir/skills/gsd-[stem]/SKILL.md.
|
|
272
|
+
*
|
|
273
|
+
* In an installed runtime the commands/gsd source tree is absent; only the
|
|
274
|
+
* skills/ layout exists. Returning an empty manifest caused resolveSurface to
|
|
275
|
+
* materialise the full-sentinel to an empty Set, making every capability appear
|
|
276
|
+
* unsurfaced even when the skill was physically installed.
|
|
277
|
+
*/
|
|
278
|
+
function _resolveManifest(commandsGsdDir, configDir) {
|
|
279
|
+
if (node_fs_1.default.existsSync(commandsGsdDir)) {
|
|
280
|
+
return loadSkillsManifest(commandsGsdDir);
|
|
281
|
+
}
|
|
282
|
+
return _loadInstalledSkillsManifest(configDir);
|
|
283
|
+
}
|
|
196
284
|
/**
|
|
197
285
|
* Command entry point: resolve install profile, surface, and config; compute
|
|
198
286
|
* capability state; emit the envelope via core.output.
|
|
@@ -265,7 +353,9 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir) {
|
|
|
265
353
|
let installedSkills;
|
|
266
354
|
try {
|
|
267
355
|
const commandsGsdDir = _resolveCommandsGsdDir();
|
|
268
|
-
|
|
356
|
+
// Fix #1160: use _resolveManifest so installed-runtime layouts (where
|
|
357
|
+
// commands/gsd is absent) fall back to <configDir>/skills/gsd-*/SKILL.md.
|
|
358
|
+
const manifest = _resolveManifest(commandsGsdDir, resolvedConfigDir);
|
|
269
359
|
const profileName = readActiveProfile(resolvedConfigDir) ?? 'full';
|
|
270
360
|
const resolvedInstall = resolveProfile({
|
|
271
361
|
modes: profileName.split(',').map((s) => s.trim()),
|
|
@@ -285,7 +375,9 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir) {
|
|
|
285
375
|
let surfacedSkills;
|
|
286
376
|
try {
|
|
287
377
|
const commandsGsdDir = _resolveCommandsGsdDir();
|
|
288
|
-
|
|
378
|
+
// Fix #1160: use _resolveManifest so installed-runtime layouts (where
|
|
379
|
+
// commands/gsd is absent) fall back to <configDir>/skills/gsd-*/SKILL.md.
|
|
380
|
+
const manifest = _resolveManifest(commandsGsdDir, resolvedConfigDir);
|
|
289
381
|
const surfaceResult = resolveSurface(resolvedConfigDir, manifest, undefined, registry);
|
|
290
382
|
// resolveSurface returns { name, skills: Set<string>, agents: Set<string> }
|
|
291
383
|
// (always a concrete Set — full profile is materialized)
|
|
@@ -345,5 +437,7 @@ module.exports = {
|
|
|
345
437
|
cmdCapabilityState,
|
|
346
438
|
// Exported for tests
|
|
347
439
|
_resolveCommandsGsdDir,
|
|
440
|
+
_loadInstalledSkillsManifest,
|
|
441
|
+
_resolveManifest,
|
|
348
442
|
_isSafePropKey,
|
|
349
443
|
};
|