@opengsd/gsd-core 1.3.1 → 1.4.0-rc.2

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 (136) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/GEMINI.md +53 -0
  3. package/agents/gsd-advisor-researcher.md +1 -20
  4. package/agents/gsd-ai-researcher.md +2 -21
  5. package/agents/gsd-code-fixer.md +1 -1
  6. package/agents/gsd-code-reviewer.md +1 -1
  7. package/agents/gsd-domain-researcher.md +2 -21
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-eval-planner.md +1 -1
  10. package/agents/gsd-executor.md +1 -1
  11. package/agents/gsd-framework-selector.md +1 -1
  12. package/agents/gsd-nyquist-auditor.md +1 -1
  13. package/agents/gsd-pattern-mapper.md +1 -1
  14. package/agents/gsd-phase-researcher.md +92 -166
  15. package/agents/gsd-planner.md +9 -36
  16. package/agents/gsd-project-researcher.md +62 -141
  17. package/agents/gsd-security-auditor.md +1 -1
  18. package/agents/gsd-ui-auditor.md +1 -1
  19. package/agents/gsd-ui-checker.md +1 -1
  20. package/agents/gsd-ui-researcher.md +3 -22
  21. package/agents/gsd-user-profiler.md +1 -1
  22. package/agents/gsd-verifier.md +8 -2
  23. package/bin/install.js +1977 -339
  24. package/commands/gsd/autonomous.md +2 -0
  25. package/commands/gsd/execute-phase.md +2 -0
  26. package/commands/gsd/graphify.md +11 -6
  27. package/commands/gsd/import.md +6 -2
  28. package/commands/gsd/plan-phase.md +4 -2
  29. package/commands/gsd/progress.md +1 -0
  30. package/commands/gsd/stats.md +1 -0
  31. package/commands/gsd/update.md +3 -2
  32. package/gemini-extension.json +6 -0
  33. package/gsd-core/bin/check-latest-version.cjs +61 -6
  34. package/gsd-core/bin/gsd-tools.cjs +238 -32
  35. package/gsd-core/bin/lib/check-command-router.cjs +1 -0
  36. package/gsd-core/bin/lib/cli-exit.cjs +42 -0
  37. package/gsd-core/bin/lib/command-routing-hub.cjs +1 -1
  38. package/gsd-core/bin/lib/commands.cjs +5 -4
  39. package/gsd-core/bin/lib/config.cjs +28 -4
  40. package/gsd-core/bin/lib/core.cjs +72 -28
  41. package/gsd-core/bin/lib/graphify.cjs +2 -2
  42. package/gsd-core/bin/lib/init-command-router.cjs +2 -2
  43. package/gsd-core/bin/lib/init.cjs +19 -3
  44. package/gsd-core/bin/lib/install-profiles.cjs +58 -0
  45. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  46. package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +1 -1
  47. package/gsd-core/bin/lib/intel.cjs +3 -20
  48. package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
  49. package/gsd-core/bin/lib/phase.cjs +3 -3
  50. package/gsd-core/bin/lib/research-provider.cjs +137 -0
  51. package/gsd-core/bin/lib/research-store.cjs +167 -0
  52. package/gsd-core/bin/lib/roadmap-upgrade.cjs +4 -19
  53. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -9
  54. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
  55. package/gsd-core/bin/lib/runtime-homes.cjs +32 -11
  56. package/gsd-core/bin/lib/security.cjs +73 -0
  57. package/gsd-core/bin/lib/shell-command-projection.cjs +9 -0
  58. package/gsd-core/bin/lib/surface.cjs +54 -11
  59. package/gsd-core/bin/lib/validate.cjs +2 -2
  60. package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
  61. package/gsd-core/bin/lib/verification.cjs +193 -0
  62. package/gsd-core/bin/lib/verify.cjs +2 -2
  63. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -1
  64. package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
  65. package/gsd-core/bin/lib/worktree-safety.cjs +31 -0
  66. package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
  67. package/gsd-core/bin/verify-reapply-patches.cjs +8 -11
  68. package/gsd-core/references/planner-load-graph-context.md +36 -0
  69. package/gsd-core/references/planning-config.md +3 -1
  70. package/gsd-core/references/research-documentation-lookup.md +29 -0
  71. package/gsd-core/references/research-philosophy.md +29 -0
  72. package/gsd-core/references/research-verification-protocol.md +27 -0
  73. package/gsd-core/workflows/execute-phase.md +19 -8
  74. package/gsd-core/workflows/help/modes/full.md +4 -3
  75. package/gsd-core/workflows/ingest-docs.md +3 -2
  76. package/gsd-core/workflows/plan-phase.md +14 -10
  77. package/gsd-core/workflows/plan-review-convergence.md +3 -3
  78. package/gsd-core/workflows/review.md +24 -7
  79. package/gsd-core/workflows/ship.md +5 -8
  80. package/gsd-core/workflows/spec-phase.md +2 -1
  81. package/gsd-core/workflows/update.md +34 -6
  82. package/hooks/dist/gsd-config-reload.js +133 -0
  83. package/hooks/dist/gsd-context-monitor.js +1 -1
  84. package/hooks/dist/gsd-cursor-post-tool.js +75 -0
  85. package/hooks/dist/gsd-cursor-session-start.js +52 -0
  86. package/hooks/dist/gsd-workflow-guard.js +1 -0
  87. package/hooks/dist/gsd-worktree-path-guard.js +1 -1
  88. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  89. package/hooks/gsd-config-reload.js +133 -0
  90. package/hooks/gsd-context-monitor.js +1 -1
  91. package/hooks/gsd-cursor-post-tool.js +75 -0
  92. package/hooks/gsd-cursor-session-start.js +52 -0
  93. package/hooks/gsd-workflow-guard.js +1 -0
  94. package/hooks/gsd-worktree-path-guard.js +1 -1
  95. package/hooks/hooks.json +69 -0
  96. package/hooks/managed-hooks-registry.cjs +3 -0
  97. package/package.json +8 -1
  98. package/scripts/affected-tests-lib.cjs +3 -2
  99. package/scripts/build-hooks.js +7 -0
  100. package/scripts/changeset/cli.cjs +226 -28
  101. package/scripts/changeset/lint.cjs +5 -4
  102. package/scripts/changeset/new.cjs +4 -4
  103. package/scripts/check-alias-drift.cjs +77 -71
  104. package/scripts/check-env.cjs +185 -179
  105. package/scripts/check-npm-integrity.cjs +115 -109
  106. package/scripts/ci-guard-runner.cjs +11 -5
  107. package/scripts/ci-prepare-test-scope.cjs +27 -22
  108. package/scripts/ci-rebase-check.cjs +46 -45
  109. package/scripts/ci-test-scope.cjs +126 -22
  110. package/scripts/diff-touches-shipped-paths.cjs +52 -44
  111. package/scripts/gen-inventory-manifest.cjs +38 -32
  112. package/scripts/gen-research-agents.cjs +276 -0
  113. package/scripts/issue-dedupe.cjs +278 -0
  114. package/scripts/lib/cli-exit.cjs +56 -0
  115. package/scripts/lint-command-contract.cjs +28 -22
  116. package/scripts/lint-descriptions.cjs +32 -28
  117. package/scripts/lint-docs-required.cjs +4 -4
  118. package/scripts/lint-legacy-dir-name.cjs +56 -52
  119. package/scripts/lint-pr-check-project-dir.cjs +3 -1
  120. package/scripts/lint-shell-command-projection-drift.cjs +27 -22
  121. package/scripts/lint-skill-deps.cjs +31 -26
  122. package/scripts/lint-test-file-count.allowlist.json +2 -0
  123. package/scripts/lint-test-file-count.cjs +5 -4
  124. package/scripts/mutation-matrix.cjs +6 -3
  125. package/scripts/prompt-injection-scan.sh +1 -1
  126. package/scripts/release-notes/discord-release-summary.cjs +373 -0
  127. package/scripts/release-notes/format-github-release-notes.cjs +8 -3
  128. package/scripts/release-tarball-smoke.cjs +6 -4
  129. package/scripts/research-profiles.cjs +149 -0
  130. package/scripts/run-affected-tests.cjs +2 -1
  131. package/scripts/run-cross-platform-tests.cjs +11 -7
  132. package/scripts/run-tests.cjs +8 -7
  133. package/scripts/strip-prose-atrefs.cjs +1 -1
  134. package/scripts/sync-manifest-versions.cjs +119 -0
  135. package/scripts/sync-runtime-launcher.cjs +0 -3
  136. package/scripts/verify-npm-publish.cjs +14 -26
@@ -506,6 +506,7 @@ function loadConfig(cwd, options = {}) {
506
506
  effort: (parsed['effort']) || null,
507
507
  fast_mode: (parsed['fast_mode']) || null,
508
508
  agent_skills: (parsed['agent_skills']) || {},
509
+ agent_skills_security: (parsed['agent_skills_security']) || null,
509
510
  manager: (parsed['manager']) || {},
510
511
  response_language: get('response_language') || null,
511
512
  claude_md_path: get('claude_md_path') || null,
@@ -994,35 +995,38 @@ function extractCurrentMilestone(content, cwd) {
994
995
  const firstMatch = allMatches[0];
995
996
  const selected = allMatches.find((m) => !isClosed(m[1])) || firstMatch;
996
997
  const sectionStart = selected.index;
997
- const sectionMatch = selected;
998
- const headingLevel = (sectionMatch[1].match(/^(#{1,3})\s/) ?? ['', '#'])[1].length;
999
- const restContent = content.slice(sectionStart + sectionMatch[0].length);
1000
- const nextMilestonePattern = new RegExp(`^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`, 'i');
1001
- let sectionEnd = content.length;
1002
- let fenceChar = null;
1003
- let fenceLen = 0;
1004
- let charOffset = 0;
1005
- for (const line of restContent.split('\n')) {
1006
- const fenceMatch = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
1007
- if (fenceMatch) {
1008
- const char = fenceMatch[1][0];
1009
- const len = fenceMatch[1].length;
1010
- const trailing = fenceMatch[2] || '';
1011
- if (!fenceChar) {
1012
- fenceChar = char;
1013
- fenceLen = len;
998
+ const computeSectionEnd = (headingText, headingStart) => {
999
+ const level = (headingText.match(/^(#{1,3})\s/) ?? ['', '#'])[1].length;
1000
+ const rest = content.slice(headingStart + headingText.length);
1001
+ const stopPattern = new RegExp(`^#{1,${level}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`, 'i');
1002
+ let end = content.length;
1003
+ let fc = null;
1004
+ let fl = 0;
1005
+ let off = 0;
1006
+ for (const line of rest.split('\n')) {
1007
+ const fm = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
1008
+ if (fm) {
1009
+ const ch = fm[1][0];
1010
+ const ln = fm[1].length;
1011
+ const trailing = fm[2] || '';
1012
+ if (!fc) {
1013
+ fc = ch;
1014
+ fl = ln;
1015
+ }
1016
+ else if (ch === fc && ln >= fl && /^\s*$/.test(trailing)) {
1017
+ fc = null;
1018
+ fl = 0;
1019
+ }
1014
1020
  }
1015
- else if (char === fenceChar && len >= fenceLen && /^\s*$/.test(trailing)) {
1016
- fenceChar = null;
1017
- fenceLen = 0;
1021
+ else if (!fc && stopPattern.test(line)) {
1022
+ end = headingStart + headingText.length + off;
1023
+ break;
1018
1024
  }
1025
+ off += line.length + 1;
1019
1026
  }
1020
- else if (!fenceChar && nextMilestonePattern.test(line)) {
1021
- sectionEnd = sectionStart + sectionMatch[0].length + charOffset;
1022
- break;
1023
- }
1024
- charOffset += line.length + 1;
1025
- }
1027
+ return end;
1028
+ };
1029
+ const sectionEnd = computeSectionEnd(selected[0], sectionStart);
1026
1030
  const anyMilestonePattern = /^#{1,3}\s+(?!Phase\s+\S)(?:.*v\d+\.\d+|✅|📋|🚧)/im;
1027
1031
  const firstMilestoneMatch = content.match(anyMilestonePattern);
1028
1032
  const preambleCutoff = firstMilestoneMatch
@@ -1030,11 +1034,36 @@ function extractCurrentMilestone(content, cwd) {
1030
1034
  : firstMatch.index;
1031
1035
  const beforeMilestones = content.slice(0, preambleCutoff);
1032
1036
  const currentSection = content.slice(sectionStart, sectionEnd);
1037
+ // Multi-milestone roadmaps split each added milestone across two version-bearing
1038
+ // headings: a `## Phases` checklist subsection (early) and a dedicated
1039
+ // `## Milestone … (Phase Details)` section (late) holding the `### Phase N:`
1040
+ // detail headers. The scope window above stops at the next version-bearing
1041
+ // heading — the current milestone's OWN Phase Details heading — leaving those
1042
+ // detail headers outside `currentSection`. Append that section so phase
1043
+ // resolution and counting see the current milestone's phases. Anchor the lookup
1044
+ // to the SELECTED heading's specific version token (boundary-aware, so a
1045
+ // `v3.0` state does not match a `v3.0-A` sub-milestone) so sibling milestones
1046
+ // that share a version prefix do not cross-pollinate. (#730)
1047
+ const selectedVersionToken = selected[1].match(/v\d+(?:\.\d+)+(?:[-.][A-Za-z0-9]+)*/i)?.[0];
1048
+ const detailsVersionBoundary = selectedVersionToken
1049
+ ? new RegExp(`${escapeRegex(selectedVersionToken)}(?![\\w.-])`, 'i')
1050
+ : null;
1051
+ let detailsSection = '';
1052
+ const detailsMatch = allMatches.find((m) => /\(Phase\s+Details\)/i.test(m[1]) &&
1053
+ !isClosed(m[1]) &&
1054
+ (!detailsVersionBoundary || detailsVersionBoundary.test(m[1])) &&
1055
+ (m.index ?? 0) >= sectionEnd);
1056
+ if (detailsMatch) {
1057
+ const detailsStart = detailsMatch.index ?? 0;
1058
+ detailsSection = content.slice(detailsStart, computeSectionEnd(detailsMatch[0], detailsStart));
1059
+ }
1033
1060
  const preamble = beforeMilestones
1034
1061
  .replace(/<details>[\s\S]*?<\/details>/gi, '')
1035
1062
  .replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
1036
1063
  .replace(/^#{1,4}\s*Phase Details\b[^\n]*\n?/gim, '');
1037
- return preamble + currentSection;
1064
+ return detailsSection
1065
+ ? preamble + currentSection + '\n' + detailsSection
1066
+ : preamble + currentSection;
1038
1067
  }
1039
1068
  /**
1040
1069
  * Replace a pattern only in the current milestone section of ROADMAP.md.
@@ -1399,7 +1428,12 @@ const VALID_GRANULARITIES = new Set(['coarse', 'standard', 'fine']);
1399
1428
  /**
1400
1429
  * Resolve the planning granularity for a phase type (#68).
1401
1430
  */
1402
- function resolveGranularityInternal(cwd, phaseType) {
1431
+ function resolveGranularityInternal(cwd, phaseType, override) {
1432
+ if (override !== undefined && override !== null && override !== '') {
1433
+ if (VALID_GRANULARITIES.has(override)) {
1434
+ return override;
1435
+ }
1436
+ }
1403
1437
  const config = loadConfig(cwd);
1404
1438
  const configGranularities = config['granularities'];
1405
1439
  const perPhase = (phaseType && configGranularities && typeof configGranularities === 'object')
@@ -1418,6 +1452,15 @@ function resolveGranularityInternal(cwd, phaseType) {
1418
1452
  }
1419
1453
  return 'standard';
1420
1454
  }
1455
+ /**
1456
+ * Validate a CLI granularity override at the command boundary. Empty/null/undefined
1457
+ * are treated as "no override" (no-op). An invalid non-empty value calls `fail`.
1458
+ */
1459
+ function assertValidGranularityOverride(override, fail) {
1460
+ if (override !== undefined && override !== null && override !== '' && !VALID_GRANULARITIES.has(override)) {
1461
+ fail(`invalid granularity '${override}' (valid: ${[...VALID_GRANULARITIES].join(', ')})`);
1462
+ }
1463
+ }
1421
1464
  /**
1422
1465
  * #3024 — Resolve a model for a specific dynamic-routing attempt.
1423
1466
  */
@@ -1956,6 +1999,7 @@ module.exports = {
1956
1999
  resolveModelForTier,
1957
2000
  resolveGranularityInternal,
1958
2001
  VALID_GRANULARITIES,
2002
+ assertValidGranularityOverride,
1959
2003
  resolveEffortInternal,
1960
2004
  resolveFastModeInternal,
1961
2005
  resolveEffortForTier,
@@ -35,7 +35,7 @@ function isGraphifyEnabled(planningDir) {
35
35
  return true;
36
36
  return false;
37
37
  }
38
- catch (_e) {
38
+ catch {
39
39
  return false;
40
40
  }
41
41
  }
@@ -157,7 +157,7 @@ function safeReadJson(filePath) {
157
157
  return null;
158
158
  return JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf8'));
159
159
  }
160
- catch (_e) {
160
+ catch {
161
161
  return null;
162
162
  }
163
163
  }
@@ -34,8 +34,8 @@ function routeInitCommand({ init, args, cwd, raw, error }) {
34
34
  init.cmdInitExecutePhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'] });
35
35
  },
36
36
  'plan-phase': () => {
37
- const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['validate', 'tdd']);
38
- init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'] });
37
+ const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity'], ['validate', 'tdd']);
38
+ init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], granularity: namedArgs['granularity'] });
39
39
  },
40
40
  'new-project': () => init.cmdInitNewProject(cwd, raw),
41
41
  'new-milestone': () => init.cmdInitNewMilestone(cwd, raw),
@@ -28,7 +28,7 @@ const security_cjs_1 = require("./security.cjs");
28
28
  const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
29
29
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
30
30
  const frontmatterMod = require("./frontmatter.cjs");
31
- const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, gitWorktreeInfoInternal, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, normalizePhaseName, toPosixPath, output, error, checkAgentsInstalled, phaseTokenMatches, } = core;
31
+ const { loadConfig, resolveModelInternal, resolveGranularityInternal, assertValidGranularityOverride, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, gitWorktreeInfoInternal, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, normalizePhaseName, toPosixPath, output, error, checkAgentsInstalled, phaseTokenMatches, } = core;
32
32
  const { planningPaths, planningDir, planningRoot, findContextMdIn, } = planningWorkspace;
33
33
  const { determinePhaseStatus } = commandsMod;
34
34
  const { extractFrontmatter } = frontmatterMod;
@@ -297,11 +297,15 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
297
297
  expectedPhaseDirPlan = toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningPaths(cwd).phases, dirName)));
298
298
  }
299
299
  }
300
+ const granularityOverride = options['granularity'];
301
+ assertValidGranularityOverride(granularityOverride, error);
302
+ const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
300
303
  const result = {
301
304
  researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
302
305
  planner_model: resolveModelInternal(cwd, 'gsd-planner'),
303
306
  checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
304
307
  tdd_mode: options['tdd'] || config.tdd_mode || false,
308
+ granularity,
305
309
  research_enabled: config.research,
306
310
  plan_checker_enabled: config.plan_checker,
307
311
  nyquist_validation_enabled: config.nyquist_validation,
@@ -1521,6 +1525,11 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
1521
1525
  skillPaths = [skillPaths];
1522
1526
  if (!Array.isArray(skillPaths) || skillPaths.length === 0)
1523
1527
  return '';
1528
+ // Hoist trusted roots computation before the loop: loadTrustedGlobalRoots does
1529
+ // realpathSync I/O and should run at most once per call, not once per failing skill.
1530
+ // It returns [] cheaply when no roots are configured, so the realpath cost only
1531
+ // occurs when the caller has actually set trusted_global_roots.
1532
+ const trustedGlobalRoots = (0, security_cjs_1.loadTrustedGlobalRoots)(config);
1524
1533
  const validPaths = [];
1525
1534
  for (const skillPath of skillPaths) {
1526
1535
  if (typeof skillPath !== 'string')
@@ -1548,8 +1557,15 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
1548
1557
  }
1549
1558
  const pathCheck = (0, security_cjs_1.validatePath)(globalSkillMd, globalSkillsBase, { allowAbsolute: true });
1550
1559
  if (!pathCheck['safe']) {
1551
- process.stderr.write(`[agent-skills] WARNING: Global skill "${skillName}" failed path check (symlink escape?) — skipping\n`);
1552
- continue;
1560
+ const acceptedViaTrustedRoot = trustedGlobalRoots.some((root) => {
1561
+ const rootCheck = (0, security_cjs_1.validatePath)(globalSkillMd, root, { allowAbsolute: true });
1562
+ return Boolean(rootCheck['safe']);
1563
+ });
1564
+ if (!acceptedViaTrustedRoot) {
1565
+ process.stderr.write(`[agent-skills] WARNING: Global skill "${skillName}" failed path check (symlink escape?) — skipping\n`);
1566
+ continue;
1567
+ }
1568
+ process.stderr.write(`[agent-skills] NOTE: Global skill "${skillName}" accepted via trusted_global_roots (resolves outside the default skills dir)\n`);
1553
1569
  }
1554
1570
  validPaths.push({ ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
1555
1571
  continue;
@@ -351,6 +351,63 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
351
351
  ensureExitCleanup();
352
352
  return stageDir;
353
353
  }
354
+ /**
355
+ * Stage converted command files as flat `.md` files.
356
+ *
357
+ * Analogous to `stageSkillsForRuntimeAsSkills` but for runtimes that use a
358
+ * flat commands directory (e.g. Cursor's `.cursor/commands/<name>.md`).
359
+ * Each source `.md` is passed through `converter` and written as a single flat
360
+ * `${stem}.md` file in the staging directory (no subdirectory, no prefix).
361
+ *
362
+ * The `_copyStaged` commands branch in install.js will add the prefix when
363
+ * copying staged files to the destination directory, so staged files must be
364
+ * named with just the stem (e.g. `help.md` not `gsd-help.md`).
365
+ *
366
+ * The `converter` receives `(content, ${prefix}${stem})` so it can embed the
367
+ * full command name (e.g. 'gsd-help') into the document body if needed.
368
+ *
369
+ * Used by the `convertedCommandsKind` layout descriptor in
370
+ * runtime-artifact-layout.cts (#785 — Cursor 1.6 slash commands).
371
+ *
372
+ * @param srcCommandsDir source commands directory (e.g. commands/gsd/)
373
+ * @param resolvedProfile profile filter — '*' for all, Set for subset
374
+ * @param converter (content, commandName) → string pure converter
375
+ * @param prefix command name prefix (for converter arg), e.g. 'gsd-'
376
+ */
377
+ function stageCommandsForRuntimeFlat(srcCommandsDir, resolvedProfile, converter, prefix) {
378
+ if (!node_fs_1.default.existsSync(srcCommandsDir))
379
+ return srcCommandsDir;
380
+ const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-commands-'));
381
+ try {
382
+ const entries = node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true });
383
+ for (const entry of entries) {
384
+ if (!entry.isFile())
385
+ continue;
386
+ if (!entry.name.endsWith('.md'))
387
+ continue;
388
+ const stem = entry.name.slice(0, -3);
389
+ if (resolvedProfile.skills !== '*' && !(resolvedProfile.skills).has(stem))
390
+ continue;
391
+ const content = node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
392
+ // Pass the full command name (with prefix) to the converter so it can
393
+ // reference the installed command name in the body (e.g. for descriptions).
394
+ // The staged file itself is named without the prefix; _copyStaged adds it.
395
+ const commandName = `${prefix}${stem}`;
396
+ const converted = converter(content, commandName);
397
+ node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, `${stem}.md`), converted);
398
+ }
399
+ }
400
+ catch (err) {
401
+ try {
402
+ node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
403
+ }
404
+ catch { /* best-effort */ }
405
+ throw err;
406
+ }
407
+ STAGED_DIRS.add(stageDir);
408
+ ensureExitCleanup();
409
+ return stageDir;
410
+ }
354
411
  // ---------------------------------------------------------------------------
355
412
  // Profile marker persistence
356
413
  // ---------------------------------------------------------------------------
@@ -513,6 +570,7 @@ module.exports = {
513
570
  stageSkillsForProfile,
514
571
  stageAgentsForProfile,
515
572
  stageSkillsForRuntimeAsSkills,
573
+ stageCommandsForRuntimeFlat,
516
574
  STAGED_DIRS,
517
575
  readActiveProfile,
518
576
  writeActiveProfile,
@@ -34,7 +34,10 @@ const VALID_CHOICES = ['keep', 'remove'];
34
34
  exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
35
35
  'hooks/gsd-check-update-worker.js',
36
36
  'hooks/gsd-check-update.js',
37
+ 'hooks/gsd-config-reload.js',
37
38
  'hooks/gsd-context-monitor.js',
39
+ 'hooks/gsd-cursor-post-tool.js',
40
+ 'hooks/gsd-cursor-session-start.js',
38
41
  'hooks/gsd-graphify-update.sh',
39
42
  'hooks/gsd-phase-boundary.sh',
40
43
  'hooks/gsd-prompt-guard.js',
@@ -24,7 +24,7 @@ const RUNTIME_SURFACES = {
24
24
  kilo: ['gsd-core', 'command', 'skills', 'agents'],
25
25
  copilot: ['gsd-core', 'skills', 'agents'],
26
26
  antigravity: ['gsd-core', 'skills', 'agents'],
27
- cursor: ['gsd-core', 'skills', 'agents'],
27
+ cursor: ['gsd-core', 'skills', 'agents', 'hooks', 'hooks.json'],
28
28
  windsurf: ['gsd-core', 'skills', 'agents', 'rules'],
29
29
  augment: ['gsd-core', 'skills', 'agents'],
30
30
  trae: ['gsd-core', 'skills', 'agents', 'rules'],
@@ -59,7 +59,7 @@ function isIntelEnabled(planningDir) {
59
59
  return true;
60
60
  return false;
61
61
  }
62
- catch (_e) {
62
+ catch {
63
63
  return false;
64
64
  }
65
65
  }
@@ -86,7 +86,7 @@ function safeReadJson(filePath) {
86
86
  return null;
87
87
  return JSON.parse(raw);
88
88
  }
89
- catch (_e) {
89
+ catch {
90
90
  return null;
91
91
  }
92
92
  }
@@ -101,7 +101,7 @@ function hashFile(filePath) {
101
101
  return null;
102
102
  return node_crypto_1.default.createHash('sha256').update(content).digest('hex');
103
103
  }
104
- catch (_e) {
104
+ catch {
105
105
  return null;
106
106
  }
107
107
  }
@@ -147,23 +147,6 @@ function matchesInValue(value, lowerTerm) {
147
147
  }
148
148
  return false;
149
149
  }
150
- /**
151
- * Search for a term in arch.md text content.
152
- * Returns matching lines.
153
- */
154
- function searchArchMd(filePath, term) {
155
- try {
156
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
157
- if (content === null)
158
- return [];
159
- const lowerTerm = term.toLowerCase();
160
- const lines = content.split(/\r?\n/);
161
- return lines.filter(line => line.toLowerCase().includes(lowerTerm));
162
- }
163
- catch (_e) {
164
- return [];
165
- }
166
- }
167
150
  /**
168
151
  * Query intel files for a search term.
169
152
  * Searches across all JSON intel files (keys and values) and arch.md (text lines).