@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -2
- package/.opencode/plugins/gsd-core.js +251 -1
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.compact.md +6 -0
- package/agents/gsd-debug-session-manager.md +6 -0
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +1 -1
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +398 -95
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +34 -12
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +29 -7
- package/gsd-core/bin/lib/capability-registry.cjs +138 -80
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +6 -3
- package/gsd-core/bin/lib/capability-validator.cjs +8 -5
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/commands.cjs +33 -9
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
- package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
- package/gsd-core/bin/lib/graphify.cjs +3 -2
- package/gsd-core/bin/lib/init.cjs +43 -16
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +11 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +5 -3
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- package/gsd-core/bin/lib/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/phase.cjs +141 -11
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap.cjs +56 -5
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +142 -6
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
- package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
- package/gsd-core/bin/lib/smart-entry.cjs +100 -53
- package/gsd-core/bin/lib/state-document.cjs +37 -3
- package/gsd-core/bin/lib/state-transition.cjs +98 -19
- package/gsd-core/bin/lib/state.cjs +544 -126
- package/gsd-core/bin/lib/task-command-router.cjs +2 -3
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- package/gsd-core/bin/lib/verification.cjs +107 -17
- package/gsd-core/bin/lib/verify.cjs +36 -1
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +3 -3
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/tdd.md +4 -3
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +21 -2
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone.md +25 -5
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
- package/gsd-core/workflows/discuss-phase.md +5 -1
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
- package/gsd-core/workflows/execute-phase.md +40 -7
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/import.md +5 -1
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +41 -73
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +22 -7
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +19 -11
- package/gsd-core/workflows/review.md +6 -2
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +3 -3
- package/gsd-core/workflows/ship.md +13 -10
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +11 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
- package/hooks/dist/gsd-check-update.js +26 -20
- package/hooks/dist/gsd-context-monitor.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
- package/hooks/dist/gsd-secret-read-guard.js +189 -15
- package/hooks/dist/gsd-statusline.js +321 -22
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-agent-isolation-guard.js +3 -1
- package/hooks/gsd-check-update.js +26 -20
- package/hooks/gsd-context-monitor.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +2 -1
- package/hooks/gsd-secret-read-guard.js +189 -15
- package/hooks/gsd-statusline.js +321 -22
- package/hooks/gsd-validate-commit.sh +27 -5
- package/package.json +1 -1
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
- package/scripts/diagnose-validate-commit-phases.cjs +348 -0
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-loop-host-contract.cjs +4 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +13 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +26 -0
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/mutation-matrix.cjs +3 -1
- package/scripts/release-tarball-smoke.cjs +15 -7
- package/scripts/xtrace-phase-timing.cjs +205 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- 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
|
|
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
|
|
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, '
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
+
const lines = (typeof blockText === 'string' ? blockText : '')
|
|
226
238
|
.split(/\r?\n/)
|
|
227
|
-
.
|
|
228
|
-
|
|
229
|
-
.map((line) => line.
|
|
230
|
-
|
|
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).
|
|
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
|
-
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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 {
|