@opengsd/gsd-core 1.5.0-rc.4 → 1.5.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.
Files changed (89) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/LICENSE +1 -1
  3. package/agents/gsd-executor.md +22 -0
  4. package/agents/gsd-phase-researcher.md +1 -1
  5. package/agents/gsd-planner.md +4 -0
  6. package/agents/gsd-project-researcher.md +1 -1
  7. package/agents/gsd-verifier.md +45 -13
  8. package/bin/install.js +56 -100
  9. package/commands/gsd/autonomous.md +1 -1
  10. package/commands/gsd/execute-phase.md +1 -1
  11. package/commands/gsd/plan-phase.md +1 -1
  12. package/gemini-extension.json +1 -1
  13. package/gsd-core/bin/gsd-tools.cjs +85 -53
  14. package/gsd-core/bin/lib/agent-command-router.cjs +2 -2
  15. package/gsd-core/bin/lib/agent-install-check.cjs +143 -0
  16. package/gsd-core/bin/lib/audit-command-router.cjs +4 -4
  17. package/gsd-core/bin/lib/capability-activation.cjs +37 -10
  18. package/gsd-core/bin/lib/capability-registry.cjs +2 -0
  19. package/gsd-core/bin/lib/capability-state.cjs +80 -30
  20. package/gsd-core/bin/lib/capability-writer.cjs +5 -4
  21. package/gsd-core/bin/lib/check-command-router.cjs +50 -51
  22. package/gsd-core/bin/lib/commands.cjs +20 -2
  23. package/gsd-core/bin/lib/config-loader.cjs +3 -4
  24. package/gsd-core/bin/lib/config-schema.cjs +1 -1
  25. package/gsd-core/bin/lib/config-types.cjs +2 -1
  26. package/gsd-core/bin/lib/config.cjs +5 -2
  27. package/gsd-core/bin/lib/decisions.cjs +19 -1
  28. package/gsd-core/bin/lib/docs.cjs +14 -2
  29. package/gsd-core/bin/lib/frontmatter.cjs +2 -2
  30. package/gsd-core/bin/lib/gap-checker.cjs +5 -2
  31. package/gsd-core/bin/lib/git-base-branch.cjs +27 -1
  32. package/gsd-core/bin/lib/graphify-command-router.cjs +6 -8
  33. package/gsd-core/bin/lib/graphify.cjs +7 -31
  34. package/gsd-core/bin/lib/gsd2-import.cjs +2 -2
  35. package/gsd-core/bin/lib/init.cjs +30 -4
  36. package/gsd-core/bin/lib/intel-command-router.cjs +6 -3
  37. package/gsd-core/bin/lib/intel.cjs +28 -34
  38. package/gsd-core/bin/lib/io.cjs +2 -4
  39. package/gsd-core/bin/lib/learnings.cjs +2 -2
  40. package/gsd-core/bin/lib/loop-resolver.cjs +45 -167
  41. package/gsd-core/bin/lib/milestone.cjs +13 -5
  42. package/gsd-core/bin/lib/model-resolver.cjs +3 -4
  43. package/gsd-core/bin/lib/phase-id.cjs +3 -5
  44. package/gsd-core/bin/lib/phase-locator.cjs +3 -6
  45. package/gsd-core/bin/lib/phase.cjs +59 -13
  46. package/gsd-core/bin/lib/probe-core.cjs +40 -11
  47. package/gsd-core/bin/lib/profile-output.cjs +5 -2
  48. package/gsd-core/bin/lib/prohibition-enforcement.cjs +660 -0
  49. package/gsd-core/bin/lib/roadmap-command-router.cjs +2 -2
  50. package/gsd-core/bin/lib/roadmap-parser.cjs +28 -25
  51. package/gsd-core/bin/lib/roadmap.cjs +9 -4
  52. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +4 -1
  53. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +24 -3
  54. package/gsd-core/bin/lib/state.cjs +75 -17
  55. package/gsd-core/bin/lib/task-command-router.cjs +2 -2
  56. package/gsd-core/bin/lib/teams-status.cjs +74 -0
  57. package/gsd-core/bin/lib/template.cjs +11 -2
  58. package/gsd-core/bin/lib/uat.cjs +64 -2
  59. package/gsd-core/bin/lib/verification.cjs +8 -5
  60. package/gsd-core/bin/lib/verify.cjs +311 -4
  61. package/gsd-core/bin/lib/workstream-inventory.cjs +2 -2
  62. package/gsd-core/bin/lib/workstream.cjs +8 -2
  63. package/gsd-core/bin/lib/worktree-safety.cjs +44 -3
  64. package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
  65. package/gsd-core/references/planner-antipatterns.md +46 -0
  66. package/gsd-core/references/planning-config.md +5 -1
  67. package/gsd-core/references/prohibition-probe.md +80 -2
  68. package/gsd-core/references/worktree-branch-check.md +11 -5
  69. package/gsd-core/templates/verification-report.md +16 -3
  70. package/gsd-core/workflows/docs-update.md +23 -31
  71. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
  72. package/gsd-core/workflows/execute-phase.md +7 -2
  73. package/gsd-core/workflows/map-codebase.md +8 -10
  74. package/gsd-core/workflows/plan-phase.md +6 -0
  75. package/gsd-core/workflows/quick.md +26 -2
  76. package/gsd-core/workflows/settings-advanced.md +5 -5
  77. package/gsd-core/workflows/settings-integrations.md +5 -5
  78. package/gsd-core/workflows/spec-phase.md +30 -2
  79. package/gsd-core/workflows/verify-phase.md +22 -7
  80. package/hooks/dist/gsd-worktree-path-guard.js +34 -18
  81. package/hooks/gsd-worktree-path-guard.js +34 -18
  82. package/package.json +3 -3
  83. package/scripts/ci-prepare-test-scope.cjs +56 -14
  84. package/scripts/diff-touches-shipped-paths.cjs +5 -11
  85. package/scripts/gen-capability-registry.cjs +27 -1
  86. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  87. package/scripts/research-profiles.cjs +2 -2
  88. package/scripts/run-tests.cjs +1 -0
  89. package/gsd-core/bin/lib/core.cjs +0 -345
@@ -194,13 +194,14 @@
194
194
  const fs = require('fs');
195
195
  const path = require('path');
196
196
  const { ExitError, runMain } = require('./lib/cli-exit.cjs');
197
- const core = require('./lib/core.cjs');
198
- const { error, ERROR_REASON } = core;
197
+ const io = require('./lib/io.cjs');
198
+ const { error, ERROR_REASON, setJsonErrorMode, output } = io;
199
+ const projectRoot = require('./lib/project-root.cjs');
199
200
  // Resolve findProjectRoot lazily at call time rather than binding it at module
200
- // load. It is a re-export from core.cjs (sourced from project-root.cjs); a
201
- // call-time lookup is robust against any require/load-ordering edge where the
202
- // re-export isn't bound yet when this entrypoint is first required (#604).
203
- const findProjectRoot = (...args) => core.findProjectRoot(...args);
201
+ // load. It is sourced from project-root.cjs; a call-time lookup is robust
202
+ // against any require/load-ordering edge where the export isn't bound yet
203
+ // when this entrypoint is first required (#604).
204
+ const findProjectRoot = (...args) => projectRoot.findProjectRoot(...args);
204
205
  const { getActiveWorkstream } = require('./lib/planning-workspace.cjs');
205
206
  const { resolveActiveWorkstream, applyResolvedWorkstreamEnv } = require('./lib/active-workstream-store.cjs');
206
207
  const state = require('./lib/state.cjs');
@@ -255,7 +256,7 @@ const { getEffectiveAuthority, classifyDriftSeverity } = require('./lib/plan-dri
255
256
  * @param {string} opts.cwd - project dir
256
257
  * @param {boolean} opts.raw - raw output mode
257
258
  * @param {Function} opts.error - error reporter
258
- * @param {Function} opts.output - output emitter (core.output)
259
+ * @param {Function} opts.output - output emitter (output)
259
260
  */
260
261
  function _dispatchNonFamily({ registryCommand, registryArgs, legacyCommand, legacyArgs, cwd, raw, error, output }) {
261
262
  void registryCommand;
@@ -294,7 +295,7 @@ function _dispatchNonFamily({ registryCommand, registryArgs, legacyCommand, lega
294
295
  * @param {string[]} opts.args Remaining args passed to the router
295
296
  * @param {string} opts.cwd Project working directory
296
297
  * @param {boolean} opts.raw Raw output mode flag
297
- * @param {Function} opts.error Error reporter (core.error)
298
+ * @param {Function} opts.error Error reporter (io.error)
298
299
  * @param {object} [opts.registry] Injectable registry (for tests)
299
300
  * @param {Function} [opts.requireModule] Injectable module loader (for tests)
300
301
  * @returns {boolean} true if the command was dispatched, false otherwise
@@ -404,10 +405,10 @@ async function main() {
404
405
  // their plain-text diagnostic.
405
406
  const jsonErrorsIdx = args.indexOf('--json-errors');
406
407
  if (jsonErrorsIdx !== -1) {
407
- core.setJsonErrorMode(true);
408
+ setJsonErrorMode(true);
408
409
  args.splice(jsonErrorsIdx, 1);
409
410
  } else if (process.env.GSD_JSON_ERRORS === '1') {
410
- core.setJsonErrorMode(true);
411
+ setJsonErrorMode(true);
411
412
  }
412
413
 
413
414
  // Optional cwd override for sandboxed subagents running outside project root.
@@ -433,7 +434,7 @@ async function main() {
433
434
  // Resolve worktree root: in a linked worktree, .planning/ lives in the main worktree.
434
435
  // However, in monorepo worktrees where the subdirectory itself owns .planning/,
435
436
  // skip worktree resolution — the CWD is already the correct project root.
436
- const { resolveWorktreeRoot } = require('./lib/core.cjs');
437
+ const { resolveWorktreeRoot } = require('./lib/worktree-safety.cjs');
437
438
  if (!fs.existsSync(path.join(cwd, '.planning'))) {
438
439
  const worktreeRoot = resolveWorktreeRoot(cwd);
439
440
  if (worktreeRoot !== cwd) {
@@ -596,7 +597,7 @@ async function main() {
596
597
  }
597
598
 
598
599
  // Intercept stdout to transparently resolve @file: references (#1891).
599
- // core.cjs output() writes @file:<path> when JSON > 50KB. The --pick path
600
+ // io.cjs output() writes @file:<path> when JSON > 50KB. The --pick path
600
601
  // already resolves this, but the normal path wrote @file: to stdout, forcing
601
602
  // every workflow to have a bash-specific `if [[ "$INIT" == @file:* ]]` check
602
603
  // that breaks on PowerShell and other non-bash shells.
@@ -802,7 +803,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
802
803
  cwd,
803
804
  raw,
804
805
  error,
805
- output: core.output,
806
+ output: output,
806
807
  });
807
808
  if (!handled) phase.cmdFindPhase(cwd, args[1], raw);
808
809
  break;
@@ -895,7 +896,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
895
896
  cwd,
896
897
  raw,
897
898
  error,
898
- output: core.output,
899
+ output: output,
899
900
  });
900
901
  if (handled) break;
901
902
  }
@@ -957,7 +958,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
957
958
  cwd,
958
959
  raw,
959
960
  error,
960
- output: core.output,
961
+ output: output,
961
962
  });
962
963
  if (!handled) commands.cmdGenerateSlug(args[1], raw);
963
964
  break;
@@ -997,7 +998,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
997
998
  cwd,
998
999
  raw,
999
1000
  error,
1000
- output: core.output,
1001
+ output: output,
1001
1002
  });
1002
1003
  if (!handled) config.cmdConfigEnsureSection(cwd, raw);
1003
1004
  break;
@@ -1013,7 +1014,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1013
1014
  cwd,
1014
1015
  raw,
1015
1016
  error,
1016
- output: core.output,
1017
+ output: output,
1017
1018
  });
1018
1019
  if (!handled) config.cmdConfigSet(cwd, args[1], args[2], raw);
1019
1020
  break;
@@ -1029,7 +1030,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1029
1030
  cwd,
1030
1031
  raw,
1031
1032
  error,
1032
- output: core.output,
1033
+ output: output,
1033
1034
  });
1034
1035
  if (!handled) config.cmdConfigSetModelProfile(cwd, args[1], raw);
1035
1036
  break;
@@ -1054,7 +1055,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1054
1055
  cwd,
1055
1056
  raw,
1056
1057
  error,
1057
- output: core.output,
1058
+ output: output,
1058
1059
  });
1059
1060
  if (!handled) config.cmdConfigGet(cwd, args[1], raw, defaultValue);
1060
1061
  break;
@@ -1070,7 +1071,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1070
1071
  cwd,
1071
1072
  raw,
1072
1073
  error,
1073
- output: core.output,
1074
+ output: output,
1074
1075
  });
1075
1076
  if (!handled) config.cmdConfigNewProject(cwd, args[1], raw);
1076
1077
  break;
@@ -1183,7 +1184,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1183
1184
  args,
1184
1185
  cwd,
1185
1186
  raw,
1186
- output: core.output,
1187
+ output: output,
1187
1188
  error,
1188
1189
  });
1189
1190
  break;
@@ -1255,12 +1256,12 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1255
1256
  const configDirIdx = args.indexOf('--config-dir');
1256
1257
  if (configDirEqArg) {
1257
1258
  const value = configDirEqArg.slice('--config-dir='.length).trim();
1258
- if (!value) error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1259
+ if (!value) error('Missing value for --config-dir', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1259
1260
  loopConfigDir = value;
1260
1261
  } else if (configDirIdx !== -1) {
1261
1262
  const value = args[configDirIdx + 1];
1262
1263
  if (!value || value.startsWith('--')) {
1263
- error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1264
+ error('Missing value for --config-dir', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1264
1265
  }
1265
1266
  loopConfigDir = value;
1266
1267
  }
@@ -1270,12 +1271,12 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1270
1271
  const activeCapIdx = args.indexOf('--active-cap');
1271
1272
  if (activeCapEqArg) {
1272
1273
  const value = activeCapEqArg.slice('--active-cap='.length).trim();
1273
- if (!value) error('Missing value for --active-cap (e.g. --active-cap tdd)', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1274
+ if (!value) error('Missing value for --active-cap (e.g. --active-cap tdd)', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1274
1275
  loopActiveCap = value;
1275
1276
  } else if (activeCapIdx !== -1) {
1276
1277
  const value = args[activeCapIdx + 1];
1277
1278
  if (!value || value.startsWith('--')) {
1278
- error('Missing value for --active-cap (e.g. --active-cap tdd)', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1279
+ error('Missing value for --active-cap (e.g. --active-cap tdd)', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1279
1280
  }
1280
1281
  loopActiveCap = value;
1281
1282
  }
@@ -1286,7 +1287,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1286
1287
  } else {
1287
1288
  error(
1288
1289
  `Unknown loop subcommand: ${loopSubcommand}. Available: render-hooks`,
1289
- core.ERROR_REASON ? core.ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
1290
+ ERROR_REASON ? ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
1290
1291
  );
1291
1292
  }
1292
1293
  break;
@@ -1307,7 +1308,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1307
1308
  const configDirVal = args[configDirIdx + 1];
1308
1309
  // Validate that --config-dir has a following non-flag value.
1309
1310
  if (!configDirVal || configDirVal.startsWith('--')) {
1310
- error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1311
+ error('Missing value for --config-dir', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1311
1312
  }
1312
1313
  configDir = configDirVal;
1313
1314
  }
@@ -1317,7 +1318,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1317
1318
  // capability set <id> [--on|--off|--enable|--disable] [--gate <key>=<bool>]... [--config-dir <dir>] [--runtime <r>] [--scope <s>]
1318
1319
  const capId = args[2];
1319
1320
  if (!capId || capId.startsWith('--')) {
1320
- error('Missing capability id for: capability set <id>', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1321
+ error('Missing capability id for: capability set <id>', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1321
1322
  }
1322
1323
  // Parse --config-dir
1323
1324
  const setConfigDirIdx = args.indexOf('--config-dir');
@@ -1325,7 +1326,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1325
1326
  if (setConfigDirIdx !== -1) {
1326
1327
  const setConfigDirVal = args[setConfigDirIdx + 1];
1327
1328
  if (!setConfigDirVal || setConfigDirVal.startsWith('--')) {
1328
- error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1329
+ error('Missing value for --config-dir', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1329
1330
  }
1330
1331
  setConfigDir = setConfigDirVal;
1331
1332
  }
@@ -1334,7 +1335,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1334
1335
  const hasOn = args.includes('--on') || args.includes('--enable');
1335
1336
  const hasOff = args.includes('--off') || args.includes('--disable');
1336
1337
  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
+ error('Conflicting flags: --on/--enable and --off/--disable cannot both be present', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1338
1339
  }
1339
1340
  let setEnabled;
1340
1341
  if (hasOn) {
@@ -1348,16 +1349,16 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1348
1349
  if (args[gi] === '--gate') {
1349
1350
  const gateVal = args[gi + 1];
1350
1351
  if (!gateVal || gateVal.startsWith('--')) {
1351
- error('Missing value for --gate (expected <key>=<true|false>)', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1352
+ error('Missing value for --gate (expected <key>=<true|false>)', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1352
1353
  }
1353
1354
  const eqIdx = gateVal.indexOf('=');
1354
1355
  if (eqIdx === -1) {
1355
- error(`Malformed --gate value "${gateVal}": expected <key>=<true|false>`, core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1356
+ error(`Malformed --gate value "${gateVal}": expected <key>=<true|false>`, ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1356
1357
  }
1357
1358
  const gateKey = gateVal.slice(0, eqIdx);
1358
1359
  const gateBoolStr = gateVal.slice(eqIdx + 1);
1359
1360
  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
+ error(`Malformed --gate value "${gateVal}": bool must be true or false`, ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1361
1362
  }
1362
1363
  setGates[gateKey] = gateBoolStr === 'true';
1363
1364
  gi++; // skip consumed value
@@ -1369,7 +1370,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1369
1370
  if (runtimeIdx !== -1) {
1370
1371
  const runtimeVal = args[runtimeIdx + 1];
1371
1372
  if (!runtimeVal || runtimeVal.startsWith('--')) {
1372
- error('Missing value for --runtime', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1373
+ error('Missing value for --runtime', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1373
1374
  }
1374
1375
  setRuntime = runtimeVal;
1375
1376
  }
@@ -1378,7 +1379,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1378
1379
  if (scopeIdx !== -1) {
1379
1380
  const scopeVal = args[scopeIdx + 1];
1380
1381
  if (!scopeVal || scopeVal.startsWith('--')) {
1381
- error('Missing value for --scope', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1382
+ error('Missing value for --scope', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
1382
1383
  }
1383
1384
  setScope = scopeVal;
1384
1385
  }
@@ -1392,7 +1393,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1392
1393
  } else {
1393
1394
  error(
1394
1395
  `Unknown capability subcommand: ${capSubcommand}. Available: state, set`,
1395
- core.ERROR_REASON ? core.ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
1396
+ ERROR_REASON ? ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
1396
1397
  );
1397
1398
  }
1398
1399
  break;
@@ -1484,7 +1485,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1484
1485
  cwd,
1485
1486
  raw,
1486
1487
  error,
1487
- output: core.output,
1488
+ output: output,
1488
1489
  });
1489
1490
  if (!handled) docs.cmdDocsInit(cwd, raw);
1490
1491
  break;
@@ -1518,6 +1519,17 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1518
1519
  break;
1519
1520
  }
1520
1521
 
1522
+ // ─── teams-status ──────────────────────────────────────────────────────
1523
+ // Read-only detector for claude-code's experimental agent-teams feature.
1524
+ // issue #1355: stop gsd-core hanging silently under claude-code agent-teams.
1525
+ // No capability registration needed — this is a diagnostic query command,
1526
+ // not a feature capability.
1527
+ case 'teams-status': {
1528
+ const teamsStatus = require('./lib/teams-status.cjs');
1529
+ teamsStatus.cmdTeamsStatus(cwd, { active: args.includes('--active') });
1530
+ break;
1531
+ }
1532
+
1521
1533
  // ─── detect-custom-files ───────────────────────────────────────────────
1522
1534
  // CJS-native: no SDK counterpart exists in the command registry.
1523
1535
  // detect-custom-files reads a gsd-file-manifest.json against the
@@ -1565,18 +1577,30 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1565
1577
 
1566
1578
  const manifestKeys = new Set(Object.keys(manifest.files || {}));
1567
1579
 
1568
- // GSD-managed directories to scan for user-added files.
1569
- // These are the directories the installer wipes on update.
1570
- const GSD_MANAGED_DIRS = [
1580
+ // GSD-managed directories to scan for user-added files. Whole-owned
1581
+ // roots are wiped recursively; shared runtime roots are pruned by the
1582
+ // same gsd-* top-level prefix used by install.js _removeGsdEntries.
1583
+ const GSD_WHOLE_MANAGED_DIRS = [
1571
1584
  'gsd-core',
1572
- 'agents',
1573
1585
  path.join('commands', 'gsd'),
1586
+ ];
1587
+ const GSD_PREFIX_MANAGED_DIRS = [
1588
+ 'agents',
1574
1589
  'hooks',
1575
1590
  'skills',
1576
1591
  ];
1577
1592
 
1578
1593
  function collectCustomFiles(dir, baseDir, manifestKeys, out) {
1579
1594
  if (!fs.existsSync(dir)) return;
1595
+ const stat = fs.statSync(dir);
1596
+ if (stat.isFile()) {
1597
+ const relPath = path.relative(baseDir, dir).replace(/\\/g, '/');
1598
+ if (!manifestKeys.has(relPath)) {
1599
+ out.push(relPath);
1600
+ }
1601
+ return;
1602
+ }
1603
+ if (!stat.isDirectory()) return;
1580
1604
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
1581
1605
  const fullPath = path.join(dir, entry.name);
1582
1606
  if (entry.isDirectory()) {
@@ -1592,11 +1616,19 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1592
1616
  }
1593
1617
 
1594
1618
  const customFiles = [];
1595
- for (const managedDir of GSD_MANAGED_DIRS) {
1619
+ for (const managedDir of GSD_WHOLE_MANAGED_DIRS) {
1596
1620
  const absDir = path.join(resolvedConfigDir, managedDir);
1597
1621
  if (!fs.existsSync(absDir)) continue;
1598
1622
  collectCustomFiles(absDir, resolvedConfigDir, manifestKeys, customFiles);
1599
1623
  }
1624
+ for (const managedDir of GSD_PREFIX_MANAGED_DIRS) {
1625
+ const absDir = path.join(resolvedConfigDir, managedDir);
1626
+ if (!fs.existsSync(absDir)) continue;
1627
+ for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) {
1628
+ if (!entry.name.startsWith('gsd-')) continue;
1629
+ collectCustomFiles(path.join(absDir, entry.name), resolvedConfigDir, manifestKeys, customFiles);
1630
+ }
1631
+ }
1600
1632
 
1601
1633
  const out = {
1602
1634
  custom_files: customFiles,
@@ -1810,7 +1842,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1810
1842
  // ─── Research Store ────────────────────────────────────────────────────
1811
1843
  //
1812
1844
  // research-store get <key> [--kind <k>]
1813
- // -> getResearch(cwd, key, { homeDir }); searches both tiers; core.output(result, raw)
1845
+ // -> getResearch(cwd, key, { homeDir }); searches both tiers; output(result, raw)
1814
1846
  // (--kind is accepted for backward compatibility but no longer drives tier selection)
1815
1847
  // research-store put <key> --content <str> --source <s> --provider <p>
1816
1848
  // --confidence <c> --kind <k>
@@ -1834,7 +1866,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1834
1866
  }
1835
1867
  // --kind is accepted but no longer drives tier selection; getResearch searches both tiers
1836
1868
  const result = researchStore.getResearch(cwd, key, { homeDir });
1837
- core.output(result, raw);
1869
+ output(result, raw);
1838
1870
  } else if (subcommand === 'put') {
1839
1871
  const key = args[2];
1840
1872
  if (!key || key.startsWith('--')) {
@@ -1866,7 +1898,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1866
1898
  error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
1867
1899
  }
1868
1900
  const entry = researchStore.putResearch(cwd, key, { content, source, provider, confidence, kind }, { homeDir });
1869
- core.output(entry, raw);
1901
+ output(entry, raw);
1870
1902
  } else {
1871
1903
  error('Unknown research-store subcommand. Available: get, put', ERROR_REASON.SDK_UNKNOWN_COMMAND);
1872
1904
  }
@@ -1902,14 +1934,14 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1902
1934
  const { ecosystem = '', config: planConfig = {}, questions } = planInput;
1903
1935
  const homeDir = process.env.HOME || require('os').homedir();
1904
1936
  const plan = researchProvider.planResearch({ questions, ecosystem, config: planConfig, cwd, homeDir });
1905
- core.output(plan, raw);
1937
+ output(plan, raw);
1906
1938
  break;
1907
1939
  }
1908
1940
 
1909
1941
  // ─── Classify Confidence ──────────────────────────────────────────────
1910
1942
  //
1911
1943
  // classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]
1912
- // -> classifyConfidence({ provider, verifiedAgainstOfficial, legitimacyVerdict }); core.output(result, raw)
1944
+ // -> classifyConfidence({ provider, verifiedAgainstOfficial, legitimacyVerdict }); output(result, raw)
1913
1945
  //
1914
1946
  // legitimacyVerdict is CODE-COMPUTED via checkPackages — never caller-supplied — so an agent cannot self-assert OK→HIGH.
1915
1947
 
@@ -1936,7 +1968,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1936
1968
  legitimacyVerdict = results[0] ? results[0].verdict : null;
1937
1969
  }
1938
1970
  const confidence = researchProvider.classifyConfidence({ provider, verifiedAgainstOfficial: verified, legitimacyVerdict });
1939
- core.output({ provider, package: pkg || null, ecosystem: ecosystem || null, legitimacyVerdict, verified, confidence }, raw);
1971
+ output({ provider, package: pkg || null, ecosystem: ecosystem || null, legitimacyVerdict, verified, confidence }, raw);
1940
1972
  break;
1941
1973
  }
1942
1974
 
@@ -1980,7 +2012,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1980
2012
  } catch (pkgErr) {
1981
2013
  error(`package-legitimacy: ${pkgErr && pkgErr.message ? pkgErr.message : String(pkgErr)}`, ERROR_REASON.UNKNOWN);
1982
2014
  }
1983
- core.output(pkgResults, raw);
2015
+ output(pkgResults, raw);
1984
2016
  break;
1985
2017
  }
1986
2018
 
@@ -2101,7 +2133,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2101
2133
  }
2102
2134
  }
2103
2135
 
2104
- core.output({ valid: errors.length === 0, errors, slots }, raw);
2136
+ output({ valid: errors.length === 0, errors, slots }, raw);
2105
2137
  break;
2106
2138
  }
2107
2139
 
@@ -2114,7 +2146,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2114
2146
 
2115
2147
  // Read config.json directly for both plan_review.source_grounding_authority
2116
2148
  // 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
2149
+ // config-loader.cjs's loadConfig() whitelist does not return; plan_review is only in config.cjs's private
2118
2150
  // buildConfig(), and intel is a federated capability config key.
2119
2151
  let configuredAuthority = 'grep';
2120
2152
  let intelEnabled = false;
@@ -2138,7 +2170,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2138
2170
 
2139
2171
  if (subcommand === 'authority') {
2140
2172
  // Pass rawValue as 3rd arg so --raw returns unquoted string (not JSON)
2141
- core.output(effectiveAuthority, raw, effectiveAuthority);
2173
+ output(effectiveAuthority, raw, effectiveAuthority);
2142
2174
  break;
2143
2175
  }
2144
2176
 
@@ -2155,7 +2187,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2155
2187
  ? authVal
2156
2188
  : effectiveAuthority;
2157
2189
  const result = classifyDriftSeverity({ status: statusVal, authority: authorityForClassify });
2158
- core.output(result, raw);
2190
+ output(result, raw);
2159
2191
  break;
2160
2192
  }
2161
2193
 
@@ -7,8 +7,8 @@
7
7
  * from the prior hand-written .cjs; only types are added.
8
8
  */
9
9
  // eslint-disable-next-line @typescript-eslint/no-require-imports
10
- const core = require("./core.cjs");
11
- const { output, error, ERROR_REASON } = core;
10
+ const io = require("./io.cjs");
11
+ const { output, error, ERROR_REASON } = io;
12
12
  // ─── Constants ────────────────────────────────────────────────────────────────
13
13
  const QUOTA_SENTINELS = [
14
14
  '429',
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Install Check — moved from core.cts (ADR-857 T0 #1268 phase rehome-core-squatters).
4
+ *
5
+ * Owns:
6
+ * - getAgentsDir(runtime?): string
7
+ * - checkAgentsInstalled(runtime?): AgentsInstalledResult
8
+ *
9
+ * The core.cjs re-export spine was retired in epic #1267; callers import
10
+ * these symbols from agent-install-check.cjs directly.
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ const node_fs_1 = __importDefault(require("node:fs"));
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
18
+ const modelProfiles = require("./model-profiles.cjs");
19
+ const { MODEL_PROFILES } = modelProfiles;
20
+ const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
21
+ /**
22
+ * Resolve the agents directory for the given runtime.
23
+ *
24
+ * Priority:
25
+ * 1. GSD_AGENTS_DIR env var (explicit override, any runtime)
26
+ * 2. For claude runtime: __dirname-relative path (agents/ sibling of gsd-core/)
27
+ * This is correct for both repo runs and real installs (the runtime config dir's
28
+ * agents/ folder) because gsd-tools.cjs lives inside gsd-core/bin/ in both cases.
29
+ * 3. For non-claude runtimes: getGlobalConfigDir(runtime)/agents
30
+ *
31
+ * @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
32
+ */
33
+ function getAgentsDir(runtime) {
34
+ if (process.env['GSD_AGENTS_DIR']) {
35
+ return process.env['GSD_AGENTS_DIR'];
36
+ }
37
+ const resolved = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
38
+ if (resolved === 'claude') {
39
+ return node_path_1.default.join(__dirname, '..', '..', '..', 'agents');
40
+ }
41
+ return node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)(resolved), 'agents');
42
+ }
43
+ /**
44
+ * Check which GSD agents are installed on disk.
45
+ *
46
+ * @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
47
+ */
48
+ function checkAgentsInstalled(runtime) {
49
+ const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
50
+ const agentsDir = getAgentsDir(resolvedRuntime);
51
+ const expectedAgents = Object.keys(MODEL_PROFILES);
52
+ const installed = [];
53
+ const missing = [];
54
+ if (!node_fs_1.default.existsSync(agentsDir)) {
55
+ return {
56
+ agents_installed: false,
57
+ missing_agents: expectedAgents,
58
+ installed_agents: [],
59
+ incomplete_agents: [],
60
+ agents_dir: agentsDir,
61
+ agent_runtime: resolvedRuntime,
62
+ };
63
+ }
64
+ for (const agent of expectedAgents) {
65
+ const agentFile = node_path_1.default.join(agentsDir, `${agent}.md`);
66
+ const agentFileCopilot = node_path_1.default.join(agentsDir, `${agent}.agent.md`);
67
+ const agentFileCodex = node_path_1.default.join(agentsDir, `${agent}.toml`);
68
+ const agentFileKimiYaml = node_path_1.default.join(agentsDir, 'subagents', `${agent}.yaml`);
69
+ const agentFileKimiPrompt = node_path_1.default.join(agentsDir, 'subagents', `${agent}.md`);
70
+ const kimiAgentInstalled = resolvedRuntime === 'kimi' &&
71
+ node_fs_1.default.existsSync(agentFileKimiYaml) &&
72
+ node_fs_1.default.existsSync(agentFileKimiPrompt);
73
+ if (node_fs_1.default.existsSync(agentFile) ||
74
+ node_fs_1.default.existsSync(agentFileCopilot) ||
75
+ node_fs_1.default.existsSync(agentFileCodex) ||
76
+ kimiAgentInstalled) {
77
+ installed.push(agent);
78
+ }
79
+ else {
80
+ missing.push(agent);
81
+ }
82
+ }
83
+ // ── Manifest-backed completeness check ──────────────────────────────────────
84
+ // If a gsd-file-manifest.json exists alongside the agents dir (parent dir),
85
+ // verify that every manifest-tracked file for each expected agent is present
86
+ // on disk. Missing manifest-tracked files indicate an incomplete install even
87
+ // when the plain presence check above passed (e.g. .md present, .toml absent).
88
+ // If no manifest is found the check is a no-op (graceful for claude/bundled).
89
+ const incomplete = [];
90
+ const manifestPath = node_path_1.default.join(node_path_1.default.dirname(agentsDir), 'gsd-file-manifest.json');
91
+ let manifestFiles = {};
92
+ try {
93
+ const raw = node_fs_1.default.readFileSync(manifestPath, 'utf8');
94
+ const parsed = JSON.parse(raw);
95
+ if (parsed !== null &&
96
+ typeof parsed === 'object' &&
97
+ 'files' in parsed &&
98
+ typeof parsed['files'] === 'object' &&
99
+ parsed['files'] !== null) {
100
+ manifestFiles = parsed['files'];
101
+ }
102
+ }
103
+ catch {
104
+ // No manifest or unreadable — completeness check is skipped
105
+ }
106
+ if (Object.keys(manifestFiles).length > 0) {
107
+ for (const agent of expectedAgents) {
108
+ // Find all manifest keys that belong to this agent:
109
+ // key must be "agents/<agentName>.<ext>" with no further path segments.
110
+ const agentPrefix = `agents/${agent}.`;
111
+ const agentManifestKeys = Object.keys(manifestFiles).filter(key => {
112
+ if (!key.startsWith(agentPrefix))
113
+ return false;
114
+ const rest = key.slice(agentPrefix.length);
115
+ // rest must be a bare extension (no slashes, non-empty)
116
+ return rest.length > 0 && !rest.includes('/');
117
+ });
118
+ if (agentManifestKeys.length === 0) {
119
+ // Agent not tracked in manifest — skip completeness check for this agent
120
+ continue;
121
+ }
122
+ const allPresent = agentManifestKeys.every(key => {
123
+ const basename = key.slice('agents/'.length);
124
+ return node_fs_1.default.existsSync(node_path_1.default.join(agentsDir, basename));
125
+ });
126
+ if (!allPresent) {
127
+ incomplete.push(agent);
128
+ }
129
+ }
130
+ }
131
+ return {
132
+ agents_installed: installed.length > 0 && missing.length === 0 && incomplete.length === 0,
133
+ missing_agents: missing,
134
+ installed_agents: installed,
135
+ incomplete_agents: incomplete,
136
+ agents_dir: agentsDir,
137
+ agent_runtime: resolvedRuntime,
138
+ };
139
+ }
140
+ module.exports = {
141
+ getAgentsDir,
142
+ checkAgentsInstalled,
143
+ };
@@ -24,7 +24,7 @@
24
24
  * inline case arms which each required only their own module).
25
25
  */
26
26
  // eslint-disable-next-line @typescript-eslint/no-require-imports
27
- const core = require("./core.cjs");
27
+ const io = require("./io.cjs");
28
28
  // ─── routeAuditUat ────────────────────────────────────────────────────────────
29
29
  function routeAuditUat({ args, cwd, raw, error, _uat }) {
30
30
  // Suppress unused-variable warnings for args/error — this command has no
@@ -42,16 +42,16 @@ function routeAuditOpen({ args, cwd, raw, error, _audit, _core }) {
42
42
  void error;
43
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
44
44
  const a = _audit ?? require('./audit.cjs');
45
- const c = _core ?? core;
45
+ const c = _core ?? io;
46
46
  const wantJson = args.includes('--json');
47
47
  const result = a.auditOpenArtifacts(cwd);
48
48
  if (wantJson) {
49
- // core.output JSON-stringifies its first arg; pass the object directly.
49
+ // io.output JSON-stringifies its first arg; pass the object directly.
50
50
  c.output(result, raw);
51
51
  }
52
52
  else {
53
53
  // Human-readable report must bypass JSON encoding — use the rawValue
54
- // form (third arg) which core.output emits verbatim.
54
+ // form (third arg) which io.output emits verbatim.
55
55
  c.output(null, true, a.formatAuditReport(result));
56
56
  }
57
57
  }