peaks-loop 4.1.2 → 4.1.3
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 +136 -47
- package/CHANGELOG.md +56 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/contracts/readonly-argv-whitelist.json +211 -0
- package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
- package/dist/cli/commands/audit-artifact-runners.js +170 -0
- package/dist/cli/commands/audit-command-shared.d.ts +97 -0
- package/dist/cli/commands/audit-command-shared.js +60 -0
- package/dist/cli/commands/audit-commands.d.ts +7 -31
- package/dist/cli/commands/audit-commands.js +64 -473
- package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
- package/dist/cli/commands/audit-goal-runners.js +114 -0
- package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
- package/dist/cli/commands/audit-red-lines-runners.js +43 -0
- package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
- package/dist/cli/commands/audit-refusal-data.js +69 -0
- package/dist/cli/commands/audit-static-runners.d.ts +13 -0
- package/dist/cli/commands/audit-static-runners.js +139 -0
- package/dist/cli/commands/await-runners.d.ts +11 -0
- package/dist/cli/commands/await-runners.js +148 -0
- package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
- package/dist/cli/commands/code-auto-compact-command.js +124 -0
- package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
- package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
- package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
- package/dist/cli/commands/code-context-audit-command.js +68 -0
- package/dist/cli/commands/code-context-now-command.d.ts +3 -0
- package/dist/cli/commands/code-context-now-command.js +124 -0
- package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
- package/dist/cli/commands/code-context-now-envelope.js +136 -0
- package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
- package/dist/cli/commands/code-context-now-mapping.js +43 -0
- package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
- package/dist/cli/commands/code-emit-handoff-command.js +99 -0
- package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
- package/dist/cli/commands/code-gate-step-08-command.js +139 -0
- package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
- package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
- package/dist/cli/commands/code-runtime-commands.js +20 -672
- package/dist/cli/commands/code-runtime-session.d.ts +1 -0
- package/dist/cli/commands/code-runtime-session.js +15 -0
- package/dist/cli/commands/compact-command.d.ts +6 -2
- package/dist/cli/commands/compact-command.js +12 -559
- package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
- package/dist/cli/commands/compact-dry-run-command.js +87 -0
- package/dist/cli/commands/compact-force-command.d.ts +3 -0
- package/dist/cli/commands/compact-force-command.js +98 -0
- package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
- package/dist/cli/commands/compact-harness-window-command.js +174 -0
- package/dist/cli/commands/compact-history-command.d.ts +3 -0
- package/dist/cli/commands/compact-history-command.js +89 -0
- package/dist/cli/commands/compact-read-commands.d.ts +3 -0
- package/dist/cli/commands/compact-read-commands.js +123 -0
- package/dist/cli/commands/compact-settle-command.d.ts +3 -0
- package/dist/cli/commands/compact-settle-command.js +130 -0
- package/dist/cli/commands/compact-shared.d.ts +9 -0
- package/dist/cli/commands/compact-shared.js +26 -0
- package/dist/cli/commands/core/skill-command.d.ts +1 -44
- package/dist/cli/commands/core/skill-command.js +13 -541
- package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
- package/dist/cli/commands/core/skill-context-verdict.js +63 -0
- package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
- package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
- package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-commands.js +123 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
- package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-registry-commands.js +117 -0
- package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
- package/dist/cli/commands/core/skill-runbook-command.js +29 -0
- package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
- package/dist/cli/commands/dispatch-command-spec.js +62 -0
- package/dist/cli/commands/dispatch-commands.js +159 -775
- package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
- package/dist/cli/commands/dispatch-detached-branch.js +58 -0
- package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
- package/dist/cli/commands/dispatch-envelope.js +104 -0
- package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
- package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
- package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
- package/dist/cli/commands/dispatch-preflight.js +105 -0
- package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
- package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
- package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
- package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
- package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
- package/dist/cli/commands/dispatch-record-write.js +96 -0
- package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
- package/dist/cli/commands/dispatch-tool-call.js +128 -0
- package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
- package/dist/cli/commands/evolution-command-shared.js +107 -0
- package/dist/cli/commands/evolution-commands.d.ts +11 -1
- package/dist/cli/commands/evolution-commands.js +19 -350
- package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
- package/dist/cli/commands/evolution-evaluate-command.js +138 -0
- package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
- package/dist/cli/commands/evolution-propose-command.js +145 -0
- package/dist/cli/commands/evolution-status-command.d.ts +17 -0
- package/dist/cli/commands/evolution-status-command.js +41 -0
- package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
- package/dist/cli/commands/evolution-verdict-commands.js +100 -0
- package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
- package/dist/cli/commands/finalize-accumulator.js +55 -0
- package/dist/cli/commands/finalize-branches.d.ts +34 -0
- package/dist/cli/commands/finalize-branches.js +119 -0
- package/dist/cli/commands/finalize-runners.d.ts +3 -0
- package/dist/cli/commands/finalize-runners.js +92 -0
- package/dist/cli/commands/finalize-selection.d.ts +65 -0
- package/dist/cli/commands/finalize-selection.js +35 -0
- package/dist/cli/commands/gate-commands.js +26 -8
- package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
- package/dist/cli/commands/job-checkpoint-commands.js +157 -0
- package/dist/cli/commands/job-command-shared.d.ts +109 -0
- package/dist/cli/commands/job-command-shared.js +72 -0
- package/dist/cli/commands/job-commands.d.ts +4 -64
- package/dist/cli/commands/job-commands.js +42 -552
- package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
- package/dist/cli/commands/job-cost-check-command.js +41 -0
- package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
- package/dist/cli/commands/job-init-status-commands.js +149 -0
- package/dist/cli/commands/job-run-commands.d.ts +3 -0
- package/dist/cli/commands/job-run-commands.js +97 -0
- package/dist/cli/commands/job-state-root.d.ts +35 -0
- package/dist/cli/commands/job-state-root.js +76 -0
- package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
- package/dist/cli/commands/loop-eval-capture-index.js +44 -0
- package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
- package/dist/cli/commands/loop-eval-commands.js +12 -626
- package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-eval-command.js +168 -0
- package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
- package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
- package/dist/cli/commands/loop-eval-parents.js +20 -0
- package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
- package/dist/cli/commands/loop-eval-run-code.js +40 -0
- package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-run-command.js +113 -0
- package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
- package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
- package/dist/cli/commands/mcp-commands.d.ts +10 -0
- package/dist/cli/commands/mcp-commands.js +157 -0
- package/dist/cli/commands/request-command-options.d.ts +49 -0
- package/dist/cli/commands/request-command-options.js +1 -0
- package/dist/cli/commands/request-commands.d.ts +3 -9
- package/dist/cli/commands/request-commands.js +17 -522
- package/dist/cli/commands/request-init-command.d.ts +3 -0
- package/dist/cli/commands/request-init-command.js +100 -0
- package/dist/cli/commands/request-lint-command.d.ts +3 -0
- package/dist/cli/commands/request-lint-command.js +50 -0
- package/dist/cli/commands/request-list-command.d.ts +10 -0
- package/dist/cli/commands/request-list-command.js +53 -0
- package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
- package/dist/cli/commands/request-repair-status-command.js +61 -0
- package/dist/cli/commands/request-show-command.d.ts +3 -0
- package/dist/cli/commands/request-show-command.js +56 -0
- package/dist/cli/commands/request-transition-command.d.ts +3 -0
- package/dist/cli/commands/request-transition-command.js +122 -0
- package/dist/cli/commands/request-transition-failures.d.ts +8 -0
- package/dist/cli/commands/request-transition-failures.js +90 -0
- package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
- package/dist/cli/commands/request-transition-hooks.js +115 -0
- package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
- package/dist/cli/commands/request-transition-preflight.js +107 -0
- package/dist/cli/commands/scan-command-shared.d.ts +96 -0
- package/dist/cli/commands/scan-command-shared.js +83 -0
- package/dist/cli/commands/scan-commands.d.ts +3 -2
- package/dist/cli/commands/scan-commands.js +23 -463
- package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
- package/dist/cli/commands/scan-inventory-commands.js +96 -0
- package/dist/cli/commands/scan-project-commands.d.ts +3 -0
- package/dist/cli/commands/scan-project-commands.js +65 -0
- package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
- package/dist/cli/commands/scan-scope-commands.js +149 -0
- package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
- package/dist/cli/commands/scan-scope-helpers.js +54 -0
- package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
- package/dist/cli/commands/scan-structural-commands.js +113 -0
- package/dist/cli/commands/share-commands.d.ts +12 -68
- package/dist/cli/commands/share-commands.js +47 -556
- package/dist/cli/commands/share-error-hints.d.ts +14 -0
- package/dist/cli/commands/share-error-hints.js +38 -0
- package/dist/cli/commands/share-identity.d.ts +13 -0
- package/dist/cli/commands/share-identity.js +22 -0
- package/dist/cli/commands/share-record-paths.d.ts +37 -0
- package/dist/cli/commands/share-record-paths.js +68 -0
- package/dist/cli/commands/share-runners.d.ts +3 -0
- package/dist/cli/commands/share-runners.js +158 -0
- package/dist/cli/commands/shared-read-runners.d.ts +3 -0
- package/dist/cli/commands/shared-read-runners.js +51 -0
- package/dist/cli/commands/slice-check-command.d.ts +3 -0
- package/dist/cli/commands/slice-check-command.js +54 -0
- package/dist/cli/commands/slice-commands.d.ts +2 -22
- package/dist/cli/commands/slice-commands.js +18 -517
- package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
- package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
- package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
- package/dist/cli/commands/slice-decompose-command.js +123 -0
- package/dist/cli/commands/slice-list-commands.d.ts +3 -0
- package/dist/cli/commands/slice-list-commands.js +182 -0
- package/dist/cli/commands/slice-pick-command.d.ts +3 -0
- package/dist/cli/commands/slice-pick-command.js +60 -0
- package/dist/cli/commands/slice-plan-command.d.ts +24 -0
- package/dist/cli/commands/slice-plan-command.js +111 -0
- package/dist/cli/commands/workflow-commands.d.ts +10 -1
- package/dist/cli/commands/workflow-commands.js +6 -594
- package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-gate-commands.js +116 -0
- package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
- package/dist/cli/commands/workflow-plan-helpers.js +103 -0
- package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-planning-commands.js +56 -0
- package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
- package/dist/cli/commands/workflow-swarm-actions.js +109 -0
- package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-swarm-commands.js +138 -0
- package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
- package/dist/cli/commands/workflow-tech-actions.js +150 -0
- package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
- package/dist/cli/commands/workspace/init-actions.js +199 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
- package/dist/cli/commands/workspace/init-command.d.ts +8 -124
- package/dist/cli/commands/workspace/init-command.js +59 -623
- package/dist/cli/commands/workspace/init-context.d.ts +58 -0
- package/dist/cli/commands/workspace/init-context.js +121 -0
- package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
- package/dist/cli/commands/workspace/init-envelope.js +34 -0
- package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
- package/dist/cli/commands/workspace/init-failures.js +84 -0
- package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
- package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
- package/dist/cli/commands/workspace/init-options.d.ts +102 -0
- package/dist/cli/commands/workspace/init-options.js +1 -0
- package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
- package/dist/cli/commands/worktree-lease-commands.js +42 -512
- package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
- package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-git.js +48 -0
- package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-list-command.js +100 -0
- package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
- package/dist/cli/commands/worktree-lease-load.js +28 -0
- package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
- package/dist/cli/commands/worktree-lease-release-command.js +105 -0
- package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
- package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-session.js +29 -0
- package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
- package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-status-command.js +86 -0
- package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
- package/dist/cli/commands/worktree-lease-store.js +24 -0
- package/dist/cli/program.js +5 -0
- package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
- package/dist/services/distribution/mcp-install-runner.js +59 -0
- package/dist/services/distribution/mcp-install.d.ts +95 -0
- package/dist/services/distribution/mcp-install.js +179 -0
- package/dist/services/distribution/plugin-manifest.d.ts +97 -0
- package/dist/services/distribution/plugin-manifest.js +126 -0
- package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
- package/dist/services/hooks/mcp-surface-gate.js +87 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
- package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
- package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
- package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
- package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
- package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
- package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
- package/dist/services/ide/ide-mcp-install-types.js +104 -0
- package/dist/services/ide/ide-types.d.ts +45 -1
- package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
- package/dist/services/ide/mcp-tool-matcher.js +84 -0
- package/dist/services/lint/npx-resolver.js +7 -0
- package/dist/services/mcp/cli-executor.d.ts +36 -0
- package/dist/services/mcp/cli-executor.js +97 -0
- package/dist/services/mcp/json-rpc.d.ts +81 -0
- package/dist/services/mcp/json-rpc.js +116 -0
- package/dist/services/mcp/memory-tool.d.ts +3 -0
- package/dist/services/mcp/memory-tool.js +62 -0
- package/dist/services/mcp/server-main.d.ts +1 -0
- package/dist/services/mcp/server-main.js +66 -0
- package/dist/services/mcp/server.d.ts +22 -0
- package/dist/services/mcp/server.js +152 -0
- package/dist/services/mcp/surface.d.ts +62 -0
- package/dist/services/mcp/surface.js +198 -0
- package/dist/services/mcp/tool-core.d.ts +42 -0
- package/dist/services/mcp/tool-core.js +137 -0
- package/dist/services/mcp/tools.d.ts +13 -0
- package/dist/services/mcp/tools.js +58 -0
- package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
- package/dist/services/readonly-surface/argv-guard.js +164 -0
- package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
- package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
- package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
- package/dist/services/readonly-surface/whitelist-generator.js +212 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
- package/dist/services/skills/hooks-settings-service.js +10 -2
- package/package.json +17 -6
- package/scripts/install-skills-bundles.mjs +233 -0
- package/scripts/install-skills-fs.mjs +148 -0
- package/scripts/install-skills-ide.mjs +279 -0
- package/scripts/install-skills-output-style-default.mjs +196 -0
- package/scripts/install-skills-output-styles.mjs +121 -0
- package/scripts/install-skills-report.mjs +42 -0
- package/scripts/install-skills-user-config.mjs +198 -0
- package/scripts/install-skills.mjs +36 -1066
- package/scripts/watch-tree.mjs +140 -0
- package/scripts/watch.mjs +4 -126
- package/skills/peaks-status/SKILL.md +31 -13
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `finalize` action body for `peaks sub-agent`, split out of
|
|
3
|
+
* `share-commands.ts` so that registrar file stays under the line cap. The
|
|
4
|
+
* registrar fires this runner without awaiting it (the original fired a
|
|
5
|
+
* floating async IIFE in the same way).
|
|
6
|
+
*
|
|
7
|
+
* `runFinalizeSweep` builds the accumulator, applies the outcome mapping and
|
|
8
|
+
* prints the envelope; the three selection branches live in
|
|
9
|
+
* `finalize-branches.ts` and the selection rule itself in
|
|
10
|
+
* `finalize-selection.ts`.
|
|
11
|
+
*/
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import { fail, getErrorMessage, ok } from 'peaks-loop-shared/result';
|
|
14
|
+
import { printResult } from '../cli-helpers.js';
|
|
15
|
+
import { getCurrentSessionId } from '../../services/skills/skill-presence-service.js';
|
|
16
|
+
import { OUTCOME_MAP, buildFinalizeHints, makeAccumulator, tryReadRecord } from './finalize-accumulator.js';
|
|
17
|
+
import { finalizeAllStale, finalizeByBatch, finalizeByRequestId } from './finalize-branches.js';
|
|
18
|
+
export async function runFinalize(options, io) {
|
|
19
|
+
const asJson = options.json === true;
|
|
20
|
+
try {
|
|
21
|
+
const projectRoot = resolve(options.project ?? process.cwd());
|
|
22
|
+
const sessionId = options.sessionId ?? getCurrentSessionId(projectRoot) ?? 'unknown-sid';
|
|
23
|
+
if (!options.allStale && !options.requestId && !options.batch) {
|
|
24
|
+
printResult(io, fail('sub-agent.finalize', 'MISSING_TARGET', 'Pass --request-id or --batch (or --all-stale)', { ok: false }, ['Call finalize after each Task completes.']), asJson);
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
await runFinalizeSweep({ options, io, asJson, projectRoot, sessionId });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
printResult(io, fail('sub-agent.finalize', 'FINALIZE_ERROR', getErrorMessage(error), { ok: false }, [
|
|
32
|
+
'Inspect the error.'
|
|
33
|
+
]), asJson);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function runFinalizeSweep(input) {
|
|
38
|
+
const { options, io, asJson, projectRoot, sessionId } = input;
|
|
39
|
+
const outcome = (options.outcome ?? 'done');
|
|
40
|
+
const writerMod = await import('../../services/dispatch/dispatch-record-writer.js');
|
|
41
|
+
const { readRecord, markCompleted, readActiveDispatchIndex } = writerMod;
|
|
42
|
+
const mapped = OUTCOME_MAP[outcome] ?? OUTCOME_MAP['done'];
|
|
43
|
+
const acc = makeAccumulator();
|
|
44
|
+
const readOne = (recordPath) => tryReadRecord(recordPath, readRecord, acc);
|
|
45
|
+
const apply = (recordPath, rid) => {
|
|
46
|
+
markCompleted({
|
|
47
|
+
recordPath,
|
|
48
|
+
now: () => new Date(),
|
|
49
|
+
status: mapped.status,
|
|
50
|
+
outcome: mapped.outcome,
|
|
51
|
+
projectRoot
|
|
52
|
+
});
|
|
53
|
+
acc.finalized.push({ recordPath, requestId: rid, status: mapped.status });
|
|
54
|
+
};
|
|
55
|
+
let selection = null;
|
|
56
|
+
if (options.allStale) {
|
|
57
|
+
finalizeAllStale({ readActiveDispatchIndex, projectRoot, sessionId, apply, acc });
|
|
58
|
+
}
|
|
59
|
+
else if (options.requestId) {
|
|
60
|
+
const result = await finalizeByRequestId({
|
|
61
|
+
projectRoot,
|
|
62
|
+
sessionId,
|
|
63
|
+
requestId: options.requestId,
|
|
64
|
+
io,
|
|
65
|
+
asJson,
|
|
66
|
+
readOne,
|
|
67
|
+
apply,
|
|
68
|
+
acc
|
|
69
|
+
});
|
|
70
|
+
if (result.kind === 'not-found')
|
|
71
|
+
return;
|
|
72
|
+
selection = result.selection;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
await finalizeByBatch({ projectRoot, sessionId, batchId: options.batch, readOne, apply, acc });
|
|
76
|
+
}
|
|
77
|
+
printFinalizeResult({ io, asJson, sessionId, outcome, acc, selection });
|
|
78
|
+
if (acc.errors.length > 0)
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
/** Print the `sub-agent.finalize` envelope and its hints. */
|
|
82
|
+
function printFinalizeResult(input) {
|
|
83
|
+
const { acc } = input;
|
|
84
|
+
printResult(input.io, ok('sub-agent.finalize', {
|
|
85
|
+
finalized: acc.finalized,
|
|
86
|
+
skipped: acc.skipped,
|
|
87
|
+
errors: acc.errors,
|
|
88
|
+
selection: input.selection,
|
|
89
|
+
sessionId: input.sessionId,
|
|
90
|
+
outcome: input.outcome
|
|
91
|
+
}, acc.errors.length > 0 ? [acc.errors.length + ' failed'] : [], buildFinalizeHints(acc, input.selection)), input.asJson);
|
|
92
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure `--request-id` selection contract for `peaks sub-agent finalize`, plus
|
|
3
|
+
* the option shape the command registers. Extracted from `share-commands.ts` so
|
|
4
|
+
* that file stays under the line cap; these are the pieces the finalize tests
|
|
5
|
+
* import directly, and none of them touches the filesystem.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A `dispatch-*.json` candidate for `finalize --request-id`, as read off disk.
|
|
9
|
+
* The record's own `createdAt` is carried as the recency key — NOT the file's
|
|
10
|
+
* mtime, which any heartbeat or finalize rewrites, so a `done` record can look
|
|
11
|
+
* newer than the `queued` one that superseded it. Keeping the key on the
|
|
12
|
+
* candidate makes the selection rule a pure function of the records and
|
|
13
|
+
* testable without a filesystem.
|
|
14
|
+
*/
|
|
15
|
+
export interface FinalizeCandidate {
|
|
16
|
+
readonly recordPath: string;
|
|
17
|
+
readonly requestId: string;
|
|
18
|
+
readonly status: string;
|
|
19
|
+
readonly createdAt: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* What `--request-id` actually did, reported in the envelope. N3: the branch
|
|
23
|
+
* used to resolve silently and could not say WHY a record was or was not the
|
|
24
|
+
* one finalized.
|
|
25
|
+
*/
|
|
26
|
+
export interface FinalizeSelection {
|
|
27
|
+
readonly requestId: string;
|
|
28
|
+
readonly rule: string;
|
|
29
|
+
readonly matched: number;
|
|
30
|
+
readonly chosen: string | null;
|
|
31
|
+
readonly rejected: readonly {
|
|
32
|
+
readonly recordPath: string;
|
|
33
|
+
readonly status: string;
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
}[];
|
|
36
|
+
}
|
|
37
|
+
/** The one selection rule `--request-id` and `--batch` now share. */
|
|
38
|
+
export declare const FINALIZE_SELECTION_RULE: string;
|
|
39
|
+
/** The options `peaks sub-agent finalize` accepts. */
|
|
40
|
+
export interface FinalizeOptions {
|
|
41
|
+
batch?: string;
|
|
42
|
+
requestId?: string;
|
|
43
|
+
outcome?: string;
|
|
44
|
+
error?: string;
|
|
45
|
+
allStale?: boolean;
|
|
46
|
+
project?: string;
|
|
47
|
+
sessionId?: string;
|
|
48
|
+
json?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* N3 — pick the record `finalize --request-id` should act on.
|
|
52
|
+
*
|
|
53
|
+
* The branch this replaces `break`ed on the FIRST file whose record carried
|
|
54
|
+
* the requestId and never looked at `status`. With a re-dispatched request
|
|
55
|
+
* always resolved to the OLDEST one — the already-`done` RD record — so
|
|
56
|
+
* finalizing reported success while the newer `queued` QA record stayed
|
|
57
|
+
* queued forever. `--batch` never had that bug: it filters on `queued`.
|
|
58
|
+
*
|
|
59
|
+
* Both branches are now the same rule. Return `null` when nothing is queued:
|
|
60
|
+
* a record that already left `queued` is precisely the one that must NOT be
|
|
61
|
+
* re-finalized, so the caller reports the survivors instead of touching one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function selectFinalizeTarget(candidates: readonly FinalizeCandidate[]): FinalizeCandidate | null;
|
|
64
|
+
/** Why a candidate was not the chosen one — reported, never guessed at. */
|
|
65
|
+
export declare function describeFinalizeRejection(candidate: FinalizeCandidate, chosen: FinalizeCandidate | null): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure `--request-id` selection contract for `peaks sub-agent finalize`, plus
|
|
3
|
+
* the option shape the command registers. Extracted from `share-commands.ts` so
|
|
4
|
+
* that file stays under the line cap; these are the pieces the finalize tests
|
|
5
|
+
* import directly, and none of them touches the filesystem.
|
|
6
|
+
*/
|
|
7
|
+
/** The one selection rule `--request-id` and `--batch` now share. */
|
|
8
|
+
export const FINALIZE_SELECTION_RULE = 'prefer status=queued; among those, newest by record createdAt (then filename); ' +
|
|
9
|
+
'no queued match means nothing is finalized';
|
|
10
|
+
/**
|
|
11
|
+
* N3 — pick the record `finalize --request-id` should act on.
|
|
12
|
+
*
|
|
13
|
+
* The branch this replaces `break`ed on the FIRST file whose record carried
|
|
14
|
+
* the requestId and never looked at `status`. With a re-dispatched request
|
|
15
|
+
* always resolved to the OLDEST one — the already-`done` RD record — so
|
|
16
|
+
* finalizing reported success while the newer `queued` QA record stayed
|
|
17
|
+
* queued forever. `--batch` never had that bug: it filters on `queued`.
|
|
18
|
+
*
|
|
19
|
+
* Both branches are now the same rule. Return `null` when nothing is queued:
|
|
20
|
+
* a record that already left `queued` is precisely the one that must NOT be
|
|
21
|
+
* re-finalized, so the caller reports the survivors instead of touching one.
|
|
22
|
+
*/
|
|
23
|
+
export function selectFinalizeTarget(candidates) {
|
|
24
|
+
const queued = candidates.filter((candidate) => candidate.status === 'queued');
|
|
25
|
+
if (queued.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
return [...queued].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.recordPath.localeCompare(a.recordPath))[0];
|
|
28
|
+
}
|
|
29
|
+
/** Why a candidate was not the chosen one — reported, never guessed at. */
|
|
30
|
+
export function describeFinalizeRejection(candidate, chosen) {
|
|
31
|
+
if (candidate.status !== 'queued' || chosen === null) {
|
|
32
|
+
return 'status is ' + candidate.status;
|
|
33
|
+
}
|
|
34
|
+
return 'superseded by the newer queued record ' + chosen.recordPath;
|
|
35
|
+
}
|
|
@@ -4,6 +4,7 @@ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
|
4
4
|
import { emitHint } from '../../services/hooks/output.js';
|
|
5
5
|
import { parseClaudeShapeStdin } from '../../services/ide/hook-translator.js';
|
|
6
6
|
import { classifyTool, emitAllowSkipped, emitAllowWorktree, emitSopAllow, emitSopDeny, emitSopWarnings, handleWorktreeGate, parseHookStdin, resolveToolMatcher } from './gate-commands-enforce.js';
|
|
7
|
+
import { handleMcpSurfaceGate } from '../../services/hooks/mcp-surface-gate.js';
|
|
7
8
|
export function registerGateCommands(program, io) {
|
|
8
9
|
const gate = program
|
|
9
10
|
.command('gate')
|
|
@@ -53,6 +54,19 @@ function exitWhenRealCliHook() {
|
|
|
53
54
|
process.exit(process.exitCode || 0);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
57
|
+
/** Read the payload's surface out of the tool name, the adapter's matcher and the command. */
|
|
58
|
+
function classifySurface(parsedStdin) {
|
|
59
|
+
const toolMatcher = resolveToolMatcher(parsedStdin);
|
|
60
|
+
const { toolName, command } = parseClaudeShapeStdin(parsedStdin);
|
|
61
|
+
const toolKind = classifyTool(toolName);
|
|
62
|
+
const isBash = toolName === toolMatcher && typeof command === 'string' && command.trim().length > 0;
|
|
63
|
+
return {
|
|
64
|
+
toolKind,
|
|
65
|
+
command,
|
|
66
|
+
bashCommand: isBash ? command : null,
|
|
67
|
+
isWorktreeToolSurface: toolKind === 'Agent' || toolKind === 'EnterWorktree'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
56
70
|
/**
|
|
57
71
|
* Body of the `gate enforce` PreToolUse handler. The worktree-then-SOP ordering and every early
|
|
58
72
|
* return / exit-code path are documented on the helpers this delegates to in `gate-commands-enforce`.
|
|
@@ -63,23 +77,27 @@ async function runGateEnforceAction(io, options) {
|
|
|
63
77
|
try {
|
|
64
78
|
const raw = await readHookPayload();
|
|
65
79
|
const parsedStdin = parseHookStdin(raw);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
// The MCP branch runs FIRST and is fail-CLOSED, so it must not sit behind
|
|
81
|
+
// the "not a guarded surface" early return below — an MCP tool name matches
|
|
82
|
+
// neither the Bash matcher nor a worktree tool, so everything after this
|
|
83
|
+
// point would wave it through. A block here has already been emitted.
|
|
84
|
+
if (handleMcpSurfaceGate(io, parsedStdin)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const surface = classifySurface(parsedStdin);
|
|
88
|
+
const { toolKind, command } = surface;
|
|
89
|
+
if (surface.bashCommand === null && !surface.isWorktreeToolSurface) {
|
|
72
90
|
emitAllowSkipped(io, options);
|
|
73
91
|
return;
|
|
74
92
|
}
|
|
75
93
|
if (handleWorktreeGate(io, options, toolKind, { command, parsedStdin })) {
|
|
76
94
|
return;
|
|
77
95
|
}
|
|
78
|
-
if (
|
|
96
|
+
if (surface.bashCommand === null) {
|
|
79
97
|
emitAllowWorktree(io, options);
|
|
80
98
|
return;
|
|
81
99
|
}
|
|
82
|
-
const decision = await enforceBashCommand(options.project,
|
|
100
|
+
const decision = await enforceBashCommand(options.project, surface.bashCommand);
|
|
83
101
|
if (decision.decision === 'deny') {
|
|
84
102
|
emitSopDeny(io, options, decision);
|
|
85
103
|
return;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { ok, fail } from 'peaks-loop-shared/result';
|
|
2
|
+
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
3
|
+
import { JobStateStore } from '../../services/job/job-state-store.js';
|
|
4
|
+
import { JobOrchestrator } from '../../services/job/job-orchestrator.js';
|
|
5
|
+
import { writeJobProgress } from '../../services/job/job-progress-store.js';
|
|
6
|
+
import { JobCheckpointInputSchema, JobBlockInputSchema } from '../../services/job/job-types.js';
|
|
7
|
+
import { codegraphRefreshNotice, refreshCodegraphAfterSlice } from '../../services/codegraph/codegraph-autorefresh.js';
|
|
8
|
+
import { asJson, failResult, projectRoot, resolveSliceId } from './job-command-shared.js';
|
|
9
|
+
import { SESSION_ID_HELP, resolveJobStateRoot } from './job-state-root.js';
|
|
10
|
+
const SLICE_NOT_FOUND_HINTS = ['Re-run with one of the valid slice ids'];
|
|
11
|
+
const NO_REFRESH = { codegraph: null, codegraphWarning: null };
|
|
12
|
+
/**
|
|
13
|
+
* Auto codegraph refresh at the slice-complete boundary. Best-effort:
|
|
14
|
+
* a refresh failure must never fail the checkpoint.
|
|
15
|
+
*
|
|
16
|
+
* A2 (2026-09-17): "must never fail the checkpoint" is not "must never be
|
|
17
|
+
* seen". A refresh that did not happen while a codegraph store IS in use is now
|
|
18
|
+
* a warning line (stderr, `warning: ` prefix, via printResult) naming the reason
|
|
19
|
+
* and the remedy. See `codegraphRefreshNotice` for why `no-codegraph-dir` stays
|
|
20
|
+
* silent.
|
|
21
|
+
*
|
|
22
|
+
* ADVISORY (F1, 2026-09-17) — deliberately exits 0, and the reach of that word
|
|
23
|
+
* is exactly here. The checkpoint itself SUCCEEDED (the slice flipped to done
|
|
24
|
+
* and progress.json was mirrored above); the refresh is a derived index,
|
|
25
|
+
* rebuildable on demand. So the outcome is reported through `ok(...)` + a
|
|
26
|
+
* `warning:` line, never through `failResult`, and `process.exitCode` is left
|
|
27
|
+
* alone even when the refresh throws. Raising it would turn an advisory rebuild
|
|
28
|
+
* into a build breaker for every CI that runs `peaks job checkpoint`. Pinned by
|
|
29
|
+
* the "advisory stays 0" control in tests/unit/cli/job-exit-code.test.ts.
|
|
30
|
+
*/
|
|
31
|
+
async function applyCheckpointDone(ctx) {
|
|
32
|
+
const { orch, jobRoot, opts, data, sliceId } = ctx;
|
|
33
|
+
await orch.checkpointDone({
|
|
34
|
+
jobId: data.jobId,
|
|
35
|
+
sliceId,
|
|
36
|
+
...(data.commitSha ? { commitSha: data.commitSha } : {})
|
|
37
|
+
});
|
|
38
|
+
// v3.1.2: after each --state done, mirror slice progress to
|
|
39
|
+
// .peaks/_runtime/<sessionId>/job/<jid>/progress.json so the
|
|
40
|
+
// next LLM turn (or peaks code gate-step-08 hook) can read it.
|
|
41
|
+
const project = projectRoot(opts);
|
|
42
|
+
const sessId = jobRoot.sessionId;
|
|
43
|
+
const state = orch.status(data.jobId);
|
|
44
|
+
writeJobProgress(project, sessId, {
|
|
45
|
+
jobId: data.jobId,
|
|
46
|
+
done: state.done,
|
|
47
|
+
total: state.total,
|
|
48
|
+
currentSlice: state.currentSlice,
|
|
49
|
+
lastCommitSha: data.commitSha ?? null,
|
|
50
|
+
updatedAt: new Date().toISOString()
|
|
51
|
+
});
|
|
52
|
+
let codegraph;
|
|
53
|
+
try {
|
|
54
|
+
codegraph = await refreshCodegraphAfterSlice(project);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
codegraph = {
|
|
58
|
+
refreshed: false,
|
|
59
|
+
reason: 'unavailable',
|
|
60
|
+
note: `auto codegraph refresh failed: ${e instanceof Error ? e.message : String(e)}`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { codegraph, codegraphWarning: codegraphRefreshNotice(codegraph) };
|
|
64
|
+
}
|
|
65
|
+
async function applyCheckpointState(ctx) {
|
|
66
|
+
const { orch, data, sliceId } = ctx;
|
|
67
|
+
if (data.state === 'done')
|
|
68
|
+
return applyCheckpointDone(ctx);
|
|
69
|
+
if (data.state === 'skipped') {
|
|
70
|
+
await orch.checkpointSkipped({ jobId: data.jobId, sliceId, reason: data.reason });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
await orch.checkpointFailed({ jobId: data.jobId, sliceId, reason: data.reason });
|
|
74
|
+
}
|
|
75
|
+
return NO_REFRESH;
|
|
76
|
+
}
|
|
77
|
+
function failCheckpointSliceNotFound(io, opts, data, slice) {
|
|
78
|
+
failResult(io, fail('checkpoint', 'SLICE_NOT_FOUND', slice.message, { jobId: data.jobId, sliceId: data.sliceId, validSliceIds: slice.validSliceIds }, SLICE_NOT_FOUND_HINTS), opts);
|
|
79
|
+
}
|
|
80
|
+
async function runJobCheckpoint(opts, io) {
|
|
81
|
+
const parsed = JobCheckpointInputSchema.safeParse({
|
|
82
|
+
jobId: opts.jobId,
|
|
83
|
+
sliceId: opts.sliceId,
|
|
84
|
+
state: opts.state,
|
|
85
|
+
commitSha: opts.commitSha,
|
|
86
|
+
reason: opts.reason,
|
|
87
|
+
project: projectRoot(opts),
|
|
88
|
+
json: opts.json
|
|
89
|
+
});
|
|
90
|
+
if (!parsed.success)
|
|
91
|
+
return failResult(io, fail('checkpoint', 'INVALID_CHECKPOINT', parsed.error.message, {}), opts);
|
|
92
|
+
const jobRoot = resolveJobStateRoot(opts, opts.jobId);
|
|
93
|
+
const store = new JobStateStore(jobRoot.rootDir);
|
|
94
|
+
// D7: `--slice-id` accepts the canonical `slice-NNN` or the slice's label
|
|
95
|
+
// ("S1"); an id that matches no slice is rejected here, BEFORE any write,
|
|
96
|
+
// so progress.json is never touched by a checkpoint that matched nothing.
|
|
97
|
+
const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
|
|
98
|
+
if ('message' in slice) {
|
|
99
|
+
failCheckpointSliceNotFound(io, opts, parsed.data, slice);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const orch = new JobOrchestrator(store);
|
|
103
|
+
const outcome = await applyCheckpointState({
|
|
104
|
+
orch,
|
|
105
|
+
jobRoot,
|
|
106
|
+
opts,
|
|
107
|
+
data: parsed.data,
|
|
108
|
+
sliceId: slice.sliceId
|
|
109
|
+
});
|
|
110
|
+
printResult(io, ok('checkpoint', { sliceId: slice.sliceId, status: parsed.data.state, codegraph: outcome.codegraph }, outcome.codegraphWarning === null ? [] : [outcome.codegraphWarning]), asJson(opts));
|
|
111
|
+
}
|
|
112
|
+
async function runJobBlock(opts, io) {
|
|
113
|
+
const parsed = JobBlockInputSchema.safeParse({
|
|
114
|
+
jobId: opts.jobId,
|
|
115
|
+
sliceId: opts.sliceId,
|
|
116
|
+
reason: opts.reason,
|
|
117
|
+
project: projectRoot(opts),
|
|
118
|
+
json: opts.json
|
|
119
|
+
});
|
|
120
|
+
if (!parsed.success)
|
|
121
|
+
return failResult(io, fail('block', 'INVALID_BLOCK', parsed.error.message, {}), opts);
|
|
122
|
+
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
123
|
+
// D7 (same silent no-op as checkpoint): resolve label → sliceId, reject a
|
|
124
|
+
// miss before any write.
|
|
125
|
+
const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
|
|
126
|
+
if ('message' in slice) {
|
|
127
|
+
return failResult(io, fail('block', 'SLICE_NOT_FOUND', slice.message, {
|
|
128
|
+
jobId: parsed.data.jobId,
|
|
129
|
+
sliceId: parsed.data.sliceId,
|
|
130
|
+
validSliceIds: slice.validSliceIds
|
|
131
|
+
}, SLICE_NOT_FOUND_HINTS), opts);
|
|
132
|
+
}
|
|
133
|
+
const orch = new JobOrchestrator(store);
|
|
134
|
+
await orch.blockSlice({ ...parsed.data, sliceId: slice.sliceId });
|
|
135
|
+
printResult(io, ok('block', { blocked: slice.sliceId, reason: parsed.data.reason }), asJson(opts));
|
|
136
|
+
}
|
|
137
|
+
// M3.2: wire the remaining 5 subcommand slots — block, checkpoint, continue,
|
|
138
|
+
// handoff, resume. `block` is registered first here, before `continue` /
|
|
139
|
+
// `resume` / `progress` / `handoff`, to keep the `job` help order stable.
|
|
140
|
+
export function registerJobCheckpointCommands(job, io) {
|
|
141
|
+
addJsonOption(job
|
|
142
|
+
.command('checkpoint')
|
|
143
|
+
.requiredOption('--job-id <jid>')
|
|
144
|
+
.requiredOption('--slice-id <rid>')
|
|
145
|
+
.requiredOption('--state <done|failed|skipped>')
|
|
146
|
+
.option('--commit-sha <sha>')
|
|
147
|
+
.option('--reason <text>')
|
|
148
|
+
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
149
|
+
.option('--project <repo>')).action((opts) => runJobCheckpoint(opts, io));
|
|
150
|
+
addJsonOption(job
|
|
151
|
+
.command('block')
|
|
152
|
+
.requiredOption('--job-id <jid>')
|
|
153
|
+
.requiredOption('--slice-id <rid>')
|
|
154
|
+
.requiredOption('--reason <text>')
|
|
155
|
+
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
156
|
+
.option('--project <repo>')).action((opts) => runJobBlock(opts, io));
|
|
157
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ResultEnvelope } from 'peaks-loop-shared/result';
|
|
2
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
3
|
+
import type { JobStateStore } from '../../services/job/job-state-store.js';
|
|
4
|
+
export declare function asJson(opts: JobJsonOpts): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* process exit non-zero.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS. `printResult` (`src/cli/cli-helpers.ts`) renders a failed
|
|
9
|
+
* envelope — `CODE: message` + `nextActions` on stderr — but it does NOT set
|
|
10
|
+
* `process.exitCode`. Measured with the real CLI before this fix:
|
|
11
|
+
* `peaks job block --job-id j1 --slice-id nope --reason why` printed
|
|
12
|
+
* `SLICE_NOT_FOUND: …` on stderr and exited **0**, so CI and every script
|
|
13
|
+
* wrapping `peaks job` read the failure as success. Seven of this file's nine
|
|
14
|
+
* failure-reporting sites had that gap; only `job progress` set the code, and
|
|
15
|
+
* that was the file's one accidental precedent rather than a rule.
|
|
16
|
+
*
|
|
17
|
+
* Every `fail(...)` envelope in this file now goes through here, so "reported a
|
|
18
|
+
* failure" and "exited non-zero" cannot drift apart again. The helper takes the
|
|
19
|
+
* Commander `opts` object and calls `asJson(opts)` itself — it must never be
|
|
20
|
+
* handed a bare `opts.json`-less boolean, and it must never pass `opts` to
|
|
21
|
+
* `printResult` (see the `asJson` comment above for that bug's history).
|
|
22
|
+
*
|
|
23
|
+
* DELIBERATELY NOT APPLIED TO THE ADVISORY PATHS. Three sites in this file
|
|
24
|
+
* report a non-fatal outcome through an `ok(...)` envelope and MUST keep
|
|
25
|
+
* exiting 0: the two `emitJobEvent` best-effort catches (`job init`,
|
|
26
|
+
* `job status`) and the post-slice codegraph refresh in `job checkpoint`, whose
|
|
27
|
+
* stated design is "a refresh failure must never fail the checkpoint". Those
|
|
28
|
+
* are annotated in place; `tests/unit/cli/job-exit-code.test.ts` pins both
|
|
29
|
+
* directions so a future blanket "every warning exits 1" edit turns red.
|
|
30
|
+
*/
|
|
31
|
+
export declare function failResult(io: ProgramIO, result: ResultEnvelope<unknown>, opts: JobJsonOpts): void;
|
|
32
|
+
export declare function projectRoot(opts: JobProjectOpts): string;
|
|
33
|
+
/**
|
|
34
|
+
* D7: slices are keyed `slice-NNN`, but `peaks job init --slice-list "S1,…"`
|
|
35
|
+
* takes labels, so the natural string to pass back to `--slice-id` is that same
|
|
36
|
+
* label. Resolve an exact sliceId OR an exact label to the canonical sliceId;
|
|
37
|
+
* anything else is a hard error listing the valid ids, so a mistyped
|
|
38
|
+
* `slice-04` can never be accepted as a silent no-op that leaves the slice
|
|
39
|
+
* pending.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveSliceId(store: JobStateStore, jobId: string, sliceId: string): {
|
|
42
|
+
sliceId: string;
|
|
43
|
+
} | {
|
|
44
|
+
message: string;
|
|
45
|
+
validSliceIds: string[];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* S10 (2026-09-20) — the option shapes the `job` subcommands read.
|
|
49
|
+
*
|
|
50
|
+
* WHY. Commander types an action's `opts` as `any`, so every `opts.<field>`
|
|
51
|
+
* access in this file was an `any` access: 109 `no-unsafe-*` findings, the
|
|
52
|
+
* largest single TS root in the S10 census. The root is the *entrance* — the
|
|
53
|
+
* untyped `opts` parameter — not the 109 sites, so the fix is to declare it.
|
|
54
|
+
*
|
|
55
|
+
* Each interface names exactly the options its own command READS, plus `--json`
|
|
56
|
+
* (added by `addJsonOption`). So a field absent here is a field that command
|
|
57
|
+
* does not read, and reading it is now a compile error instead of an `any`
|
|
58
|
+
* access. (A command may declare more than it reads — `job status` declares
|
|
59
|
+
* `--show-cost` and never looks at it; that option is deliberately not listed.)
|
|
60
|
+
* `readonly` is honest: these are read-only inputs; Commander owns the object.
|
|
61
|
+
*
|
|
62
|
+
* The four helpers above (`asJson`, `failResult`, `projectRoot`,
|
|
63
|
+
* `resolveJobStateRoot`) take the narrowest structural type each one actually
|
|
64
|
+
* reads, so all eleven action callbacks remain assignable to them.
|
|
65
|
+
*/
|
|
66
|
+
export interface JobJsonOpts {
|
|
67
|
+
readonly json?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface JobProjectOpts {
|
|
70
|
+
readonly project?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface JobRootOpts extends JobProjectOpts {
|
|
73
|
+
readonly sessionId?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface JobInitOpts extends JobRootOpts, JobJsonOpts {
|
|
76
|
+
readonly jobId: string;
|
|
77
|
+
readonly sliceList: string;
|
|
78
|
+
readonly parallelismHint?: string;
|
|
79
|
+
readonly exitPolicy?: string;
|
|
80
|
+
readonly mainLoopStrategy?: string;
|
|
81
|
+
readonly rotateEvery?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface JobStatusOpts extends JobRootOpts, JobJsonOpts {
|
|
84
|
+
readonly jobId: string;
|
|
85
|
+
readonly watch?: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface JobIdOnlyOpts extends JobRootOpts, JobJsonOpts {
|
|
88
|
+
readonly jobId: string;
|
|
89
|
+
}
|
|
90
|
+
export interface JobSubagentCleanupOpts extends JobIdOnlyOpts {
|
|
91
|
+
readonly batchId: string;
|
|
92
|
+
readonly force?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface JobCheckpointOpts extends JobIdOnlyOpts {
|
|
95
|
+
readonly sliceId: string;
|
|
96
|
+
readonly state: string;
|
|
97
|
+
readonly commitSha?: string;
|
|
98
|
+
readonly reason?: string;
|
|
99
|
+
}
|
|
100
|
+
export interface JobBlockOpts extends JobIdOnlyOpts {
|
|
101
|
+
readonly sliceId: string;
|
|
102
|
+
readonly reason: string;
|
|
103
|
+
}
|
|
104
|
+
export interface JobProgressOpts extends JobIdOnlyOpts {
|
|
105
|
+
readonly allowMissing?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface JobCostCheckOpts extends JobRootOpts, JobJsonOpts {
|
|
108
|
+
readonly reviewFile: string;
|
|
109
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { printResult } from '../cli-helpers.js';
|
|
2
|
+
// `printResult`'s third parameter is `asJson: boolean`. Each `job` subcommand
|
|
3
|
+
// registers `--json` (see the `addJsonOption` calls below), so the flag to pass
|
|
4
|
+
// it is `opts.json` — NEVER the whole options object. Commander types an
|
|
5
|
+
// action's `opts` as `any`, so `printResult(io, envelope, opts)` type-checks
|
|
6
|
+
// and is ALWAYS truthy: it forces the envelope branch and makes the
|
|
7
|
+
// `warning: ` / `next: ` human rendering in `cli-helpers.ts` unreachable. That
|
|
8
|
+
// is how the A2 codegraph-refresh warning ended up inside the JSON stdout
|
|
9
|
+
// envelope instead of on stderr as `warning: `. `job progress` always passed
|
|
10
|
+
// `opts.json`; the rest of this file did not, and E1 (2026-09-17) brought them
|
|
11
|
+
// in line. Do not reintroduce the object form.
|
|
12
|
+
//
|
|
13
|
+
// S10 (2026-09-20): the parameter is now `JobJsonOpts` rather than `any`. The
|
|
14
|
+
// gate above is about which VALUE is passed, not about the parameter's type —
|
|
15
|
+
// declaring the one field this function reads is what stops every `opts.<x>`
|
|
16
|
+
// call site in the file from being an `any` access. See the option-interface
|
|
17
|
+
// block above `registerJobCommands`.
|
|
18
|
+
export function asJson(opts) {
|
|
19
|
+
return opts.json === true;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* process exit non-zero.
|
|
23
|
+
*
|
|
24
|
+
* WHY THIS EXISTS. `printResult` (`src/cli/cli-helpers.ts`) renders a failed
|
|
25
|
+
* envelope — `CODE: message` + `nextActions` on stderr — but it does NOT set
|
|
26
|
+
* `process.exitCode`. Measured with the real CLI before this fix:
|
|
27
|
+
* `peaks job block --job-id j1 --slice-id nope --reason why` printed
|
|
28
|
+
* `SLICE_NOT_FOUND: …` on stderr and exited **0**, so CI and every script
|
|
29
|
+
* wrapping `peaks job` read the failure as success. Seven of this file's nine
|
|
30
|
+
* failure-reporting sites had that gap; only `job progress` set the code, and
|
|
31
|
+
* that was the file's one accidental precedent rather than a rule.
|
|
32
|
+
*
|
|
33
|
+
* Every `fail(...)` envelope in this file now goes through here, so "reported a
|
|
34
|
+
* failure" and "exited non-zero" cannot drift apart again. The helper takes the
|
|
35
|
+
* Commander `opts` object and calls `asJson(opts)` itself — it must never be
|
|
36
|
+
* handed a bare `opts.json`-less boolean, and it must never pass `opts` to
|
|
37
|
+
* `printResult` (see the `asJson` comment above for that bug's history).
|
|
38
|
+
*
|
|
39
|
+
* DELIBERATELY NOT APPLIED TO THE ADVISORY PATHS. Three sites in this file
|
|
40
|
+
* report a non-fatal outcome through an `ok(...)` envelope and MUST keep
|
|
41
|
+
* exiting 0: the two `emitJobEvent` best-effort catches (`job init`,
|
|
42
|
+
* `job status`) and the post-slice codegraph refresh in `job checkpoint`, whose
|
|
43
|
+
* stated design is "a refresh failure must never fail the checkpoint". Those
|
|
44
|
+
* are annotated in place; `tests/unit/cli/job-exit-code.test.ts` pins both
|
|
45
|
+
* directions so a future blanket "every warning exits 1" edit turns red.
|
|
46
|
+
*/
|
|
47
|
+
export function failResult(io, result, opts) {
|
|
48
|
+
printResult(io, result, asJson(opts));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
export function projectRoot(opts) {
|
|
52
|
+
// Reuse the workspace root resolver from peaks CLI; for now, CWD as a safe placeholder.
|
|
53
|
+
return opts.project ?? process.cwd();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* D7: slices are keyed `slice-NNN`, but `peaks job init --slice-list "S1,…"`
|
|
57
|
+
* takes labels, so the natural string to pass back to `--slice-id` is that same
|
|
58
|
+
* label. Resolve an exact sliceId OR an exact label to the canonical sliceId;
|
|
59
|
+
* anything else is a hard error listing the valid ids, so a mistyped
|
|
60
|
+
* `slice-04` can never be accepted as a silent no-op that leaves the slice
|
|
61
|
+
* pending.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveSliceId(store, jobId, sliceId) {
|
|
64
|
+
const slices = store.load(jobId).slices;
|
|
65
|
+
const hit = slices.find((sl) => sl.sliceId === sliceId || sl.label === sliceId);
|
|
66
|
+
if (hit)
|
|
67
|
+
return { sliceId: hit.sliceId };
|
|
68
|
+
return {
|
|
69
|
+
message: `no slice "${sliceId}" in job ${jobId}; valid ids: ${slices.map((sl) => `${sl.sliceId} (${sl.label})`).join(', ')}`,
|
|
70
|
+
validSliceIds: slices.map((sl) => sl.sliceId)
|
|
71
|
+
};
|
|
72
|
+
}
|