@opengsd/gsd-core 1.16.0 → 1.18.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 (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -2
  3. package/.opencode/plugins/gsd-core.js +251 -1
  4. package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
  5. package/GEMINI.md +1 -1
  6. package/agents/gsd-advisor-researcher.compact.md +1 -1
  7. package/agents/gsd-advisor-researcher.md +1 -1
  8. package/agents/gsd-assumptions-analyzer.compact.md +1 -1
  9. package/agents/gsd-assumptions-analyzer.md +1 -1
  10. package/agents/gsd-code-fixer.compact.md +1 -1
  11. package/agents/gsd-code-fixer.md +1 -1
  12. package/agents/gsd-code-reviewer.compact.md +1 -1
  13. package/agents/gsd-code-reviewer.md +1 -1
  14. package/agents/gsd-codebase-mapper.compact.md +1 -1
  15. package/agents/gsd-codebase-mapper.md +1 -1
  16. package/agents/gsd-debug-session-manager.compact.md +6 -0
  17. package/agents/gsd-debug-session-manager.md +6 -0
  18. package/agents/gsd-debugger.md +1 -1
  19. package/agents/gsd-doc-verifier.compact.md +4 -4
  20. package/agents/gsd-doc-verifier.md +4 -3
  21. package/agents/gsd-doc-writer.compact.md +23 -5
  22. package/agents/gsd-doc-writer.md +19 -6
  23. package/agents/gsd-eval-auditor.compact.md +1 -1
  24. package/agents/gsd-eval-auditor.md +1 -1
  25. package/agents/gsd-executor.md +7 -3
  26. package/agents/gsd-integration-checker.compact.md +1 -1
  27. package/agents/gsd-integration-checker.md +1 -1
  28. package/agents/gsd-nyquist-auditor.compact.md +1 -1
  29. package/agents/gsd-nyquist-auditor.md +1 -1
  30. package/agents/gsd-phase-researcher.md +1 -1
  31. package/agents/gsd-plan-checker.md +2 -1
  32. package/agents/gsd-planner.md +5 -4
  33. package/agents/gsd-project-researcher.compact.md +1 -1
  34. package/agents/gsd-project-researcher.md +1 -1
  35. package/agents/gsd-research-synthesizer.compact.md +5 -5
  36. package/agents/gsd-research-synthesizer.md +5 -5
  37. package/agents/gsd-roadmapper.compact.md +1 -1
  38. package/agents/gsd-roadmapper.md +1 -1
  39. package/agents/gsd-security-auditor.compact.md +1 -1
  40. package/agents/gsd-security-auditor.md +1 -1
  41. package/agents/gsd-ui-auditor.compact.md +1 -1
  42. package/agents/gsd-ui-auditor.md +1 -1
  43. package/agents/gsd-ui-checker.compact.md +1 -1
  44. package/agents/gsd-ui-checker.md +1 -1
  45. package/agents/gsd-ui-researcher.compact.md +13 -1
  46. package/agents/gsd-ui-researcher.md +28 -1
  47. package/agents/gsd-verifier.md +1 -1
  48. package/bin/gsd-mcp-server.js +3 -1
  49. package/bin/install.js +398 -95
  50. package/commands/gsd/code-review.md +8 -0
  51. package/commands/gsd/pause-work.md +1 -1
  52. package/gsd-core/bin/gsd-tools.cjs +34 -12
  53. package/gsd-core/bin/lib/adr-parser.cjs +37 -5
  54. package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
  55. package/gsd-core/bin/lib/capability-lock.cjs +29 -7
  56. package/gsd-core/bin/lib/capability-registry.cjs +138 -80
  57. package/gsd-core/bin/lib/capability-source.cjs +3 -1
  58. package/gsd-core/bin/lib/capability-state.cjs +6 -3
  59. package/gsd-core/bin/lib/capability-validator.cjs +8 -5
  60. package/gsd-core/bin/lib/capability-writer.cjs +2 -1
  61. package/gsd-core/bin/lib/commands.cjs +33 -9
  62. package/gsd-core/bin/lib/config-loader.cjs +3 -3
  63. package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
  64. package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
  65. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
  66. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
  67. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
  68. package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
  69. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
  70. package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
  71. package/gsd-core/bin/lib/graphify.cjs +3 -2
  72. package/gsd-core/bin/lib/init.cjs +43 -16
  73. package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
  74. package/gsd-core/bin/lib/install-engine.cjs +11 -0
  75. package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
  76. package/gsd-core/bin/lib/install-profiles.cjs +5 -3
  77. package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
  78. package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
  79. package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
  80. package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
  81. package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
  82. package/gsd-core/bin/lib/mcp-server.cjs +5 -1
  83. package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
  84. package/gsd-core/bin/lib/opencode-version.cjs +486 -0
  85. package/gsd-core/bin/lib/phase.cjs +141 -11
  86. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
  87. package/gsd-core/bin/lib/report-parser.cjs +57 -1
  88. package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
  89. package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
  90. package/gsd-core/bin/lib/roadmap.cjs +56 -5
  91. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +142 -6
  92. package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
  93. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
  94. package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
  95. package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
  96. package/gsd-core/bin/lib/smart-entry.cjs +100 -53
  97. package/gsd-core/bin/lib/state-document.cjs +37 -3
  98. package/gsd-core/bin/lib/state-transition.cjs +98 -19
  99. package/gsd-core/bin/lib/state.cjs +544 -126
  100. package/gsd-core/bin/lib/task-command-router.cjs +2 -3
  101. package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
  102. package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
  103. package/gsd-core/bin/lib/update-context.cjs +18 -1
  104. package/gsd-core/bin/lib/verification.cjs +107 -17
  105. package/gsd-core/bin/lib/verify.cjs +36 -1
  106. package/gsd-core/references/agent-contracts.md +1 -1
  107. package/gsd-core/references/agent-skills-bootstrap.md +26 -7
  108. package/gsd-core/references/git-integration.md +3 -3
  109. package/gsd-core/references/planner-reversibility.md +18 -4
  110. package/gsd-core/references/planner-verify-command-grounding.md +18 -0
  111. package/gsd-core/references/tdd.md +4 -3
  112. package/gsd-core/references/verifier-phase-gates.md +8 -1
  113. package/gsd-core/workflows/add-backlog.md +10 -2
  114. package/gsd-core/workflows/add-phase.md +5 -1
  115. package/gsd-core/workflows/add-tests.md +5 -1
  116. package/gsd-core/workflows/add-todo.md +5 -1
  117. package/gsd-core/workflows/ai-integration-phase.md +5 -1
  118. package/gsd-core/workflows/audit-milestone.md +21 -9
  119. package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
  120. package/gsd-core/workflows/autonomous.md +52 -64
  121. package/gsd-core/workflows/check-todos.md +5 -1
  122. package/gsd-core/workflows/code-review-fix.md +5 -1
  123. package/gsd-core/workflows/code-review.md +21 -2
  124. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
  125. package/gsd-core/workflows/complete-milestone.md +25 -5
  126. package/gsd-core/workflows/debug.md +5 -1
  127. package/gsd-core/workflows/diagnose-issues.md +5 -1
  128. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
  129. package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
  130. package/gsd-core/workflows/discuss-phase.md +5 -1
  131. package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
  132. package/gsd-core/workflows/docs-update.md +27 -5
  133. package/gsd-core/workflows/edit-phase.md +5 -1
  134. package/gsd-core/workflows/eval-review.md +11 -3
  135. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
  136. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  137. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
  138. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
  139. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
  140. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
  141. package/gsd-core/workflows/execute-phase.md +40 -7
  142. package/gsd-core/workflows/execute-plan.md +8 -4
  143. package/gsd-core/workflows/extract-learnings.md +6 -2
  144. package/gsd-core/workflows/forensics.md +5 -1
  145. package/gsd-core/workflows/import.md +5 -1
  146. package/gsd-core/workflows/insert-phase.md +5 -1
  147. package/gsd-core/workflows/manager.md +10 -2
  148. package/gsd-core/workflows/map-codebase.md +5 -1
  149. package/gsd-core/workflows/milestone-summary.md +10 -2
  150. package/gsd-core/workflows/new-milestone.md +13 -5
  151. package/gsd-core/workflows/new-project.md +9 -5
  152. package/gsd-core/workflows/next.md +8 -4
  153. package/gsd-core/workflows/pause-work.md +3 -3
  154. package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
  155. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
  156. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
  157. package/gsd-core/workflows/plan-phase.md +41 -73
  158. package/gsd-core/workflows/plan-review-convergence.md +17 -5
  159. package/gsd-core/workflows/plant-seed.md +8 -4
  160. package/gsd-core/workflows/progress.md +6 -2
  161. package/gsd-core/workflows/quick-batch.md +5 -1
  162. package/gsd-core/workflows/quick.md +22 -7
  163. package/gsd-core/workflows/remove-phase.md +5 -1
  164. package/gsd-core/workflows/resume-project.md +19 -11
  165. package/gsd-core/workflows/review.md +6 -2
  166. package/gsd-core/workflows/scan.md +5 -1
  167. package/gsd-core/workflows/secure-phase.md +6 -2
  168. package/gsd-core/workflows/settings.md +3 -3
  169. package/gsd-core/workflows/ship.md +13 -10
  170. package/gsd-core/workflows/thread.md +10 -2
  171. package/gsd-core/workflows/transition.md +5 -1
  172. package/gsd-core/workflows/ui-phase.md +5 -1
  173. package/gsd-core/workflows/ui-review.md +6 -2
  174. package/gsd-core/workflows/ultraplan-phase.md +5 -1
  175. package/gsd-core/workflows/update.md +5 -1
  176. package/gsd-core/workflows/validate-phase.md +6 -2
  177. package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
  178. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
  179. package/gsd-core/workflows/verify-work.md +11 -3
  180. package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
  181. package/hooks/dist/gsd-check-update.js +26 -20
  182. package/hooks/dist/gsd-context-monitor.js +14 -5
  183. package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
  184. package/hooks/dist/gsd-secret-read-guard.js +189 -15
  185. package/hooks/dist/gsd-statusline.js +321 -22
  186. package/hooks/dist/gsd-validate-commit.sh +27 -5
  187. package/hooks/gsd-agent-isolation-guard.js +3 -1
  188. package/hooks/gsd-check-update.js +26 -20
  189. package/hooks/gsd-context-monitor.js +14 -5
  190. package/hooks/gsd-cursor-subagent-start.js +2 -1
  191. package/hooks/gsd-secret-read-guard.js +189 -15
  192. package/hooks/gsd-statusline.js +321 -22
  193. package/hooks/gsd-validate-commit.sh +27 -5
  194. package/package.json +1 -1
  195. package/scripts/audit-workflow-script-paths.cjs +1 -1
  196. package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
  197. package/scripts/diagnose-validate-commit-phases.cjs +348 -0
  198. package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
  199. package/scripts/docs-guard-registry.cjs +2 -0
  200. package/scripts/gen-capability-registry.cjs +7 -1
  201. package/scripts/gen-loop-host-contract.cjs +4 -1
  202. package/scripts/lib/macos-conformance-tier.generated.cjs +13 -0
  203. package/scripts/lib/platform-conformance-tier.generated.cjs +26 -0
  204. package/scripts/lint-phase-arg-assignment.cjs +2 -2
  205. package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
  206. package/scripts/mutation-matrix.cjs +3 -1
  207. package/scripts/release-tarball-smoke.cjs +15 -7
  208. package/scripts/xtrace-phase-timing.cjs +205 -0
  209. package/skills/gsd-code-review/SKILL.md +8 -0
  210. package/skills/gsd-pause-work/SKILL.md +1 -1
  211. package/vscode/package.json +1 -1
@@ -48,6 +48,14 @@ Optional flags parsed from the `<arguments>` block:
48
48
  - `--files=file1,file2,...` — Explicit file list override. Has highest precedence for file scoping per D-08. When provided, workflow skips SUMMARY.md extraction and git diff fallback entirely.
49
49
 
50
50
  Context files (CLAUDE.md, SUMMARY.md, phase state) are resolved inside the workflow via `gsd-tools query init.phase-op` and delegated to agent via `<required_reading>` blocks.
51
+
52
+ ## Outside a phase
53
+
54
+ Reviewer lanes run without a phase (`gsd_run` is defined by the runtime-launcher preamble inside workflow fences; in a plain shell call `node <install>/gsd-core/bin/gsd-tools.cjs` instead): pipe canonical repo-relative file paths (one per line) to stdin of
55
+ `gsd_run review-lane dispatch-step --repo-root <root> --depth <quick|standard|deep> --base-sha <sha> --run-dir "$(mktemp -d)" --cap-id code-review --point execute:post --explicit <slugs> --raw`.
56
+ `review-lane` flags are space-separated (`--depth quick`), never `--flag=value`. `--explicit` (omitted, nothing dispatches yet the call exits ok), `--depth`, `--base-sha` (lane reviews changes relative to it; commit them first) and stdin paths are required; `--run-dir` should be a fresh temp dir (default `.` writes the prompt file into the current directory); read each lane's output path (`results[].reviewPath`) from the JSON before removing it. `--cap-id`/`--point` select the `code-review` capability, which must be enabled (`workflow.code_review`) and active at `--point` — `execute:post`, or `execute:wave:post` when `workflow.code_review_point` selects it; otherwise nothing dispatches.
57
+ `--explicit` takes comma-separated canonical slugs, no spaces (`codex,antigravity`), not flag aliases (`antigravity`, not `agy`); slugs: first column of `gsd_run review-lane sections` (`review-lane flags` lists flags/aliases).
58
+ Each lane declares its own model key, which is not always its slug (antigravity's is `review.models.agy`); see the `review.models.*` table in `docs/CONFIGURATION.md`. Unset = lane default model.
51
59
  </context>
52
60
 
53
61
  <process>
@@ -21,7 +21,7 @@ Routes to the pause-work workflow which handles:
21
21
  - Current phase detection from recent files
22
22
  - Complete state gathering (position, completed work, remaining work, decisions, blockers)
23
23
  - Handoff file creation with all context sections
24
- - Git commit as WIP
24
+ - Git commit handoff
25
25
  - Resume instructions
26
26
  </objective>
27
27
 
@@ -312,6 +312,7 @@ try {
312
312
  } catch { /* advisory — never block */ }
313
313
 
314
314
  const { resolveActiveWorkstream, applyResolvedWorkstreamEnv, peekActiveWorkstream } = require('./lib/active-workstream-store.cjs');
315
+ const { planningDir, planningRoot } = require('./lib/planning-workspace.cjs');
315
316
  const state = require('./lib/state.cjs');
316
317
  const phase = require('./lib/phase.cjs');
317
318
  const roadmap = require('./lib/roadmap.cjs');
@@ -1445,8 +1446,12 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1445
1446
  // Silent no-op when the section is absent or the table is already canonical
1446
1447
  // — the quick/fast workflows call this before their first append, so the
1447
1448
  // migration runs exactly once, on the first quick run, unprompted otherwise.
1448
- const statePath = path.join(cwd, '.planning', 'STATE.md');
1449
+ const planningPath = planningDir(cwd);
1450
+ const statePath = path.join(planningPath, 'STATE.md');
1449
1451
  if (!fs.existsSync(statePath)) {
1452
+ if (planningPath !== planningRoot(cwd)) {
1453
+ throw new ExitError(1, `quick-tasks-migrate: STATE.md not found at ${statePath}`);
1454
+ }
1450
1455
  output({ ok: true, migrated: false, reason: `STATE.md not found at ${statePath}` }, raw);
1451
1456
  return;
1452
1457
  }
@@ -1519,7 +1524,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1519
1524
  || (qtaQuickId && qtaSlug ? `[${qtaQuickId}-${qtaSlug}](./quick/${qtaQuickId}-${qtaSlug}/)` : undefined);
1520
1525
  const qtaStatus = qtaParsed['status'] || undefined;
1521
1526
 
1522
- const statePath = path.join(cwd, '.planning', 'STATE.md');
1527
+ const statePath = path.join(planningDir(cwd), 'STATE.md');
1523
1528
  if (!fs.existsSync(statePath)) {
1524
1529
  error(`quick-tasks-append: STATE.md not found at ${statePath}`, ERROR_REASON.USAGE);
1525
1530
  }
@@ -1593,7 +1598,16 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1593
1598
  const fsx = require('node:fs');
1594
1599
  const os = require('node:os');
1595
1600
  const { REVIEWER_LANES, mergeReviewerLanes } = require('./lib/review-lane-descriptor.cjs');
1596
- const { resolveLanePlan, resolveLaneEffort, resolveLaneBudget } = require('./lib/review-lane-invocation.cjs');
1601
+ const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort, resolveLaneBudget, applyPromptOverride } = require('./lib/review-lane-invocation.cjs');
1602
+ // OpenCode 2.x argv differs from 1.x (no --variant; effort rides on the model as `#variant`;
1603
+ // ADR-5331 D5/D8). The generation is probed lazily, only when the opencode lane is resolved.
1604
+ const resolveLanePlan = (laneInput) => {
1605
+ if (!laneInput || !laneInput.lane || laneInput.lane.slug !== 'opencode') return resolveLanePlanBase(laneInput);
1606
+ const ocv = require('./lib/opencode-version.cjs');
1607
+ const adapted = ocv.adaptLaneInputForGeneration(laneInput, ocv.detectOpencodeGeneration().generation);
1608
+ for (const w of adapted.warnings) process.stderr.write('gsd: ' + w + '\n');
1609
+ return ocv.adaptLanePlanForGeneration(resolveLanePlanBase(adapted.input), ocv.detectOpencodeGeneration().generation);
1610
+ };
1597
1611
  const modelCatalog = require('./lib/model-catalog.cjs');
1598
1612
  const runner = require('./lib/review-lane-runner.cjs');
1599
1613
  const cfgLoader = require('./lib/config-loader.cjs');
@@ -1936,7 +1950,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1936
1950
  const dispatchResult = await dispatchReviewerLanes(
1937
1951
  {
1938
1952
  trait,
1939
- selection: { explicitFlags, detected: rosterSlugs },
1953
+ selection: { explicitFlags, detected: rosterSlugs, lanes: mergedLanes },
1940
1954
  repoRoot,
1941
1955
  paths,
1942
1956
  depth,
@@ -2088,14 +2102,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2088
2102
  }
2089
2103
 
2090
2104
  // `--prompt-file` lets the caller substitute a budget-trimmed prompt. Applied to whichever
2091
- // channel this lane actually reads from, so one flag serves both transports.
2105
+ // channel this lane actually reads from (stdin, argv, an argv-embedded file reference, or the
2106
+ // http promptPath), so one flag serves every lane (#5049).
2092
2107
  const promptOverride = flag('--prompt-file');
2093
2108
  if (promptOverride) {
2094
- if (entry.plan.transport === 'spawn') {
2095
- if (entry.plan.stdin) entry.plan.stdin = promptOverride;
2096
- } else {
2097
- entry.plan.promptPath = promptOverride;
2098
- }
2109
+ entry.plan = applyPromptOverride(entry.plan, promptOverride, repoRoot);
2099
2110
  }
2100
2111
 
2101
2112
  // ADR-2782 D5 rule 4: re-resolve the consented destination at INVOCATION. `undefined` (no
@@ -2675,8 +2686,14 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2675
2686
  'gsd-executor', readGsdEffectiveModelOverrides(cwd), null);
2676
2687
  model = resolveDispatchModelPin('gsd-executor', pinned);
2677
2688
  }
2689
+ let orchestratorExec = runtimeEntry?.runtime?.orchestratorExec;
2690
+ if (orchestratorExec && orchestratorExec.cwdFlag === '--dir' && runtimeId === 'opencode') {
2691
+ // OpenCode 2.x `run` has no --dir: bind the worktree through the process cwd (ADR-5331 D5).
2692
+ const ocv = require('./lib/opencode-version.cjs');
2693
+ orchestratorExec = ocv.adaptOrchestratorExecForGeneration(orchestratorExec, ocv.detectOpencodeGeneration().generation);
2694
+ }
2678
2695
  const resolution = hostIntegration.resolveOrchestratorExec(
2679
- runtimeEntry?.runtime?.orchestratorExec,
2696
+ orchestratorExec,
2680
2697
  cwdTarget,
2681
2698
  promptArg,
2682
2699
  model,
@@ -3007,7 +3024,12 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
3007
3024
  const jsonIdx = args.indexOf('--json');
3008
3025
  const agentSkillsJsonMode = jsonIdx !== -1;
3009
3026
  if (agentSkillsJsonMode) args.splice(jsonIdx, 1);
3010
- init.cmdAgentSkills(cwd, args[1], raw, agentSkillsJsonMode);
3027
+ // #5122: the agent's own self-load asks for configured skills only,
3028
+ // never the persona fallback served to orchestrator-side injection.
3029
+ const skillsOnlyIdx = args.indexOf('--skills-only');
3030
+ const agentSkillsOnly = skillsOnlyIdx !== -1;
3031
+ if (agentSkillsOnly) args.splice(skillsOnlyIdx, 1);
3032
+ init.cmdAgentSkills(cwd, args[1], raw, agentSkillsJsonMode, agentSkillsOnly);
3011
3033
  }
3012
3034
 
3013
3035
  function routeSkillManifest({ args, cwd, raw, error }) {
@@ -221,13 +221,45 @@ function classifyHeader(normalizedHeader) {
221
221
  }
222
222
  return null;
223
223
  }
224
+ // A list item starts with a bullet (-, *, +) or an ordered marker (1. or 1)).
225
+ const LIST_MARKER = /^([-*+]|\d+[.)])\s+/;
226
+ /**
227
+ * Split a section body into one entry per top-level list item (#5227).
228
+ *
229
+ * A line that opens a list item starts a new entry with its marker stripped. Any
230
+ * other non-blank line, and any marker line indented deeper than the first item
231
+ * (a nested sub-bullet), joins the current entry with a single space, so a
232
+ * hard-wrapped item stays one entry. Text before the first marker is its own
233
+ * entry. A block with no list marker at all keeps one entry per line, so a plain
234
+ * `## Status` body still yields its first line.
235
+ */
224
236
  function splitEntries(blockText) {
225
- return (typeof blockText === 'string' ? blockText : '')
237
+ const lines = (typeof blockText === 'string' ? blockText : '')
226
238
  .split(/\r?\n/)
227
- .map((line) => line.trim())
228
- .filter(Boolean)
229
- .map((line) => line.replace(/^[-*+]\s+/, '').trim())
230
- .filter(Boolean);
239
+ .filter((line) => line.trim() !== '');
240
+ if (!lines.some((line) => LIST_MARKER.test(line.trim())))
241
+ return lines.map((line) => line.trim());
242
+ const entries = [];
243
+ let topIndent = -1;
244
+ let open = false;
245
+ for (const raw of lines) {
246
+ const text = raw.trim();
247
+ const indent = raw.length - raw.trimStart().length;
248
+ const isMarker = LIST_MARKER.test(text);
249
+ if (isMarker && (topIndent < 0 || indent <= topIndent)) {
250
+ if (topIndent < 0)
251
+ topIndent = indent;
252
+ entries.push(text.replace(LIST_MARKER, '').trim());
253
+ open = true;
254
+ }
255
+ else if (open) {
256
+ entries[entries.length - 1] = `${entries[entries.length - 1]} ${text}`;
257
+ }
258
+ else {
259
+ entries.push(text);
260
+ }
261
+ }
262
+ return entries.filter(Boolean);
231
263
  }
232
264
  /**
233
265
  * Thin adapter: wraps the seam's `collectSections` to produce the same
@@ -807,6 +807,28 @@ function reconcile(runtimeDir) {
807
807
  }
808
808
  return result;
809
809
  }
810
+ /**
811
+ * #5121: the one bounded read for a SKILL.md found by walking a skill root. A skill file is untrusted
812
+ * repository (or installed-runtime) content: a symlink to /dev/zero exhausts memory and a FIFO blocks
813
+ * the process, because a bare readFileSync does neither a type nor a size check. Reuses the fd protocol
814
+ * of readSmallRegularFile and maps its fail-closed "unreadable" conditions (not a regular file after
815
+ * following symlinks, over the cap) to null, so a caller treats the file as it treats a missing one.
816
+ * Any other IO error still throws, as the unbounded read did.
817
+ *
818
+ * 256 KiB: the largest shipped SKILL.md is ~21 KB, so the cap is >10x headroom while still bounding the
819
+ * allocation a hostile repository can force.
820
+ */
821
+ const SKILL_FILE_MAX_BYTES = 256 * 1024;
822
+ function readSkillFile(filePath) {
823
+ try {
824
+ return readSmallRegularFile(filePath, SKILL_FILE_MAX_BYTES);
825
+ }
826
+ catch (err) {
827
+ if (err instanceof LedgerIOError && (err.code === 'ENXIO' || err.code === 'EISDIR' || err.code === 'EFBIG'))
828
+ return null;
829
+ throw err;
830
+ }
831
+ }
810
832
  module.exports = {
811
833
  readLedger,
812
834
  readLedgerStrict,
@@ -822,6 +844,9 @@ module.exports = {
822
844
  // #1459 finding 1 (HIGH): the RAW-BYTES variant — the SOLE correct reader for the byte-exact,
823
845
  // injective consent content-hash binding (a utf8 decode is lossy and could collide binary artifacts).
824
846
  readSmallRegularFileBuffer,
847
+ // #5121: bounded SKILL.md read shared by skill discovery, the installed-runtime manifest and capability skills.
848
+ readSkillFile,
849
+ SKILL_FILE_MAX_BYTES,
825
850
  // Exported for testing / introspection
826
851
  LEDGER_FILE_NAME,
827
852
  CorruptLedgerError,
@@ -176,7 +176,8 @@ function isSameHost(parsed) {
176
176
  * equality comparison (never parsed as a date). The pair (pid, startTime) uniquely identifies a
177
177
  * process instance: even if a crashed holder's pid is REUSED, the new process's start-time differs.
178
178
  * Returns null on ANY error / unobtainable value (liveness cannot be VERIFIED → steal-eligible past
179
- * the deadman). The shell-outs only run on the rare STEAL-decision path, never the happy path.
179
+ * the deadman). Probing ANOTHER pid only happens on the rare STEAL-decision path; this process's own
180
+ * start-time is probed on its first lock acquire (see selfStartTime) — never at module load (#5327).
180
181
  */
181
182
  function getProcessStartTime(pid) {
182
183
  if (!Number.isInteger(pid) || pid <= 0)
@@ -208,11 +209,29 @@ function getProcessStartTime(pid) {
208
209
  return null;
209
210
  }
210
211
  }
211
- /** THIS process's start-time, captured ONCE at module load so we never re-shell on every lock write. */
212
- const _selfStartTime = getProcessStartTime(process.pid);
213
- /** Serialize the lockfile body: JSON carrying the owner token, pid, hostname, cached start-time, ts. */
214
- function lockFileBody(token) {
215
- return JSON.stringify({ token, pid: process.pid, hostname: node_os_1.default.hostname(), startTime: _selfStartTime, ts: Date.now() });
212
+ /**
213
+ * THIS process's start-time, probed on the first lock acquire and cached so later acquires never re-shell.
214
+ * Not probed at module load: every config read loads this module, and most never take a lock (#5327).
215
+ * Only a successful probe is cached. A failed one (a PowerShell launch past its 5 s timeout on a loaded
216
+ * Windows host) is retried on the next acquire, up to SELF_PROBE_ATTEMPTS probes in all, so a host whose
217
+ * probe never succeeds pays a bounded cost instead of one launch per acquire.
218
+ * Probed per acquire ATTEMPT, before the O_EXCL create, so a process whose first acquire meets a live
219
+ * holder still pays it: probing after a successful create instead would widen the empty-body window
220
+ * that the steal path's `LOCK_STALE_MS` floor relies on.
221
+ */
222
+ const SELF_PROBE_ATTEMPTS = 2;
223
+ let _selfStartTime = null;
224
+ let _selfProbeAttempts = 0;
225
+ function selfStartTime() {
226
+ if (_selfStartTime === null && _selfProbeAttempts < SELF_PROBE_ATTEMPTS) {
227
+ _selfProbeAttempts++;
228
+ _selfStartTime = getProcessStartTime(process.pid);
229
+ }
230
+ return _selfStartTime;
231
+ }
232
+ /** Serialize the lockfile body: JSON carrying the owner token, pid, hostname, start-time, ts. */
233
+ function lockFileBody(token, startTime) {
234
+ return JSON.stringify({ token, pid: process.pid, hostname: node_os_1.default.hostname(), startTime, ts: Date.now() });
216
235
  }
217
236
  // ---------------------------------------------------------------------------
218
237
  // Liveness probes (test seam)
@@ -364,6 +383,9 @@ function acquireLock(lockPath, opts) {
364
383
  ? opts.maxAttempts
365
384
  : LOCK_MAX_ATTEMPTS;
366
385
  const waitForFresh = !!(opts && opts.waitForFresh);
386
+ // Probe BEFORE the O_EXCL create: the first probe can be a PowerShell launch, and between the create
387
+ // and the body write a racing acquirer sees an empty lockfile. That window must stay sub-ms (#5327).
388
+ const startTime = selfStartTime();
367
389
  // A held lock that is NOT steal-eligible: fail-fast (return null) by default, or BACK OFF + RETRY
368
390
  // (continue) when waitForFresh and a retry budget remains — so a contended consent writer serializes.
369
391
  const blocked = (attempt) => {
@@ -382,7 +404,7 @@ function acquireLock(lockPath, opts) {
382
404
  // write/close error, best-effort unlink the file we just created and bail. fs.writeFileSync(fd, …)
383
405
  // flushes the WHOLE buffer (no short-write) unlike a bare fs.writeSync.
384
406
  try {
385
- node_fs_1.default.writeFileSync(fd, lockFileBody(token));
407
+ node_fs_1.default.writeFileSync(fd, lockFileBody(token, startTime));
386
408
  }
387
409
  catch (writeErr) {
388
410
  try {