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
|
@@ -1,295 +1,38 @@
|
|
|
1
1
|
// src/cli/commands/job-commands.ts
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
//
|
|
3
|
+
// The `peaks job` registrar. Split into sibling modules by responsibility
|
|
4
|
+
// option shapes + shared helpers, session-root
|
|
5
|
+
// resolution, init/status, checkpoint/block, run (continue/resume/progress/
|
|
6
|
+
// handoff) and karpathy-cost-check. This file wires them up in the registration
|
|
7
|
+
// order the CLI surface dump pins.
|
|
8
|
+
//
|
|
9
|
+
// `job rotate-now` and `job subagent-cleanup` stay registered HERE: their
|
|
10
|
+
// actions carry the three contract-less `async` arrows pinned by
|
|
11
|
+
// `tests/unit/standards/gratuitous-async-guard.test.ts`, which keys its 14-site
|
|
12
|
+
// set on `file | declaration text`.
|
|
4
13
|
import { Command } from 'commander';
|
|
5
|
-
import {
|
|
14
|
+
import { ok } from 'peaks-loop-shared/result';
|
|
6
15
|
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
7
|
-
import { isUnsafePathInput } from '../../shared/path-safety.js';
|
|
8
16
|
import { JobStateStore } from '../../services/job/job-state-store.js';
|
|
9
|
-
import { JobOrchestrator } from '../../services/job/job-orchestrator.js';
|
|
10
|
-
import { writeJobProgress, readJobProgress, tryReadJobProgress, describeNextSlice } from '../../services/job/job-progress-store.js';
|
|
11
17
|
import { JobRotation } from '../../services/job/job-rotation.js';
|
|
12
18
|
import { SubAgentJobWrapper } from '../../services/job/subagent-job-wrapper.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// envelope instead of on stderr as `warning: `. `job progress` always passed
|
|
27
|
-
// `opts.json`; the rest of this file did not, and E1 (2026-09-17) brought them
|
|
28
|
-
// in line. Do not reintroduce the object form.
|
|
29
|
-
//
|
|
30
|
-
// S10 (2026-09-20): the parameter is now `JobJsonOpts` rather than `any`. The
|
|
31
|
-
// gate above is about which VALUE is passed, not about the parameter's type —
|
|
32
|
-
// declaring the one field this function reads is what stops every `opts.<x>`
|
|
33
|
-
// call site in the file from being an `any` access. See the option-interface
|
|
34
|
-
// block above `registerJobCommands`.
|
|
35
|
-
export function asJson(opts) {
|
|
36
|
-
return opts.json === true;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* process exit non-zero.
|
|
40
|
-
*
|
|
41
|
-
* WHY THIS EXISTS. `printResult` (`src/cli/cli-helpers.ts`) renders a failed
|
|
42
|
-
* envelope — `CODE: message` + `nextActions` on stderr — but it does NOT set
|
|
43
|
-
* `process.exitCode`. Measured with the real CLI before this fix:
|
|
44
|
-
* `peaks job block --job-id j1 --slice-id nope --reason why` printed
|
|
45
|
-
* `SLICE_NOT_FOUND: …` on stderr and exited **0**, so CI and every script
|
|
46
|
-
* wrapping `peaks job` read the failure as success. Seven of this file's nine
|
|
47
|
-
* failure-reporting sites had that gap; only `job progress` set the code, and
|
|
48
|
-
* that was the file's one accidental precedent rather than a rule.
|
|
49
|
-
*
|
|
50
|
-
* Every `fail(...)` envelope in this file now goes through here, so "reported a
|
|
51
|
-
* failure" and "exited non-zero" cannot drift apart again. The helper takes the
|
|
52
|
-
* Commander `opts` object and calls `asJson(opts)` itself — it must never be
|
|
53
|
-
* handed a bare `opts.json`-less boolean, and it must never pass `opts` to
|
|
54
|
-
* `printResult` (see the `asJson` comment above for that bug's history).
|
|
55
|
-
*
|
|
56
|
-
* DELIBERATELY NOT APPLIED TO THE ADVISORY PATHS. Three sites in this file
|
|
57
|
-
* report a non-fatal outcome through an `ok(...)` envelope and MUST keep
|
|
58
|
-
* exiting 0: the two `emitJobEvent` best-effort catches (`job init`,
|
|
59
|
-
* `job status`) and the post-slice codegraph refresh in `job checkpoint`, whose
|
|
60
|
-
* stated design is "a refresh failure must never fail the checkpoint". Those
|
|
61
|
-
* are annotated in place; `tests/unit/cli/job-exit-code.test.ts` pins both
|
|
62
|
-
* directions so a future blanket "every warning exits 1" edit turns red.
|
|
63
|
-
*/
|
|
64
|
-
function failResult(io, result, opts) {
|
|
65
|
-
printResult(io, result, asJson(opts));
|
|
66
|
-
process.exitCode = 1;
|
|
67
|
-
}
|
|
68
|
-
function projectRoot(opts) {
|
|
69
|
-
// Reuse the workspace root resolver from peaks CLI; for now, CWD as a safe placeholder.
|
|
70
|
-
return opts.project ?? process.cwd();
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* D6: every job subcommand resolves a job root, so every one of them must be
|
|
74
|
-
* able to name the session that holds the job. Same precedence as the rest of
|
|
75
|
-
* the CLI (`peaks sub-agent dispatch`, `peaks web *`, `peaks share *`), except
|
|
76
|
-
* that job state has no "unknown-sid" location to land in — an unresolvable
|
|
77
|
-
* session is an error, not a silent fallback.
|
|
78
|
-
*/
|
|
79
|
-
export const SESSION_ID_HELP = 'session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback: NO_ACTIVE_SESSION error)';
|
|
80
|
-
/**
|
|
81
|
-
* The session (a direct child of `<project>/.peaks/_runtime/`) that holds
|
|
82
|
-
* `jobId`, or null. Only used to explain a miss: a job that lives in another
|
|
83
|
-
* session must be reported by name so the caller can re-run with --session-id.
|
|
84
|
-
*/
|
|
85
|
-
function findSessionHoldingJob(project, jobId) {
|
|
86
|
-
const runtimeDir = join(project, '.peaks', '_runtime');
|
|
87
|
-
if (!existsSync(runtimeDir))
|
|
88
|
-
return null;
|
|
89
|
-
for (const entry of readdirSync(runtimeDir, { withFileTypes: true })) {
|
|
90
|
-
if (entry.isDirectory() &&
|
|
91
|
-
existsSync(join(runtimeDir, entry.name, 'job', jobId, 'state.json'))) {
|
|
92
|
-
return entry.name;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Resolves the on-disk root for Job state files.
|
|
99
|
-
*
|
|
100
|
-
* Per spec §3.3 + §4.5 (2.7.1 single-scope-axis layout), Job state lives at:
|
|
101
|
-
* `<projectRoot>/.peaks/_runtime/<sessionId>/job/<jobId>/state.json`
|
|
102
|
-
*
|
|
103
|
-
* The `JobStateStore` itself only knows its `rootDir` + `jobId` and joins them. We
|
|
104
|
-
* compute the canonical root here (per-call) so the store can stay layout-agnostic.
|
|
105
|
-
*
|
|
106
|
-
* Resolution order (D6 — a job must stay addressable while the single per-project
|
|
107
|
-
* `.peaks/_runtime/session.json` binding points at another session):
|
|
108
|
-
* 1. `--session-id` flag (explicit override)
|
|
109
|
-
* 2. `PEAKS_SESSION_ID` env var
|
|
110
|
-
* 3. `getCurrentSessionId(project)` — this caller's session binding, falling back
|
|
111
|
-
* to `.peaks/_runtime/session.json` when no caller binding is resolvable
|
|
112
|
-
* 4. Error (NO_ACTIVE_SESSION) — must never silently fall back to a random uuid
|
|
113
|
-
*
|
|
114
|
-
* When `jobId` is passed and it is absent from the resolved session, the thrown
|
|
115
|
-
* error names the session that does hold it (if any), instead of leaving the
|
|
116
|
-
* caller with a bare "no state for <job> at <other-sid>" path.
|
|
117
|
-
*/
|
|
118
|
-
export function resolveJobStateRoot(opts, jobId) {
|
|
119
|
-
const project = projectRoot(opts);
|
|
120
|
-
const sessionId = opts.sessionId ?? process.env.PEAKS_SESSION_ID ?? getCurrentSessionId(project);
|
|
121
|
-
if (!sessionId) {
|
|
122
|
-
throw new Error('NO_ACTIVE_SESSION: peaks job requires --session-id or an active peaks-code session via peaks workspace init');
|
|
123
|
-
}
|
|
124
|
-
// Sid axis — the sibling of the jobId guard in `JobStateStore.jobDir`. A
|
|
125
|
-
// caller-supplied `--session-id` reaches this join unmodified, so a
|
|
126
|
-
// traversal value lands `job/<id>/state.json` outside every project root
|
|
127
|
-
// while the envelope still reads `ok: true`.
|
|
128
|
-
if (isUnsafePathInput(sessionId)) {
|
|
129
|
-
throw new Error(`Invalid session id: ${sessionId} (must be a single path segment)`);
|
|
130
|
-
}
|
|
131
|
-
const rootDir = join(project, '.peaks', '_runtime', sessionId, 'job');
|
|
132
|
-
if (jobId && !existsSync(join(rootDir, jobId, 'state.json'))) {
|
|
133
|
-
const other = findSessionHoldingJob(project, jobId);
|
|
134
|
-
throw new Error(other
|
|
135
|
-
? `JOB_NOT_IN_SESSION: no job "${jobId}" in session "${sessionId}"; it lives in session "${other}" — re-run with --session-id ${other}`
|
|
136
|
-
: `JOB_NOT_IN_SESSION: no job "${jobId}" in session "${sessionId}" (and no other session under .peaks/_runtime/ has it)`);
|
|
137
|
-
}
|
|
138
|
-
return { rootDir, sessionId, projectRoot: project };
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* D7: slices are keyed `slice-NNN`, but `peaks job init --slice-list "S1,…"`
|
|
142
|
-
* takes labels, so the natural string to pass back to `--slice-id` is that same
|
|
143
|
-
* label. Resolve an exact sliceId OR an exact label to the canonical sliceId;
|
|
144
|
-
* anything else is a hard error listing the valid ids, so a mistyped
|
|
145
|
-
* `slice-04` can never be accepted as a silent no-op that leaves the slice
|
|
146
|
-
* pending.
|
|
147
|
-
*/
|
|
148
|
-
function resolveSliceId(store, jobId, sliceId) {
|
|
149
|
-
const slices = store.load(jobId).slices;
|
|
150
|
-
const hit = slices.find((sl) => sl.sliceId === sliceId || sl.label === sliceId);
|
|
151
|
-
if (hit)
|
|
152
|
-
return { sliceId: hit.sliceId };
|
|
153
|
-
return {
|
|
154
|
-
message: `no slice "${sliceId}" in job ${jobId}; valid ids: ${slices.map((sl) => `${sl.sliceId} (${sl.label})`).join(', ')}`,
|
|
155
|
-
validSliceIds: slices.map((sl) => sl.sliceId)
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
export function registerJobCommands(program, io = {
|
|
159
|
-
stdout: (t) => process.stdout.write(t),
|
|
160
|
-
stderr: (t) => process.stderr.write(t)
|
|
161
|
-
}) {
|
|
162
|
-
const job = new Command('job').description('Drive long multi-slice work as one Job (peaks-code Step 0.8+)');
|
|
163
|
-
job
|
|
164
|
-
.command('init')
|
|
165
|
-
.requiredOption('--job-id <jid>')
|
|
166
|
-
.requiredOption('--slice-list <list>')
|
|
167
|
-
.option('--parallelism-hint <serial|llm-decides>', 'llm-decides')
|
|
168
|
-
.option('--exit-policy <strict|best-effort>', 'strict')
|
|
169
|
-
.option('--main-loop-strategy <single|rotating>', 'rotating')
|
|
170
|
-
.option('--rotate-every <n>', 'rotate every N slices (rotating mode)', '3')
|
|
171
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
172
|
-
.option('--project <repo>')
|
|
173
|
-
.action((opts) => {
|
|
174
|
-
const project = projectRoot(opts);
|
|
175
|
-
// Resolve sessionId: explicit flag > PEAKS_SESSION_ID > caller-first session binding
|
|
176
|
-
// (this caller's binding, else the project-global session.json) > FAIL.
|
|
177
|
-
// Per spec §3.3, Job state lives at .peaks/_runtime/<sessionId>/job/<jobId>/state.json —
|
|
178
|
-
// a random UUID would scatter state across dirs and break resume/auto-compact.
|
|
179
|
-
let sessionId = opts.sessionId ?? process.env.PEAKS_SESSION_ID ?? getCurrentSessionId(project);
|
|
180
|
-
if (!sessionId) {
|
|
181
|
-
return failResult(io, fail('init', 'NO_ACTIVE_SESSION', 'peaks job init requires --session-id (or an active peaks-code session via peaks workspace init)', { project }, [
|
|
182
|
-
'Re-run with --session-id <sid>',
|
|
183
|
-
'Or run `peaks workspace init` to create a session first'
|
|
184
|
-
]), opts);
|
|
185
|
-
}
|
|
186
|
-
const parsed = JobInitInputSchema.safeParse({
|
|
187
|
-
jobId: opts.jobId,
|
|
188
|
-
sessionId,
|
|
189
|
-
sliceList: opts.sliceList
|
|
190
|
-
.split(',')
|
|
191
|
-
.map((s) => s.trim())
|
|
192
|
-
.filter(Boolean),
|
|
193
|
-
parallelismHint: opts.parallelismHint,
|
|
194
|
-
exitPolicy: opts.exitPolicy,
|
|
195
|
-
mainLoopStrategy: opts.mainLoopStrategy,
|
|
196
|
-
rotateEvery: Number(opts.rotateEvery),
|
|
197
|
-
project,
|
|
198
|
-
json: opts.json
|
|
199
|
-
});
|
|
200
|
-
if (!parsed.success)
|
|
201
|
-
return failResult(io, fail('init', 'INVALID_INIT', parsed.error.message, {}), opts);
|
|
202
|
-
const jobRoot = resolveJobStateRoot(opts);
|
|
203
|
-
const store = new JobStateStore(jobRoot.rootDir);
|
|
204
|
-
const orch = new JobOrchestrator(store);
|
|
205
|
-
const state = orch.init({
|
|
206
|
-
jobId: parsed.data.jobId,
|
|
207
|
-
sessionId: parsed.data.sessionId,
|
|
208
|
-
sliceList: parsed.data.sliceList,
|
|
209
|
-
parallelismHint: parsed.data.parallelismHint,
|
|
210
|
-
exitPolicy: parsed.data.exitPolicy,
|
|
211
|
-
mainLoopStrategy: parsed.data.mainLoopStrategy,
|
|
212
|
-
rotateEvery: parsed.data.rotateEvery
|
|
213
|
-
});
|
|
214
|
-
try {
|
|
215
|
-
emitJobEvent({
|
|
216
|
-
kind: 'job-started',
|
|
217
|
-
jobId: state.jobId,
|
|
218
|
-
total: state.slices.length,
|
|
219
|
-
strategy: state.mainLoopStrategy
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
catch (e) {
|
|
223
|
-
// ADVISORY (F1, 2026-09-17) — deliberately exits 0. Event emission is a
|
|
224
|
-
// telemetry side effect; a failed emit does not mean `job init` failed
|
|
225
|
-
// (the state file was already written above). This catch must NOT be
|
|
226
|
-
// routed through `failResult`. Pinned by the "advisory stays 0" control
|
|
227
|
-
// in tests/unit/cli/job-exit-code.test.ts.
|
|
228
|
-
void e;
|
|
229
|
-
}
|
|
230
|
-
printResult(io, ok('init', {
|
|
231
|
-
jobId: state.jobId,
|
|
232
|
-
sliceCount: state.slices.length,
|
|
233
|
-
statePath: `${jobRoot.rootDir}/${state.jobId}/state.json`
|
|
234
|
-
}), asJson(opts));
|
|
235
|
-
});
|
|
236
|
-
addJsonOption(job.commands.find((c) => c.name() === 'init'));
|
|
237
|
-
job
|
|
238
|
-
.command('status')
|
|
239
|
-
.requiredOption('--job-id <jid>')
|
|
240
|
-
.option('--watch', 'poll every 3s')
|
|
241
|
-
.option('--show-cost', 'overlay cost from peaks budget')
|
|
242
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
243
|
-
.option('--project <repo>')
|
|
244
|
-
.action((opts) => {
|
|
245
|
-
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
246
|
-
const orch = new JobOrchestrator(store);
|
|
247
|
-
const s = orch.status(opts.jobId);
|
|
248
|
-
if (opts.watch) {
|
|
249
|
-
const draw = () => {
|
|
250
|
-
const bar = `[${'='.repeat(s.done)}${' '.repeat(s.total - s.done)}]`;
|
|
251
|
-
process.stdout.write(`\rjob ${opts.jobId}: ${bar} ${s.done}/${s.total}${s.currentSlice ? ` next=${s.currentSlice}` : ''} `);
|
|
252
|
-
};
|
|
253
|
-
draw();
|
|
254
|
-
const iv = setInterval(() => {
|
|
255
|
-
const u = orch.status(opts.jobId);
|
|
256
|
-
Object.assign(s, u);
|
|
257
|
-
draw();
|
|
258
|
-
if (u.done + u.failed + u.skipped + u.blocked >= u.total)
|
|
259
|
-
clearInterval(iv);
|
|
260
|
-
}, 3000);
|
|
261
|
-
process.on('SIGINT', () => {
|
|
262
|
-
clearInterval(iv);
|
|
263
|
-
process.stdout.write('\n');
|
|
264
|
-
process.exit(0);
|
|
265
|
-
});
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
try {
|
|
269
|
-
emitJobEvent({
|
|
270
|
-
kind: 'job-progress',
|
|
271
|
-
jobId: opts.jobId,
|
|
272
|
-
done: s.done,
|
|
273
|
-
total: s.total,
|
|
274
|
-
...(s.currentSlice ? { currentSlice: s.currentSlice } : {})
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
catch (e) {
|
|
278
|
-
// ADVISORY (F1, 2026-09-17) — deliberately exits 0. The status itself
|
|
279
|
-
// was already read successfully; the emit is telemetry. Same rule as
|
|
280
|
-
// the `job init` catch above: do NOT route through `failResult`.
|
|
281
|
-
void e;
|
|
282
|
-
}
|
|
283
|
-
printResult(io, ok('status', s), asJson(opts));
|
|
284
|
-
});
|
|
285
|
-
addJsonOption(job.commands.find((c) => c.name() === 'status'));
|
|
286
|
-
// M4.2: wire rotate-now to JobRotation (session-rotate callbacks are stubs pending M6.5 batch-fix).
|
|
287
|
-
job
|
|
19
|
+
import { asJson } from './job-command-shared.js';
|
|
20
|
+
import { SESSION_ID_HELP, resolveJobStateRoot } from './job-state-root.js';
|
|
21
|
+
import { registerJobInitStatusCommands } from './job-init-status-commands.js';
|
|
22
|
+
import { registerJobCheckpointCommands } from './job-checkpoint-commands.js';
|
|
23
|
+
import { registerJobRunCommands } from './job-run-commands.js';
|
|
24
|
+
import { registerJobCostCheckCommand } from './job-cost-check-command.js';
|
|
25
|
+
// Published import path preserved: `job-add-slice-command.ts` imports these
|
|
26
|
+
// from `./job-commands.js`.
|
|
27
|
+
export { asJson, SESSION_ID_HELP, resolveJobStateRoot };
|
|
28
|
+
// M4.2: wire rotate-now to JobRotation (session-rotate callbacks are stubs
|
|
29
|
+
// pending M6.5 batch-fix).
|
|
30
|
+
function registerJobRotateNowCommand(job, io) {
|
|
31
|
+
addJsonOption(job
|
|
288
32
|
.command('rotate-now')
|
|
289
33
|
.requiredOption('--job-id <jid>')
|
|
290
34
|
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
291
|
-
.option('--project <repo>')
|
|
292
|
-
.action(async (opts) => {
|
|
35
|
+
.option('--project <repo>')).action(async (opts) => {
|
|
293
36
|
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
294
37
|
const rotation = new JobRotation(store, async (_jid) => {
|
|
295
38
|
/* delegate to peaks session rotate — implementation wired in M6.5 batch-fix */ return {
|
|
@@ -299,15 +42,15 @@ export function registerJobCommands(program, io = {
|
|
|
299
42
|
const r = await rotation.rotateNow(opts.jobId);
|
|
300
43
|
printResult(io, ok('rotate-now', r), asJson(opts));
|
|
301
44
|
});
|
|
302
|
-
|
|
303
|
-
|
|
45
|
+
}
|
|
46
|
+
function registerJobSubagentCleanupCommand(job, io) {
|
|
47
|
+
addJsonOption(job
|
|
304
48
|
.command('subagent-cleanup')
|
|
305
49
|
.requiredOption('--job-id <jid>')
|
|
306
50
|
.requiredOption('--batch-id <bid>')
|
|
307
51
|
.option('--force')
|
|
308
52
|
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
309
|
-
.option('--project <repo>')
|
|
310
|
-
.action(async (opts) => {
|
|
53
|
+
.option('--project <repo>')).action(async (opts) => {
|
|
311
54
|
const wrapper = new SubAgentJobWrapper(new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir), async () => ({ batchId: opts.batchId }));
|
|
312
55
|
const r = await wrapper.cleanup({
|
|
313
56
|
jobId: opts.jobId,
|
|
@@ -316,270 +59,17 @@ export function registerJobCommands(program, io = {
|
|
|
316
59
|
});
|
|
317
60
|
printResult(io, ok('subagent-cleanup', r), asJson(opts));
|
|
318
61
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const parsed = JobCheckpointInputSchema.safeParse({
|
|
332
|
-
jobId: opts.jobId,
|
|
333
|
-
sliceId: opts.sliceId,
|
|
334
|
-
state: opts.state,
|
|
335
|
-
commitSha: opts.commitSha,
|
|
336
|
-
reason: opts.reason,
|
|
337
|
-
project: projectRoot(opts),
|
|
338
|
-
json: opts.json
|
|
339
|
-
});
|
|
340
|
-
if (!parsed.success)
|
|
341
|
-
return failResult(io, fail('checkpoint', 'INVALID_CHECKPOINT', parsed.error.message, {}), opts);
|
|
342
|
-
const jobRoot = resolveJobStateRoot(opts, opts.jobId);
|
|
343
|
-
const store = new JobStateStore(jobRoot.rootDir);
|
|
344
|
-
// D7: `--slice-id` accepts the canonical `slice-NNN` or the slice's label
|
|
345
|
-
// ("S1"); an id that matches no slice is rejected here, BEFORE any write,
|
|
346
|
-
// so progress.json is never touched by a checkpoint that matched nothing.
|
|
347
|
-
const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
|
|
348
|
-
if ('message' in slice) {
|
|
349
|
-
return failResult(io, fail('checkpoint', 'SLICE_NOT_FOUND', slice.message, {
|
|
350
|
-
jobId: parsed.data.jobId,
|
|
351
|
-
sliceId: parsed.data.sliceId,
|
|
352
|
-
validSliceIds: slice.validSliceIds
|
|
353
|
-
}, ['Re-run with one of the valid slice ids']), opts);
|
|
354
|
-
}
|
|
355
|
-
const sliceId = slice.sliceId;
|
|
356
|
-
const orch = new JobOrchestrator(store);
|
|
357
|
-
// envelope carries a non-blocking `codegraph` result; null for
|
|
358
|
-
// failed/skipped (no slice-complete boundary).
|
|
359
|
-
let codegraph = null;
|
|
360
|
-
// A2 (2026-09-17): the human-visible half of the refresh outcome. null
|
|
361
|
-
// when the refresh succeeded or when no codegraph store was in use.
|
|
362
|
-
let codegraphWarning = null;
|
|
363
|
-
if (parsed.data.state === 'done') {
|
|
364
|
-
await orch.checkpointDone({
|
|
365
|
-
jobId: parsed.data.jobId,
|
|
366
|
-
sliceId,
|
|
367
|
-
...(parsed.data.commitSha ? { commitSha: parsed.data.commitSha } : {})
|
|
368
|
-
});
|
|
369
|
-
// v3.1.2: after each --state done, mirror slice progress to
|
|
370
|
-
// .peaks/_runtime/<sessionId>/job/<jid>/progress.json so the
|
|
371
|
-
// next LLM turn (or peaks code gate-step-08 hook) can read it.
|
|
372
|
-
const project = projectRoot(opts);
|
|
373
|
-
const sessId = jobRoot.sessionId;
|
|
374
|
-
const state = orch.status(parsed.data.jobId);
|
|
375
|
-
writeJobProgress(project, sessId, {
|
|
376
|
-
jobId: parsed.data.jobId,
|
|
377
|
-
done: state.done,
|
|
378
|
-
total: state.total,
|
|
379
|
-
currentSlice: state.currentSlice,
|
|
380
|
-
lastCommitSha: parsed.data.commitSha ?? null,
|
|
381
|
-
updatedAt: new Date().toISOString()
|
|
382
|
-
});
|
|
383
|
-
// Auto codegraph refresh at the slice-complete boundary. Best-effort:
|
|
384
|
-
// a refresh failure must never fail the checkpoint.
|
|
385
|
-
//
|
|
386
|
-
// A2 (2026-09-17): "must never fail the checkpoint" is not "must
|
|
387
|
-
// never be seen". A refresh that did not happen while a codegraph
|
|
388
|
-
// store IS in use is now a warning line (stderr, `warning: ` prefix,
|
|
389
|
-
// via printResult) naming the reason and the remedy. See
|
|
390
|
-
// `codegraphRefreshNotice` for why `no-codegraph-dir` stays silent.
|
|
391
|
-
//
|
|
392
|
-
// ADVISORY (F1, 2026-09-17) — deliberately exits 0, and the reach of
|
|
393
|
-
// that word is exactly here. The checkpoint itself SUCCEEDED (the slice
|
|
394
|
-
// flipped to done and progress.json was mirrored above); the refresh is
|
|
395
|
-
// a derived index, rebuildable on demand. So the outcome is reported
|
|
396
|
-
// through `ok(...)` + a `warning:` line, never through `failResult`,
|
|
397
|
-
// and `process.exitCode` is left alone even when the refresh throws.
|
|
398
|
-
// Raising it would turn an advisory rebuild into a build breaker for
|
|
399
|
-
// every CI that runs `peaks job checkpoint`. Pinned by the "advisory
|
|
400
|
-
// stays 0" control in tests/unit/cli/job-exit-code.test.ts.
|
|
401
|
-
try {
|
|
402
|
-
codegraph = await refreshCodegraphAfterSlice(project);
|
|
403
|
-
}
|
|
404
|
-
catch (e) {
|
|
405
|
-
codegraph = {
|
|
406
|
-
refreshed: false,
|
|
407
|
-
reason: 'unavailable',
|
|
408
|
-
note: `auto codegraph refresh failed: ${e instanceof Error ? e.message : String(e)}`
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
codegraphWarning = codegraphRefreshNotice(codegraph);
|
|
412
|
-
}
|
|
413
|
-
else if (parsed.data.state === 'skipped') {
|
|
414
|
-
await orch.checkpointSkipped({
|
|
415
|
-
jobId: parsed.data.jobId,
|
|
416
|
-
sliceId,
|
|
417
|
-
reason: parsed.data.reason
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
await orch.checkpointFailed({
|
|
422
|
-
jobId: parsed.data.jobId,
|
|
423
|
-
sliceId,
|
|
424
|
-
reason: parsed.data.reason
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
printResult(io, ok('checkpoint', { sliceId, status: parsed.data.state, codegraph }, codegraphWarning === null ? [] : [codegraphWarning]), asJson(opts));
|
|
428
|
-
});
|
|
429
|
-
addJsonOption(job.commands.find((c) => c.name() === 'checkpoint'));
|
|
430
|
-
job
|
|
431
|
-
.command('block')
|
|
432
|
-
.requiredOption('--job-id <jid>')
|
|
433
|
-
.requiredOption('--slice-id <rid>')
|
|
434
|
-
.requiredOption('--reason <text>')
|
|
435
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
436
|
-
.option('--project <repo>')
|
|
437
|
-
.action(async (opts) => {
|
|
438
|
-
const parsed = JobBlockInputSchema.safeParse({
|
|
439
|
-
jobId: opts.jobId,
|
|
440
|
-
sliceId: opts.sliceId,
|
|
441
|
-
reason: opts.reason,
|
|
442
|
-
project: projectRoot(opts),
|
|
443
|
-
json: opts.json
|
|
444
|
-
});
|
|
445
|
-
if (!parsed.success)
|
|
446
|
-
return failResult(io, fail('block', 'INVALID_BLOCK', parsed.error.message, {}), opts);
|
|
447
|
-
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
448
|
-
// D7 (same silent no-op as checkpoint): resolve label → sliceId, reject a
|
|
449
|
-
// miss before any write.
|
|
450
|
-
const slice = resolveSliceId(store, parsed.data.jobId, parsed.data.sliceId);
|
|
451
|
-
if ('message' in slice) {
|
|
452
|
-
return failResult(io, fail('block', 'SLICE_NOT_FOUND', slice.message, {
|
|
453
|
-
jobId: parsed.data.jobId,
|
|
454
|
-
sliceId: parsed.data.sliceId,
|
|
455
|
-
validSliceIds: slice.validSliceIds
|
|
456
|
-
}, ['Re-run with one of the valid slice ids']), opts);
|
|
457
|
-
}
|
|
458
|
-
const orch = new JobOrchestrator(store);
|
|
459
|
-
await orch.blockSlice({ ...parsed.data, sliceId: slice.sliceId });
|
|
460
|
-
printResult(io, ok('block', { blocked: slice.sliceId, reason: parsed.data.reason }), asJson(opts));
|
|
461
|
-
});
|
|
462
|
-
addJsonOption(job.commands.find((c) => c.name() === 'block'));
|
|
463
|
-
job
|
|
464
|
-
.command('continue')
|
|
465
|
-
.requiredOption('--job-id <jid>')
|
|
466
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
467
|
-
.option('--project <repo>')
|
|
468
|
-
.action((opts) => {
|
|
469
|
-
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
470
|
-
const orch = new JobOrchestrator(store);
|
|
471
|
-
const r = orch.continueNow(opts.jobId);
|
|
472
|
-
printResult(io, ok('continue', r), asJson(opts));
|
|
473
|
-
});
|
|
474
|
-
addJsonOption(job.commands.find((c) => c.name() === 'continue'));
|
|
475
|
-
job
|
|
476
|
-
.command('resume')
|
|
477
|
-
.requiredOption('--job-id <jid>')
|
|
478
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
479
|
-
.option('--project <repo>')
|
|
480
|
-
.action((opts) => {
|
|
481
|
-
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
482
|
-
const orch = new JobOrchestrator(store);
|
|
483
|
-
const s = orch.status(opts.jobId);
|
|
484
|
-
printResult(io, ok('resume', { resumed: opts.jobId, ...s }), asJson(opts));
|
|
485
|
-
});
|
|
486
|
-
addJsonOption(job.commands.find((c) => c.name() === 'resume'));
|
|
487
|
-
// v3.1.2: read the on-disk slice progress mirror written by `peaks job
|
|
488
|
-
// checkpoint --state done`; gate-step-08 and Step 0.7 (resume) read it too. The
|
|
489
|
-
// `next:` sentence comes from describeNextSlice, never re-derived from state.json.
|
|
490
|
-
job
|
|
491
|
-
.command('progress')
|
|
492
|
-
.description('v3.1.2: read the on-disk slice progress mirror (.peaks/_runtime/<sid>/job/<jid>/progress.json). ' +
|
|
493
|
-
'Returns { jobId, done, total, currentSlice, lastCommitSha, updatedAt }.')
|
|
494
|
-
.requiredOption('--job-id <jid>')
|
|
495
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
496
|
-
.option('--project <repo>')
|
|
497
|
-
// "return done=0/total=0 envelope instead of failing when progress.json is
|
|
498
|
-
// absent". No code path has ever returned such an envelope — the option was
|
|
499
|
-
// born in `d9a1a098` with `process.exitCode = 1` on the same branch — so it
|
|
500
|
-
// described a contract that never existed, and `--allow-missing` appeared to
|
|
501
|
-
// be broken. It is not: its job is to pick WHICH structured code reports the
|
|
502
|
-
// absence. The help text was the side that was wrong; it now says what the
|
|
503
|
-
// flag does. See `rd/requests/013-…-exit-code-root-cause.md` for why the
|
|
504
|
-
// alternative reading was rejected (the literal promise is also
|
|
505
|
-
// unimplementable without a new absent-vs-corrupt distinction:
|
|
506
|
-
// `tryReadJobProgress` returns `null` for a corrupt file too, so "done=0"
|
|
507
|
-
// there would report unreadable state as empty state).
|
|
508
|
-
.option('--allow-missing', 'report an absent progress.json as NO_PROGRESS (expected absence) rather than PROGRESS_READ_FAILED (read error); the command still exits non-zero')
|
|
509
|
-
.action((opts) => {
|
|
510
|
-
try {
|
|
511
|
-
const jobRoot = resolveJobStateRoot(opts, opts.jobId);
|
|
512
|
-
const sessId = jobRoot.sessionId;
|
|
513
|
-
const project = projectRoot(opts);
|
|
514
|
-
const progress = opts.allowMissing === true
|
|
515
|
-
? tryReadJobProgress(project, sessId, opts.jobId)
|
|
516
|
-
: readJobProgress(project, sessId, opts.jobId);
|
|
517
|
-
if (progress === null) {
|
|
518
|
-
// F1: this site already set `process.exitCode = 1` by hand; it is
|
|
519
|
-
// routed through `failResult` so all nine failure sites in this file
|
|
520
|
-
// share one rule instead of this one being the lone precedent.
|
|
521
|
-
failResult(io, fail('progress', 'NO_PROGRESS', `No progress.json for job ${opts.jobId} at .peaks/_runtime/${sessId}/job/${opts.jobId}/progress.json`, { jobId: opts.jobId, sessionId: sessId }, [
|
|
522
|
-
'Run `peaks job checkpoint --state done ...` at least once to seed progress.json.',
|
|
523
|
-
// return a zero-progress envelope." That line could only ever be
|
|
524
|
-
// printed when `--allow-missing` had ALREADY been passed — this
|
|
525
|
-
// `progress === null` branch is unreachable otherwise, because the
|
|
526
|
-
// no-flag path throws into the catch below and reports
|
|
527
|
-
// PROGRESS_READ_FAILED. So the CLI was advising the caller to pass
|
|
528
|
-
// the flag they had just passed, to obtain an envelope that does
|
|
529
|
-
// not exist. Replaced with the fact the caller actually needs.
|
|
530
|
-
'--allow-missing selects this NO_PROGRESS envelope over a PROGRESS_READ_FAILED read error; it does not change the exit code.'
|
|
531
|
-
]), opts);
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
printResult(io, ok('progress', progress, [], [`Next: ${describeNextSlice(progress, progress.jobId)}`]), asJson(opts));
|
|
535
|
-
}
|
|
536
|
-
catch (err) {
|
|
537
|
-
failResult(io, fail('progress', 'PROGRESS_READ_FAILED', err instanceof Error ? err.message : String(err), { jobId: opts.jobId }, ['Verify the job id and try again']), opts);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
addJsonOption(job.commands.find((c) => c.name() === 'progress'));
|
|
541
|
-
job
|
|
542
|
-
.command('handoff')
|
|
543
|
-
.requiredOption('--job-id <jid>')
|
|
544
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
545
|
-
.option('--project <repo>')
|
|
546
|
-
.action((opts) => {
|
|
547
|
-
const store = new JobStateStore(resolveJobStateRoot(opts, opts.jobId).rootDir);
|
|
548
|
-
const orch = new JobOrchestrator(store);
|
|
549
|
-
const s = orch.status(opts.jobId);
|
|
550
|
-
printResult(io, ok('handoff', { handoffFor: opts.jobId, ...s }), asJson(opts));
|
|
551
|
-
});
|
|
552
|
-
addJsonOption(job.commands.find((c) => c.name() === 'handoff'));
|
|
553
|
-
job
|
|
554
|
-
.command('karpathy-cost-check')
|
|
555
|
-
.description("Read the slice's rd/karpathy-review.md and decide whether to downgrade a block gateAction to warn (slice 2026-07-30-karpathy-cost-self-review).")
|
|
556
|
-
.requiredOption('--review-file <path>', 'path to rd/karpathy-review.md (or its .json sibling if the file is JSON)')
|
|
557
|
-
.option('--project <repo>')
|
|
558
|
-
.option('--session-id <sid>', SESSION_ID_HELP)
|
|
559
|
-
.action((opts) => {
|
|
560
|
-
const project = projectRoot(opts);
|
|
561
|
-
const sessionId = opts.sessionId ?? process.env.PEAKS_SESSION_ID ?? getCurrentSessionId(project);
|
|
562
|
-
if (!sessionId) {
|
|
563
|
-
return failResult(io, fail('karpathy-cost-check', 'NO_ACTIVE_SESSION', 'karpathy-cost-check requires --session-id (or an active peaks-code session)', { project }, [
|
|
564
|
-
'Re-run with --session-id <sid>',
|
|
565
|
-
'Or run `peaks workspace init` to create a session first'
|
|
566
|
-
]), opts);
|
|
567
|
-
}
|
|
568
|
-
const is24hModeActive = () => {
|
|
569
|
-
try {
|
|
570
|
-
const snapshot = read24hState(project, sessionId);
|
|
571
|
-
return snapshot.state === '24H_ACTIVE';
|
|
572
|
-
}
|
|
573
|
-
catch {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
const out = runKarpathyCostCheck({
|
|
578
|
-
reviewFilePath: opts.reviewFile,
|
|
579
|
-
is24hModeActive
|
|
580
|
-
});
|
|
581
|
-
printResult(io, buildCostCheckEnvelope(out), asJson(opts));
|
|
582
|
-
});
|
|
583
|
-
addJsonOption(job.commands.find((c) => c.name() === 'karpathy-cost-check'));
|
|
62
|
+
}
|
|
63
|
+
export function registerJobCommands(program, io = {
|
|
64
|
+
stdout: (t) => process.stdout.write(t),
|
|
65
|
+
stderr: (t) => process.stderr.write(t)
|
|
66
|
+
}) {
|
|
67
|
+
const job = new Command('job').description('Drive long multi-slice work as one Job (peaks-code Step 0.8+)');
|
|
68
|
+
registerJobInitStatusCommands(job, io);
|
|
69
|
+
registerJobRotateNowCommand(job, io);
|
|
70
|
+
registerJobSubagentCleanupCommand(job, io);
|
|
71
|
+
registerJobCheckpointCommands(job, io);
|
|
72
|
+
registerJobRunCommands(job, io);
|
|
73
|
+
registerJobCostCheckCommand(job, io);
|
|
584
74
|
program.addCommand(job);
|
|
585
75
|
}
|