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,168 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isUnsafePathInput } from '../../shared/path-safety.js';
|
|
4
|
+
import { dispatchEvaluator } from '../../services/loop/evaluator-dispatcher.js';
|
|
5
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
6
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
7
|
+
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
8
|
+
import { loopCommandParent } from './loop-eval-parents.js';
|
|
9
|
+
import { nextEvalCaptureIndex } from './loop-eval-capture-index.js';
|
|
10
|
+
const VALID_EVALUATORS = new Set([
|
|
11
|
+
'karpathy',
|
|
12
|
+
'code-review',
|
|
13
|
+
'security-review',
|
|
14
|
+
'perf-baseline',
|
|
15
|
+
'verdict-aggregate',
|
|
16
|
+
'monotonic-improvement',
|
|
17
|
+
'impact-scan',
|
|
18
|
+
'smoke-run',
|
|
19
|
+
'canary-watch'
|
|
20
|
+
]);
|
|
21
|
+
export function registerLoopEvalCommand(program, io) {
|
|
22
|
+
const loop = loopCommandParent(program);
|
|
23
|
+
// peaks loop eval <rid> --evaluator <name>
|
|
24
|
+
addJsonOption(loop
|
|
25
|
+
.command('eval')
|
|
26
|
+
.description('Slice B.2: invoke a native evaluator directly. The runtime calls the evaluator (no LLM scheduling) and returns a verdict envelope compatible with peaks verdict aggregate. Pass --capture-score to also persist the score row to .peaks/_runtime/<sid>/loop/<rid>/cycles/cycle-N.json (P0 closure: this is the score the run-driver reads back).')
|
|
27
|
+
.argument('<rid>', 'request id (e.g. 2026-06-30-...)')
|
|
28
|
+
.requiredOption('--evaluator <name>', `evaluator: ${[...VALID_EVALUATORS].join(', ')}`)
|
|
29
|
+
.option('--session <sid>', 'session id (required by --evaluator monotonic-improvement; also required by --capture-score)')
|
|
30
|
+
.option('--project <path>', 'project root (default: cwd)')
|
|
31
|
+
.option('--scope <scope>', 'optional scope expression (forwarded to the evaluator)')
|
|
32
|
+
.option('--threshold <threshold>', 'optional SLA threshold (evaluator-specific)')
|
|
33
|
+
.option('--capture-score', 'persist the verdict score to .peaks/_runtime/<sid>/loop/<rid>/cycles/cycle-N.json (requires --session); adds data.score', false)).action((rid, options) => runLoopEval(rid, options, io));
|
|
34
|
+
}
|
|
35
|
+
function evalUnknownEvaluatorFailure(rid, evaluator) {
|
|
36
|
+
return fail('loop.eval', 'UNKNOWN_EVALUATOR', `evaluator "${evaluator}" is not a native evaluator (allowed: ${[...VALID_EVALUATORS].join(', ')})`, { rid }, [`Use one of: ${[...VALID_EVALUATORS].join(', ')}`]);
|
|
37
|
+
}
|
|
38
|
+
function evalMissingSessionFailure(rid) {
|
|
39
|
+
return fail('loop.eval', 'CAPTURE_SCORE_NEEDS_SESSION', '--capture-score requires --session <sid>', { rid }, ['Pass --session <sid> alongside --capture-score.']);
|
|
40
|
+
}
|
|
41
|
+
function evalCapturePathFailure(rid, sid) {
|
|
42
|
+
if (isUnsafePathInput(sid)) {
|
|
43
|
+
return fail('loop.eval', 'INVALID_SESSION_ID', `Invalid session id: ${sid} (must be a single path segment)`, { provided: sid }, ['Pass a session id that is a single path segment']);
|
|
44
|
+
}
|
|
45
|
+
if (isUnsafePathInput(rid)) {
|
|
46
|
+
return fail('loop.eval', 'INVALID_REQUEST_ID', `Invalid request id: ${rid} (must be a single path segment)`, { provided: rid }, ['Pass a request id that is a single path segment']);
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function reportEvalFailure(io, failure, asJson) {
|
|
51
|
+
printResult(io, failure, asJson);
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
function reportLoopEvalFailure(io, error, data, asJson) {
|
|
55
|
+
printResult(io, fail('loop.eval', 'LOOP_EVAL_FAILED', getErrorMessage(error), data, [
|
|
56
|
+
'Verify the rid and evaluator name.'
|
|
57
|
+
]), asJson);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
function captureEvalScore(rid, sid, projectRoot, envelope) {
|
|
61
|
+
const score = envelope.degraded
|
|
62
|
+
? 0.25
|
|
63
|
+
: envelope.gateAction === 'pass'
|
|
64
|
+
? 1.0
|
|
65
|
+
: envelope.gateAction === 'warn'
|
|
66
|
+
? 0.5
|
|
67
|
+
: 0.0;
|
|
68
|
+
const dir = join(projectRoot, '.peaks', '_runtime', sid, 'loop', rid, 'cycles');
|
|
69
|
+
const n = nextEvalCaptureIndex(projectRoot, sid, rid);
|
|
70
|
+
const path = join(dir, `cycle-${n}.json`);
|
|
71
|
+
try {
|
|
72
|
+
mkdirSync(dir, { recursive: true });
|
|
73
|
+
writeFileSync(path, JSON.stringify({
|
|
74
|
+
cycle: n,
|
|
75
|
+
rid,
|
|
76
|
+
sid,
|
|
77
|
+
persistedAt: new Date().toISOString(),
|
|
78
|
+
scores: [
|
|
79
|
+
{
|
|
80
|
+
evaluator: envelope.kind,
|
|
81
|
+
score,
|
|
82
|
+
gateAction: envelope.gateAction,
|
|
83
|
+
degraded: envelope.degraded,
|
|
84
|
+
observedAt: new Date().toISOString()
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}), 'utf8');
|
|
88
|
+
return { score, persistedAt: path };
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return { score, persistedAt: null };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function evalResultData(rid, envelope, capture) {
|
|
95
|
+
return {
|
|
96
|
+
rid,
|
|
97
|
+
evaluator: envelope.kind,
|
|
98
|
+
verdict: envelope.gateAction,
|
|
99
|
+
passed: envelope.passed,
|
|
100
|
+
violations: envelope.violations,
|
|
101
|
+
summary: envelope.summary,
|
|
102
|
+
wallSeconds: envelope.wallSeconds,
|
|
103
|
+
degraded: envelope.degraded,
|
|
104
|
+
...(capture !== null ? { score: capture.score, capture } : {})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function evalNextActions(rid, envelope) {
|
|
108
|
+
return envelope.degraded
|
|
109
|
+
? [
|
|
110
|
+
'Evaluator ran in degraded mode (peaks CLI unavailable). Verify the verdict by running `peaks verdict aggregate --from-rid ' +
|
|
111
|
+
rid +
|
|
112
|
+
'`.',
|
|
113
|
+
'Re-run on a fully-installed peaks-loop environment for a real verdict.'
|
|
114
|
+
]
|
|
115
|
+
: [
|
|
116
|
+
`Verifier verdict-aggregate can consume this envelope via \`peaks verdict aggregate --from-rid ${rid}\`.`
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
function runLoopEval(rid, options, io) {
|
|
120
|
+
try {
|
|
121
|
+
if (!VALID_EVALUATORS.has(options.evaluator)) {
|
|
122
|
+
const failure = evalUnknownEvaluatorFailure(rid, options.evaluator);
|
|
123
|
+
printResult(io, failure, options.json);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
128
|
+
const envelope = dispatchEvaluator(options.evaluator, {
|
|
129
|
+
projectRoot,
|
|
130
|
+
rid,
|
|
131
|
+
...(options.session !== undefined ? { sessionId: options.session } : {}),
|
|
132
|
+
...(options.scope !== undefined ? { scope: options.scope } : {}),
|
|
133
|
+
...(options.threshold !== undefined ? { threshold: options.threshold } : {})
|
|
134
|
+
});
|
|
135
|
+
const exitCode = envelope.gateAction === 'block' ? 1 : 0;
|
|
136
|
+
// --capture-score path: persist a single-row cycle-N.json to
|
|
137
|
+
// the run-driver's cycles dir. Mirrors the scoring convention
|
|
138
|
+
// of monotonic-guard (pass=1.0, warn=0.5, block=0.0; degraded=0.25).
|
|
139
|
+
let capture = null;
|
|
140
|
+
if (options.captureScore === true) {
|
|
141
|
+
// Sid axis AND rid axis — the join below takes TWO ids, not one.
|
|
142
|
+
// covers "the whole `--capture-score` write path". The security audit of
|
|
143
|
+
// the `--session` guard passed and the `rid` slot escaped:
|
|
144
|
+
// `peaks loop eval '../../../../…/EVILCYC' --capture-score --session
|
|
145
|
+
// <legal>` created `<projectRoot>/../EVILCYC/cycles/cycle-1.json` under
|
|
146
|
+
// an `ok: true` envelope. The rid is the CLI positional and has no pinned
|
|
147
|
+
// format, so the segment check is the control for that axis.
|
|
148
|
+
const sid = options.session;
|
|
149
|
+
if (sid === undefined || sid.length === 0) {
|
|
150
|
+
reportEvalFailure(io, evalMissingSessionFailure(rid), options.json);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const pathFailure = evalCapturePathFailure(rid, sid);
|
|
154
|
+
if (pathFailure !== null) {
|
|
155
|
+
reportEvalFailure(io, pathFailure, options.json);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
capture = captureEvalScore(rid, sid, projectRoot, envelope);
|
|
159
|
+
}
|
|
160
|
+
const data = evalResultData(rid, envelope, capture);
|
|
161
|
+
const actions = evalNextActions(rid, envelope);
|
|
162
|
+
printResult(io, ok('loop.eval', data, [], actions), options.json);
|
|
163
|
+
process.exitCode = exitCode;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
reportLoopEvalFailure(io, error, { rid, evaluator: options.evaluator }, options.json);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { DEFAULT_MONOTONIC_THRESHOLD } from '../../services/loop/monotonic-guard.js';
|
|
2
|
+
import { runMonotonicCheck, resolveMonotonicContext } from '../../services/loop/monotonic-runner.js';
|
|
3
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
5
|
+
import { loopCommandParent } from './loop-eval-parents.js';
|
|
6
|
+
export function registerLoopCheckMonotonicCommand(program, io) {
|
|
7
|
+
const loop = loopCommandParent(program);
|
|
8
|
+
// peaks loop check-monotonic <rid>
|
|
9
|
+
// Slice C: compare adjacent cycles' per-evaluator scores; reject
|
|
10
|
+
// score regression > threshold with `MONOTONICITY_VIOLATION`.
|
|
11
|
+
addJsonOption(loop
|
|
12
|
+
.command('check-monotonic')
|
|
13
|
+
.description('Slice C.2: compare adjacent cycles of evaluator scores for a rid. Reject (exit 1) when an evaluator score regresses beyond the configured threshold.')
|
|
14
|
+
.argument('<rid>', 'request id (e.g. 2026-06-30-...)')
|
|
15
|
+
.requiredOption('--session <sid>', 'session id')
|
|
16
|
+
.option('--project <path>', 'project root (default: cwd)')
|
|
17
|
+
.option('--threshold <threshold>', `maximum allowed score regression on the 0..1 scale (default: ${DEFAULT_MONOTONIC_THRESHOLD} = 5%)`)
|
|
18
|
+
.option('--no-persist', 'skip persisting the current cycle score rows to disk')).action((rid, options) => runCheckMonotonic(rid, options, io));
|
|
19
|
+
}
|
|
20
|
+
function reportMonotonicFailure(io, error, rid, asJson) {
|
|
21
|
+
printResult(io, fail('loop.check-monotonic', 'LOOP_CHECK_MONOTONIC_FAILED', getErrorMessage(error), { rid }, [
|
|
22
|
+
'Verify the rid and session binding.'
|
|
23
|
+
]), asJson);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
}
|
|
26
|
+
function monotonicResultData(rid, sid, projectRoot, result) {
|
|
27
|
+
return {
|
|
28
|
+
rid,
|
|
29
|
+
sessionId: sid,
|
|
30
|
+
projectRoot,
|
|
31
|
+
currentCycle: result.currentCycle,
|
|
32
|
+
previousCycle: result.previousCycle,
|
|
33
|
+
persistedAt: result.persistedAt,
|
|
34
|
+
rows: result.rows,
|
|
35
|
+
threshold: result.report.threshold,
|
|
36
|
+
status: result.report.status,
|
|
37
|
+
code: result.report.code,
|
|
38
|
+
monotonicityViolation: result.report.monotonicityViolation,
|
|
39
|
+
regressions: result.report.regressions,
|
|
40
|
+
reason: result.report.reason
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function monotonicNextActions(rid, result) {
|
|
44
|
+
return result.report.monotonicityViolation
|
|
45
|
+
? [
|
|
46
|
+
'Investigate which evaluator regressed most and the previous cycle threshold.',
|
|
47
|
+
`Inspect ${result.persistedAt ?? 'the persisted cycle row at .peaks/_runtime/<sid>/loop/<rid>/cycle-N.json'}.`
|
|
48
|
+
]
|
|
49
|
+
: result.report.status === 'skip'
|
|
50
|
+
? [
|
|
51
|
+
'Cycle is the first run or incomparable; monotonicity guard is a no-op.',
|
|
52
|
+
'A future cycle will surface a violation if any evaluator regresses.'
|
|
53
|
+
]
|
|
54
|
+
: [
|
|
55
|
+
'All evaluators held or improved.',
|
|
56
|
+
`Verifier verdict-aggregate can consume this envelope via \`peaks verdict aggregate --from-rid ${rid}\`.`
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
function runCheckMonotonic(rid, options, io) {
|
|
60
|
+
try {
|
|
61
|
+
const { projectRoot, sid } = resolveMonotonicContext({
|
|
62
|
+
...(options.project !== undefined ? { project: options.project } : {}),
|
|
63
|
+
session: options.session,
|
|
64
|
+
rid
|
|
65
|
+
});
|
|
66
|
+
const persist = options.persist !== false;
|
|
67
|
+
const thresholdNum = options.threshold !== undefined ? Number(options.threshold) : DEFAULT_MONOTONIC_THRESHOLD;
|
|
68
|
+
if (options.threshold !== undefined &&
|
|
69
|
+
(!Number.isFinite(thresholdNum) || thresholdNum < 0 || thresholdNum > 1)) {
|
|
70
|
+
printResult(io, fail('loop.check-monotonic', 'INVALID_THRESHOLD', `threshold must be a finite number in [0,1] (got "${options.threshold}")`, { rid }, [`Pass --threshold ${DEFAULT_MONOTONIC_THRESHOLD} (default) or any number in [0,1].`]), options.json);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const result = runMonotonicCheck({
|
|
75
|
+
projectRoot,
|
|
76
|
+
sid,
|
|
77
|
+
rid,
|
|
78
|
+
threshold: thresholdNum,
|
|
79
|
+
persist
|
|
80
|
+
});
|
|
81
|
+
const exitCode = result.report.monotonicityViolation ? 1 : 0;
|
|
82
|
+
const data = monotonicResultData(rid, sid, projectRoot, result);
|
|
83
|
+
const actions = monotonicNextActions(rid, result);
|
|
84
|
+
printResult(io, ok('loop.check-monotonic', data, [], actions), options.json);
|
|
85
|
+
process.exitCode = exitCode;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
reportMonotonicFailure(io, error, rid, options.json);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/** The `peaks workflow` parent, or a newly created one. */
|
|
3
|
+
export declare function workflowCommandParent(program: Command): Command;
|
|
4
|
+
/** The `peaks loop` parent, or a newly created one. */
|
|
5
|
+
export declare function loopCommandParent(program: Command): Command;
|
|
6
|
+
/** The `peaks loop spec` parent, or a newly created one. */
|
|
7
|
+
export declare function loopSpecParent(program: Command): Command;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The `peaks workflow` parent, or a newly created one. */
|
|
2
|
+
export function workflowCommandParent(program) {
|
|
3
|
+
const existingWorkflow = program.commands.find((c) => c.name() === 'workflow');
|
|
4
|
+
return (existingWorkflow ??
|
|
5
|
+
program.command('workflow').description('Workflow primitive (run / plan / lint)'));
|
|
6
|
+
}
|
|
7
|
+
/** The `peaks loop` parent, or a newly created one. */
|
|
8
|
+
export function loopCommandParent(program) {
|
|
9
|
+
const existingLoop = program.commands.find((c) => c.name() === 'loop');
|
|
10
|
+
return existingLoop ?? program.command('loop').description('Loop primitive (eval)');
|
|
11
|
+
}
|
|
12
|
+
/** The `peaks loop spec` parent, or a newly created one. */
|
|
13
|
+
export function loopSpecParent(program) {
|
|
14
|
+
const loop = loopCommandParent(program);
|
|
15
|
+
const existingSpec = loop.commands.find((c) => c.name() === 'spec');
|
|
16
|
+
return (existingSpec ??
|
|
17
|
+
loop
|
|
18
|
+
.command('spec')
|
|
19
|
+
.description('Slice E.2: read or bootstrap the spec for a rid. Defaults to read; pass --bootstrap to write a default spec.'));
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Map a run-driver code to a process exit code. */
|
|
2
|
+
export declare function mapRunDriverCodeToExit(code: string): number;
|
|
3
|
+
export declare function nextActionsForCode(code: string, rid: string, sid: string, summary: {
|
|
4
|
+
reachedMaxCycles: boolean;
|
|
5
|
+
regressionCount: number;
|
|
6
|
+
totalCycles: number;
|
|
7
|
+
}): string[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The `peaks loop run` result presenter: the run-driver's terminal code mapped to a
|
|
2
|
+
// process exit code, and to the prose a caller reads after a failed run.
|
|
3
|
+
import { MONOTONIC_TERMINATION } from '../../services/loop/spec-service.js';
|
|
4
|
+
/** Map a run-driver code to a process exit code. */
|
|
5
|
+
export function mapRunDriverCodeToExit(code) {
|
|
6
|
+
if (code === 'RUN_OK' || code === 'RUN_OK_REGRESSION')
|
|
7
|
+
return 0;
|
|
8
|
+
return 1;
|
|
9
|
+
}
|
|
10
|
+
export function nextActionsForCode(code, rid, sid, summary) {
|
|
11
|
+
switch (code) {
|
|
12
|
+
case 'SPEC_NOT_FOUND':
|
|
13
|
+
return [`Create a spec with \`peaks loop spec bootstrap ${rid} --session ${sid}\`.`];
|
|
14
|
+
case 'SPEC_INVALID':
|
|
15
|
+
return [`Re-run \`peaks loop spec lint <path>\` to see the schema errors.`];
|
|
16
|
+
case 'UNKNOWN_TERMINATION_STRATEGY':
|
|
17
|
+
return [
|
|
18
|
+
`Edit the spec.yaml and set termination.strategy to one of: ${MONOTONIC_TERMINATION}, max-cycles, manual.`
|
|
19
|
+
];
|
|
20
|
+
case 'MONOTONICITY_VIOLATION':
|
|
21
|
+
return [
|
|
22
|
+
'Inspect the regression rows in the cycles output and the previous cycle row at .peaks/_runtime/' +
|
|
23
|
+
sid +
|
|
24
|
+
'/loop/' +
|
|
25
|
+
rid +
|
|
26
|
+
'/cycles/.',
|
|
27
|
+
'Fix the regression in the next iteration and re-run.'
|
|
28
|
+
];
|
|
29
|
+
case 'LOCKED':
|
|
30
|
+
return ['Wait for the in-flight loop run to complete, or pick a different rid.'];
|
|
31
|
+
case 'RUN_OK_REGRESSION':
|
|
32
|
+
return ['A regression was observed; the run captured it for operator review.'];
|
|
33
|
+
case 'RUN_OK':
|
|
34
|
+
return [
|
|
35
|
+
`Loop completed ${summary.totalCycles} cycle(s); ${summary.regressionCount} regression(s).`
|
|
36
|
+
];
|
|
37
|
+
default:
|
|
38
|
+
return ['Inspect the run-driver output above.'];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { MONOTONIC_TERMINATION, DEFAULT_MAX_CYCLES } from '../../services/loop/spec-service.js';
|
|
2
|
+
import { runLoop, resolveRunContext } from '../../services/loop/run-driver.js';
|
|
3
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
5
|
+
import { loopCommandParent } from './loop-eval-parents.js';
|
|
6
|
+
import { mapRunDriverCodeToExit, nextActionsForCode } from './loop-eval-run-code.js';
|
|
7
|
+
export function registerLoopRunCommand(program, io) {
|
|
8
|
+
const loop = loopCommandParent(program);
|
|
9
|
+
// peaks loop run <rid> — Slice F.1 (P0 closure): the closed-loop
|
|
10
|
+
// driver that actually consumes `termination.strategy` (previously
|
|
11
|
+
// declared-and-validated but never consumed — dogfood audit #2).
|
|
12
|
+
addJsonOption(loop
|
|
13
|
+
.command('run')
|
|
14
|
+
.description(`Slice F.1: drive the closed loop for <rid> per .peaks/_runtime/<sid>/loop/<rid>/spec.yaml. Consumes termination.strategy (${MONOTONIC_TERMINATION} | max-cycles | manual); aborts on MONOTONICITY_VIOLATION.`)
|
|
15
|
+
.argument('<rid>', 'request id (e.g. 2026-06-30-...)')
|
|
16
|
+
.requiredOption('--session <sid>', 'session id')
|
|
17
|
+
.option('--project <path>', 'project root (default: cwd)')
|
|
18
|
+
.option('--strategy <strategy>', `override termination.strategy (allowed: ${MONOTONIC_TERMINATION}, max-cycles, manual)`)
|
|
19
|
+
.option('--max-cycles <n>', `override termination.maxCycles (default ${DEFAULT_MAX_CYCLES})`)
|
|
20
|
+
.option('--threshold <t>', 'monotonic threshold (0..1); default 0.05')
|
|
21
|
+
.option('--no-persist', 'skip writing cycle-N.json + summary to disk')).action((rid, options) => runLoopDriver(rid, options, io));
|
|
22
|
+
}
|
|
23
|
+
function reportLoopRunFailure(io, error, rid, asJson) {
|
|
24
|
+
printResult(io, fail('loop.run', 'RUN_FAILED', getErrorMessage(error), { rid }, [
|
|
25
|
+
'Verify the rid and session binding.'
|
|
26
|
+
]), asJson);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
function loopRunInvalidThresholdFailure(rid, threshold) {
|
|
30
|
+
return fail('loop.run', 'INVALID_THRESHOLD', `threshold must be finite in [0,1] (got "${threshold}")`, { rid }, [`Pass --threshold 0.05 (default) or any number in [0,1].`]);
|
|
31
|
+
}
|
|
32
|
+
function loopRunOkData(result, rid, sid, projectRoot) {
|
|
33
|
+
return {
|
|
34
|
+
rid,
|
|
35
|
+
sessionId: sid,
|
|
36
|
+
projectRoot,
|
|
37
|
+
code: result.code,
|
|
38
|
+
strategy: result.strategy,
|
|
39
|
+
maxCycles: result.maxCycles,
|
|
40
|
+
cycles: result.cycles.map((c) => ({
|
|
41
|
+
cycle: c.cycle,
|
|
42
|
+
rows: c.rows,
|
|
43
|
+
persistedAt: c.persistedAt,
|
|
44
|
+
monotonicReport: c.monotonicReport
|
|
45
|
+
})),
|
|
46
|
+
finalReport: result.finalReport,
|
|
47
|
+
finalReportCode: result.finalReport?.code ?? null,
|
|
48
|
+
summary: result.summary
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function loopRunFailData(result, rid, sid, projectRoot) {
|
|
52
|
+
return {
|
|
53
|
+
rid,
|
|
54
|
+
sessionId: sid,
|
|
55
|
+
projectRoot,
|
|
56
|
+
code: result.code,
|
|
57
|
+
strategy: result.strategy,
|
|
58
|
+
maxCycles: result.maxCycles,
|
|
59
|
+
cycles: result.cycles,
|
|
60
|
+
finalReport: result.finalReport,
|
|
61
|
+
finalReportCode: result.finalReport?.code ?? null,
|
|
62
|
+
summary: result.summary
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function loopRunNextActions(result) {
|
|
66
|
+
return result.code === 'RUN_OK'
|
|
67
|
+
? [
|
|
68
|
+
`Loop completed ${result.summary.totalCycles} cycle(s) with ${result.summary.regressionCount} regression(s).`
|
|
69
|
+
]
|
|
70
|
+
: [`Run driver exited with ${result.code}: ${result.message}`];
|
|
71
|
+
}
|
|
72
|
+
function loopRunEnvelope(result, rid, sid, projectRoot) {
|
|
73
|
+
return result.ok
|
|
74
|
+
? ok('loop.run', loopRunOkData(result, rid, sid, projectRoot), [], loopRunNextActions(result))
|
|
75
|
+
: fail('loop.run', result.code, result.message, loopRunFailData(result, rid, sid, projectRoot), nextActionsForCode(result.code, rid, sid, result.summary));
|
|
76
|
+
}
|
|
77
|
+
function runLoopDriver(rid, options, io) {
|
|
78
|
+
try {
|
|
79
|
+
const { projectRoot, sid, rid: r2 } = resolveRunContext({
|
|
80
|
+
...(options.project !== undefined ? { project: options.project } : {}),
|
|
81
|
+
session: options.session,
|
|
82
|
+
rid
|
|
83
|
+
});
|
|
84
|
+
const threshold = options.threshold !== undefined ? Number(options.threshold) : undefined;
|
|
85
|
+
if (threshold !== undefined &&
|
|
86
|
+
(!Number.isFinite(threshold) || threshold < 0 || threshold > 1)) {
|
|
87
|
+
const failure = loopRunInvalidThresholdFailure(r2, options.threshold);
|
|
88
|
+
printResult(io, failure, options.json);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const maxCycles = options.maxCycles !== undefined
|
|
93
|
+
? Math.max(1, Math.floor(Number(options.maxCycles) || DEFAULT_MAX_CYCLES))
|
|
94
|
+
: undefined;
|
|
95
|
+
const strategyOverride = options.strategy;
|
|
96
|
+
const result = runLoop({
|
|
97
|
+
projectRoot,
|
|
98
|
+
sid,
|
|
99
|
+
rid: r2,
|
|
100
|
+
...(threshold !== undefined ? { threshold } : {}),
|
|
101
|
+
...(maxCycles !== undefined ? { maxCyclesOverride: maxCycles } : {}),
|
|
102
|
+
...(strategyOverride !== undefined ? { strategyOverride } : {}),
|
|
103
|
+
persist: options.persist !== false
|
|
104
|
+
});
|
|
105
|
+
const exitCode = mapRunDriverCodeToExit(result.code);
|
|
106
|
+
const envelope = loopRunEnvelope(result, r2, sid, projectRoot);
|
|
107
|
+
printResult(io, envelope, options.json);
|
|
108
|
+
process.exitCode = exitCode;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
reportLoopRunFailure(io, error, rid, options.json);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolveLoopSpec, persistSpec, buildSpec, lintLoopSpec, specPath, MONOTONIC_TERMINATION, DEFAULT_MAX_CYCLES } from '../../services/loop/spec-service.js';
|
|
3
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
5
|
+
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
6
|
+
import { loopSpecParent } from './loop-eval-parents.js';
|
|
7
|
+
export function registerLoopSpecCommands(program, io) {
|
|
8
|
+
// peaks loop spec <rid> — Slice E.2: read or bootstrap the
|
|
9
|
+
// project-level `.peaks/_runtime/<sid>/loop/<rid>/spec.yaml`. When
|
|
10
|
+
// `--bootstrap` is set, a default spec is written; otherwise the
|
|
11
|
+
// existing spec is read (or `{kind:'missing'}` is returned).
|
|
12
|
+
const spec = loopSpecParent(program);
|
|
13
|
+
addJsonOption(spec
|
|
14
|
+
.command('show')
|
|
15
|
+
.description('Slice E.2: print the resolved spec for a rid.')
|
|
16
|
+
.argument('<rid>', 'request id')
|
|
17
|
+
.requiredOption('--session <sid>', 'session id')
|
|
18
|
+
.option('--project <path>', 'project root (default: cwd)')).action((rid, options) => runLoopSpecShow(rid, options, io));
|
|
19
|
+
addJsonOption(spec
|
|
20
|
+
.command('bootstrap')
|
|
21
|
+
.description('Slice E.2: write a default spec.yaml for a rid at `.peaks/_runtime/<sid>/loop/<rid>/spec.yaml`. Refuses to overwrite an existing spec without --force (P1 from dogfood audit: bootstrap is destructive on re-run).')
|
|
22
|
+
.argument('<rid>', 'request id')
|
|
23
|
+
.requiredOption('--session <sid>', 'session id')
|
|
24
|
+
.option('--project <path>', 'project root (default: cwd)')
|
|
25
|
+
.option('--strategy <strategy>', `termination strategy (manual|max-cycles|${MONOTONIC_TERMINATION})`, MONOTONIC_TERMINATION)
|
|
26
|
+
.option('--max-cycles <n>', `max-cycles (only when strategy=max-cycles; default ${DEFAULT_MAX_CYCLES})`, String(DEFAULT_MAX_CYCLES))
|
|
27
|
+
.option('--force', 'overwrite an existing spec.yaml (without --force, bootstrap refuses with SPEC_EXISTS_NEEDS_FORCE)', false)).action((rid, options) => runLoopSpecBootstrap(rid, options, io));
|
|
28
|
+
}
|
|
29
|
+
function reportLoopSpecShowFailure(io, error, rid, asJson) {
|
|
30
|
+
printResult(io, fail('loop.spec.show', 'LOOP_SPEC_SHOW_FAILED', getErrorMessage(error), { rid }, [
|
|
31
|
+
'Verify the rid and session binding.'
|
|
32
|
+
]), asJson);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
}
|
|
35
|
+
function runLoopSpecShow(rid, options, io) {
|
|
36
|
+
try {
|
|
37
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
38
|
+
const resolved = resolveLoopSpec(projectRoot, options.session, rid);
|
|
39
|
+
if (resolved.spec === null) {
|
|
40
|
+
printResult(io, fail('loop.spec.show', 'SPEC_NOT_FOUND', `no spec.yaml at ${resolved.origin && resolved.origin.kind === 'missing' ? `.peaks/_runtime/${options.session}/loop/${rid}/spec.yaml` : 'unknown'}`, { rid, sessionId: options.session }, [
|
|
41
|
+
`Create one via \`peaks loop spec bootstrap ${rid} --session ${options.session} --project ${projectRoot}\`.`
|
|
42
|
+
]), options.json);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const report = lintLoopSpec(resolved.spec);
|
|
47
|
+
printResult(io, ok('loop.spec.show', {
|
|
48
|
+
rid,
|
|
49
|
+
sessionId: options.session,
|
|
50
|
+
projectRoot,
|
|
51
|
+
origin: resolved.origin,
|
|
52
|
+
spec: resolved.spec,
|
|
53
|
+
lint: { ok: report.ok, errors: report.errors, warnings: report.warnings }
|
|
54
|
+
}, [], [
|
|
55
|
+
`Edit \`${typeof resolved.origin === 'object' && resolved.origin.kind === 'project' ? resolved.origin.path : '<spec>'}\` and re-run \`peaks loop spec lint <file>\`.`
|
|
56
|
+
]), options.json);
|
|
57
|
+
if (!report.ok)
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
reportLoopSpecShowFailure(io, error, rid, options.json);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function resolveBootstrapTermination(options) {
|
|
65
|
+
const strategyRaw = options.strategy;
|
|
66
|
+
const strategy = strategyRaw === 'max-cycles' ||
|
|
67
|
+
strategyRaw === MONOTONIC_TERMINATION ||
|
|
68
|
+
strategyRaw === 'manual'
|
|
69
|
+
? strategyRaw
|
|
70
|
+
: MONOTONIC_TERMINATION;
|
|
71
|
+
return strategy === 'max-cycles'
|
|
72
|
+
? {
|
|
73
|
+
strategy,
|
|
74
|
+
maxCycles: Math.max(1, Math.floor(Number(options.maxCycles) || DEFAULT_MAX_CYCLES))
|
|
75
|
+
}
|
|
76
|
+
: { strategy };
|
|
77
|
+
}
|
|
78
|
+
function defaultSpecEvaluators() {
|
|
79
|
+
return [
|
|
80
|
+
{ kind: 'karpathy', gate: 'Gate B3', scope: 'src/' },
|
|
81
|
+
{ kind: 'code-review', gate: 'Gate B3', scope: 'src/' },
|
|
82
|
+
{ kind: 'security-review', gate: 'Gate B4', scope: 'src/' },
|
|
83
|
+
{ kind: 'perf-baseline', gate: 'Gate B4', scope: 'src/' },
|
|
84
|
+
{ kind: 'monotonic-improvement', gate: 'Gate D1' }
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
function defaultSpecSla() {
|
|
88
|
+
return [
|
|
89
|
+
{ evaluator: 'karpathy', maxScore: 0.7 },
|
|
90
|
+
{ evaluator: 'code-review', maxScore: 0.7 },
|
|
91
|
+
{ evaluator: 'security-review', maxScore: 0.7 },
|
|
92
|
+
{ evaluator: 'perf-baseline', maxScore: 0.7 },
|
|
93
|
+
{ evaluator: 'monotonic-improvement', maxScore: 0.5 }
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
function specExistsFailure(rid, sessionId, path) {
|
|
97
|
+
return fail('loop.spec.bootstrap', 'SPEC_EXISTS_NEEDS_FORCE', `spec.yaml already exists at ${path}; re-run with --force to overwrite`, { rid, sessionId, path }, [
|
|
98
|
+
`Re-run with \`peaks loop spec bootstrap ${rid} --session ${sessionId} --force\` to overwrite.`,
|
|
99
|
+
`Or edit \`${path}\` directly.`
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
function specLintFailure(rid, report) {
|
|
103
|
+
return fail('loop.spec.bootstrap', 'SPEC_LINT_FAILED', report.errors.join('; '), { rid, report }, ['Verify the strategy flag.']);
|
|
104
|
+
}
|
|
105
|
+
function specBootstrapData(context, specObj, report) {
|
|
106
|
+
return {
|
|
107
|
+
rid: context.rid,
|
|
108
|
+
sessionId: context.sessionId,
|
|
109
|
+
projectRoot: context.projectRoot,
|
|
110
|
+
path: context.path,
|
|
111
|
+
spec: specObj,
|
|
112
|
+
lint: { ok: report.ok, errors: report.errors, warnings: report.warnings },
|
|
113
|
+
overwritten: context.force === true && existsSync(context.path)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function specBootstrapActions(rid, sessionId, writtenPath) {
|
|
117
|
+
return [
|
|
118
|
+
`Run \`peaks loop spec lint ${writtenPath}\` to re-validate.`,
|
|
119
|
+
`Then run \`peaks loop run ${rid} --session ${sessionId}\` to execute the loop.`
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
function reportLoopSpecBootstrapFailure(io, error, rid, asJson) {
|
|
123
|
+
printResult(io, fail('loop.spec.bootstrap', 'LOOP_SPEC_BOOTSTRAP_FAILED', getErrorMessage(error), { rid }, [
|
|
124
|
+
'Verify the rid, session, and strategy flag.'
|
|
125
|
+
]), asJson);
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
}
|
|
128
|
+
function runLoopSpecBootstrap(rid, options, io) {
|
|
129
|
+
try {
|
|
130
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
131
|
+
const path = specPath(projectRoot, options.session, rid);
|
|
132
|
+
// P1二次保护:spec.yaml 已存在且未带 --force → SPEC_EXISTS_NEEDS_FORCE
|
|
133
|
+
if (existsSync(path) && options.force !== true) {
|
|
134
|
+
const failure = specExistsFailure(rid, options.session, path);
|
|
135
|
+
printResult(io, failure, options.json);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const termination = resolveBootstrapTermination(options);
|
|
140
|
+
const evaluators = defaultSpecEvaluators();
|
|
141
|
+
const sla = defaultSpecSla();
|
|
142
|
+
const specObj = buildSpec({ rid, evaluators, sla, termination }, rid);
|
|
143
|
+
const report = lintLoopSpec(specObj);
|
|
144
|
+
if (!report.ok) {
|
|
145
|
+
const failure = specLintFailure(rid, report);
|
|
146
|
+
printResult(io, failure, options.json);
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const writtenPath = persistSpec(projectRoot, options.session, specObj);
|
|
151
|
+
const writeCtx = {
|
|
152
|
+
rid,
|
|
153
|
+
sessionId: options.session,
|
|
154
|
+
projectRoot,
|
|
155
|
+
path: writtenPath,
|
|
156
|
+
...(options.force !== undefined ? { force: options.force } : {})
|
|
157
|
+
};
|
|
158
|
+
const data = specBootstrapData(writeCtx, specObj, report);
|
|
159
|
+
const actions = specBootstrapActions(rid, options.session, writtenPath);
|
|
160
|
+
printResult(io, ok('loop.spec.bootstrap', data, [], actions), options.json);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
reportLoopSpecBootstrapFailure(io, error, rid, options.json);
|
|
164
|
+
}
|
|
165
|
+
}
|