peaks-loop 4.0.46 → 4.0.48
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/CHANGELOG.md +54 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/agents/karpathy-reviewer.md +11 -10
- package/dist/cli/cli-helpers.d.ts +34 -0
- package/dist/cli/cli-helpers.js +57 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
- package/dist/cli/commands/code-runtime-commands.js +139 -16
- package/dist/cli/commands/compact-command.js +241 -1
- package/dist/cli/commands/config-commands.js +15 -9
- package/dist/cli/commands/container-commands.js +3 -3
- package/dist/cli/commands/core/skill-command.js +45 -10
- package/dist/cli/commands/cron-commands.js +2 -1
- package/dist/cli/commands/dashboard-long-run.js +6 -0
- package/dist/cli/commands/dispatch-commands.js +11 -1
- package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
- package/dist/cli/commands/e2e-verify.js +3 -3
- package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
- package/dist/cli/commands/hooks-commands.js +14 -5
- package/dist/cli/commands/job-commands.js +8 -0
- package/dist/cli/commands/loop-commands.js +1 -0
- package/dist/cli/commands/loop-eval-commands.js +15 -0
- package/dist/cli/commands/perf-audit-commands.js +2 -0
- package/dist/cli/commands/playwright-commands.js +14 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/qa-commands.js +22 -0
- package/dist/cli/commands/reinject-command.d.ts +72 -0
- package/dist/cli/commands/reinject-command.js +174 -0
- package/dist/cli/commands/request-commands.js +14 -3
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -0
- package/dist/cli/commands/shadcn-commands.js +1 -0
- package/dist/cli/commands/slice-integrate-commands.js +5 -0
- package/dist/cli/commands/statusline-commands.js +44 -4
- package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
- package/dist/cli/commands/sub-agent/detached.js +47 -22
- package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
- package/dist/cli/commands/test-commands.js +2 -1
- package/dist/cli/commands/verdict-aggregate-command.js +95 -13
- package/dist/cli/commands/vm-commands.js +7 -7
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +24 -2
- package/dist/cli/commands/worktree-lease-commands.js +4 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/program.js +5 -0
- package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
- package/dist/services/adapter/adapter-registry.js +1 -1
- package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
- package/dist/services/artifacts/artifact-prerequisites.js +130 -65
- package/dist/services/artifacts/artifact-service.js +1 -1
- package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
- package/dist/services/artifacts/request-artifact-service.js +18 -8
- package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
- package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
- package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
- package/dist/services/audit-independent/perf-audit-service.js +27 -5
- package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
- package/dist/services/audit-independent/security-audit-service.js +28 -6
- package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
- package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
- package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
- package/dist/services/code/auto-compact-lifecycle.d.ts +130 -1
- package/dist/services/code/auto-compact-lifecycle.js +180 -4
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +166 -34
- package/dist/services/code/compact-event-settle.d.ts +122 -0
- package/dist/services/code/compact-event-settle.js +219 -0
- package/dist/services/code/orchestrator-can-do.d.ts +4 -2
- package/dist/services/code/orchestrator-can-do.js +37 -5
- package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
- package/dist/services/codegraph/codegraph-process-runner.js +3 -2
- package/dist/services/compact/request-transition-hook.js +5 -2
- package/dist/services/compact-history/compact-history-service.d.ts +75 -0
- package/dist/services/compact-history/compact-history-service.js +49 -0
- package/dist/services/config/config-restore.d.ts +12 -1
- package/dist/services/config/config-restore.js +35 -4
- package/dist/services/config/config-rollback.js +6 -1
- package/dist/services/config/config-safety.d.ts +52 -0
- package/dist/services/config/config-safety.js +75 -1
- package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
- package/dist/services/context/auto-compact-dispatcher.js +113 -40
- package/dist/services/context/auto-compact-reader.d.ts +68 -28
- package/dist/services/context/auto-compact-reader.js +155 -1
- package/dist/services/context/auto-compact-types.d.ts +89 -12
- package/dist/services/context/auto-compact-types.js +16 -32
- package/dist/services/context/harness-context-witness.d.ts +310 -0
- package/dist/services/context/harness-context-witness.js +606 -0
- package/dist/services/context/harness-window-config.d.ts +412 -0
- package/dist/services/context/harness-window-config.js +607 -0
- package/dist/services/context/main-session-monitor.d.ts +27 -0
- package/dist/services/context/main-session-monitor.js +32 -1
- package/dist/services/context/post-compact-reinjection.d.ts +221 -0
- package/dist/services/context/post-compact-reinjection.js +491 -0
- package/dist/services/dispatch/merge-back-runner.js +5 -5
- package/dist/services/dispatch/service-shutdown.js +3 -3
- package/dist/services/doc/doc-generator.js +2 -1
- package/dist/services/env/shell-probe.js +1 -1
- package/dist/services/evidence/evidence-generator.js +86 -49
- package/dist/services/final-review/final-review-service.d.ts +9 -0
- package/dist/services/final-review/final-review-service.js +36 -12
- package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
- package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
- package/dist/services/hooks/auto-compact-hook-install.js +8 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
- package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
- package/dist/services/ide/ide-registry.d.ts +31 -0
- package/dist/services/ide/ide-registry.js +35 -0
- package/dist/services/ide/ide-types.d.ts +59 -0
- package/dist/services/job/job-state-store.js +7 -0
- package/dist/services/lint/detect-eslint.js +2 -2
- package/dist/services/lint/eslint-runner.js +3 -1
- package/dist/services/loop/evaluator-dispatcher.js +2 -1
- package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
- package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
- package/dist/services/memory/project-memory-service/store/paths.js +15 -6
- package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
- package/dist/services/prd/best-practice-auto-trigger.js +1 -0
- package/dist/services/prd/handoff-auto-regen.js +31 -27
- package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
- package/dist/services/prd/handoff-frontmatter.js +75 -0
- package/dist/services/prd/handoff-service.d.ts +41 -2
- package/dist/services/prd/handoff-service.js +81 -8
- package/dist/services/prd/handoff-types.d.ts +3 -2
- package/dist/services/prd/handoff-types.js +3 -2
- package/dist/services/qa/qa-business-review-state.js +9 -0
- package/dist/services/release/version-precheck-service.d.ts +2 -1
- package/dist/services/release/version-precheck-service.js +82 -12
- package/dist/services/runtime/vendor-adapter.d.ts +29 -4
- package/dist/services/runtime/vendors/claude-code.js +1 -1
- package/dist/services/runtime/vendors/codex.js +1 -1
- package/dist/services/runtime/vendors/copilot.js +1 -1
- package/dist/services/sc/sc-service.js +1 -1
- package/dist/services/scan/diff-scope-service.js +2 -2
- package/dist/services/scan/file-size-scan.js +2 -2
- package/dist/services/scan/karpathy-service.js +2 -2
- package/dist/services/scan/orphan-service.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -2
- package/dist/services/session/session-checkpoint-service.js +8 -0
- package/dist/services/skill/resume-detector.js +29 -11
- package/dist/services/skillhub/tar-runtime.js +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +14 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +99 -3
- package/dist/services/skills/hooks-settings-service.d.ts +12 -0
- package/dist/services/skills/hooks-settings-service.js +91 -14
- package/dist/services/skills/session-start-hook-constants.d.ts +86 -0
- package/dist/services/skills/session-start-hook-constants.js +86 -0
- package/dist/services/skills/skill-presence-service.js +9 -0
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +31 -12
- package/dist/services/slice/slice-decompose-runners.js +2 -1
- package/dist/services/slice/slice-review-state.js +8 -0
- package/dist/services/upgrade/upgrade-service.js +1 -0
- package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
- package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
- package/dist/services/workflow/pipeline-verify-service.js +24 -23
- package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
- package/dist/services/workflow/workflow-skip-service.js +2 -1
- package/dist/services/workspace/claude-settings-template.d.ts +56 -8
- package/dist/services/workspace/claude-settings-template.js +98 -20
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-claude-settings-materializer.js +124 -9
- package/dist/services/workspace/workspace-service.js +8 -0
- package/dist/services/worktree/host-worktree-reconciler.js +1 -0
- package/dist/services/worktree/long-path-cleanup.js +3 -2
- package/dist/shared/process.js +1 -1
- package/package.json +6 -6
- package/scripts/install-skills.mjs +1 -0
- package/scripts/watch.mjs +3 -1
- package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +8 -6
- package/skills/bee/peaks-qa/SKILL.md +7 -7
- package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
- package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
- package/skills/bee/peaks-rd/SKILL.md +10 -8
- package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
- package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-audit/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +3 -3
- package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
- package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +13 -7
- package/skills/peaks-code/references/runbook.md +3 -2
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
- package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
- package/skills/peaks-content/SKILL.md +1 -1
- package/skills/peaks-doctor/SKILL.md +1 -1
- package/skills/peaks-final-review/SKILL.md +1 -1
- package/skills/peaks-ide/SKILL.md +1 -1
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +1 -1
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-sop/SKILL.md +1 -1
- package/skills/peaks-status/SKILL.md +1 -1
- package/skills/peaks-test/SKILL.md +1 -1
|
@@ -25,15 +25,26 @@ export async function dispatch(f) {
|
|
|
25
25
|
if (f.noThrottle) {
|
|
26
26
|
warnings.push('user-overrode: --no-throttle (peak runtime may exceed performance ceiling)');
|
|
27
27
|
}
|
|
28
|
-
//
|
|
29
|
-
// peaks-loop-internal-runtime/dispatch.dispatchDetached,
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
28
|
+
// The vendor CLI's ChildProcess is owned by
|
|
29
|
+
// peaks-loop-internal-runtime/dispatch.dispatchDetached, and this handler
|
|
30
|
+
// deliberately attaches NO 'error' listener to it.
|
|
31
|
+
//
|
|
32
|
+
// The handler that used to live here — an 'error' listener attached to
|
|
33
|
+
// `r.child` after the await — could never fire. `ProcessSupervisor.spawn`
|
|
34
|
+
// attaches its own 'error' + 'spawn' listeners in the SAME synchronous turn
|
|
35
|
+
// the child is created, and `dispatchDetached` awaits the resulting `settled` promise
|
|
36
|
+
// before returning. A missing binary is emitted on the nextTick queue,
|
|
37
|
+
// which drains BEFORE the awaiting caller resumes — so by the time this
|
|
38
|
+
// function holds `r`, the event has already been consumed and turned into
|
|
39
|
+
// the typed `spawnError` value below. Attaching a listener here is not
|
|
40
|
+
// merely late; it is unreachable by construction.
|
|
36
41
|
const sid = process.env.PEAKS_SESSION_ID ?? 'local';
|
|
42
|
+
// `spawnError` is optional in this annotation, not because the value is
|
|
43
|
+
// uncertain but because the type this resolves against is: the workspace
|
|
44
|
+
// package's `types` entry points at `dist/index.d.ts`, and a `dist` built
|
|
45
|
+
// before the field was added does not declare it. Reading it defensively
|
|
46
|
+
// (`?? null`, below) is correct in both worlds; requiring it here would make
|
|
47
|
+
// the handler fail to compile against a stale build.
|
|
37
48
|
let r;
|
|
38
49
|
r = await dispatchDetached({
|
|
39
50
|
sid,
|
|
@@ -46,15 +57,17 @@ export async function dispatch(f) {
|
|
|
46
57
|
runtimeDir: `.peaks/_runtime/${sid}/detached`,
|
|
47
58
|
subAgentsDir: `.peaks/_sub_agents/${sid}`,
|
|
48
59
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
// The launch outcome is the envelope's `ok`, not a footnote. A vendor CLI
|
|
61
|
+
// that is not installed is an expected environment, and the dispatch record
|
|
62
|
+
// already says `status: 'failed'`; the envelope used to say `ok: true` with
|
|
63
|
+
// `pid: -1` and a "⏳ Spawning …" hint, so the two surfaces disagreed and the
|
|
64
|
+
// orchestrator read a launch that never happened as a running one.
|
|
65
|
+
//
|
|
66
|
+
// `?? null` covers a DispatchResult-shaped value produced by a test double
|
|
67
|
+
// that omits the field; a real `dispatchDetached` always sets it.
|
|
68
|
+
const spawnError = r.spawnError ?? null;
|
|
56
69
|
return {
|
|
57
|
-
ok:
|
|
70
|
+
ok: spawnError === null,
|
|
58
71
|
command: 'sub-agent.dispatch.detached',
|
|
59
72
|
data: {
|
|
60
73
|
mode: 'detached',
|
|
@@ -63,14 +76,26 @@ export async function dispatch(f) {
|
|
|
63
76
|
dispatchRecordPath: r.dispatchRecordPath,
|
|
64
77
|
maxConcurrent,
|
|
65
78
|
noThrottle: f.noThrottle ?? false,
|
|
66
|
-
|
|
79
|
+
...(spawnError === null
|
|
80
|
+
? {
|
|
81
|
+
orchestratorVisibleHint: `⏳ Spawning detached sub-agent via ${f.vendor ?? 'claude'}: rid=${f.requestId} (ETA ~60s)`
|
|
82
|
+
}
|
|
83
|
+
: {
|
|
84
|
+
spawnError: { code: spawnError.code, message: spawnError.message },
|
|
85
|
+
orchestratorVisibleHint: `❌ Could not launch detached sub-agent via ${f.vendor ?? 'claude'}: rid=${f.requestId} (${spawnError.code})`
|
|
86
|
+
}),
|
|
67
87
|
expectedCompletionSeconds: 60,
|
|
68
88
|
},
|
|
69
89
|
warnings,
|
|
70
|
-
nextActions:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
nextActions: spawnError === null
|
|
91
|
+
? [
|
|
92
|
+
'Sub-agent runs as detached OS process. Status at .peaks/_runtime/<sid>/detached/<rid>/status.json',
|
|
93
|
+
'Use `peaks sub-agent list --mode detached` to monitor.',
|
|
94
|
+
'Run `peaks sub-agent cleanup --orphan` to reap orphan processes (RL-15: user-only decision).'
|
|
95
|
+
]
|
|
96
|
+
: [
|
|
97
|
+
`The ${f.vendor ?? 'claude'} CLI is not runnable from this shell (${spawnError.code}); nothing was launched and no sub-agent is running.`,
|
|
98
|
+
'Either install the vendor CLI on PATH, or re-dispatch without --mode detached to use the in-process path.'
|
|
99
|
+
]
|
|
75
100
|
};
|
|
76
101
|
}
|
|
@@ -2,9 +2,20 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { fail, getErrorMessage, ok } from 'peaks-loop-shared/result';
|
|
4
4
|
import { getCurrentSessionId } from '../../services/skills/skill-presence-service.js';
|
|
5
|
+
import { isUnsafePathInput } from '../../shared/path-safety.js';
|
|
5
6
|
import { printResult } from '../cli-helpers.js';
|
|
6
7
|
const REGISTRATIONS_FILE = 'service-registrations.json';
|
|
7
8
|
function registrationsPath(input) {
|
|
9
|
+
// Both id axes reach this join. `dispatchId` is `--dispatch-id` (falling back
|
|
10
|
+
// to PEAKS_DISPATCH_ID), and `sessionId` is the session binding; neither was
|
|
11
|
+
// checked, and `register` writes through this path. Guarding the whole join
|
|
12
|
+
// rather than only the flag keeps the function's contract single-stated.
|
|
13
|
+
if (isUnsafePathInput(input.dispatchId)) {
|
|
14
|
+
throw new Error(`Invalid dispatch id: ${input.dispatchId} (must be a single path segment)`);
|
|
15
|
+
}
|
|
16
|
+
if (isUnsafePathInput(input.sessionId)) {
|
|
17
|
+
throw new Error(`Invalid session id: ${input.sessionId} (must be a single path segment)`);
|
|
18
|
+
}
|
|
8
19
|
return join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'dispatch', input.dispatchId, REGISTRATIONS_FILE);
|
|
9
20
|
}
|
|
10
21
|
function readAll(file) {
|
|
@@ -203,7 +203,8 @@ export function runRunner(framework, argv, projectRoot, deps = {}) {
|
|
|
203
203
|
const proc = spawnFn(resolution.command, resolution.args, {
|
|
204
204
|
cwd: projectRoot,
|
|
205
205
|
env: process.env,
|
|
206
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
206
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
207
|
+
windowsHide: true
|
|
207
208
|
});
|
|
208
209
|
let stdout = '';
|
|
209
210
|
let stderr = '';
|
|
@@ -4,11 +4,51 @@ import { aggregateVerdict } from '../../services/verdict/verdict-aggregator.js';
|
|
|
4
4
|
import { parseKarpathyEnvelope, parseMutEnvelope, parseQaEnvelope, parseSecurityEnvelope, parsePerfEnvelope, envelopesToAggregatorInput } from '../../services/verdict/envelopes.js';
|
|
5
5
|
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
6
6
|
import { fail, ok } from 'peaks-loop-shared/result';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { isUnsafePathInput } from '../../shared/path-safety.js';
|
|
8
|
+
import { DEFAULT_REQUEST_TYPE } from '../../services/artifacts/artifact-prerequisites.js';
|
|
9
|
+
import { REQUEST_ID_PATTERN } from '../../services/artifacts/request-artifact-service.js';
|
|
10
|
+
import { contractEvidencePaths } from '../../services/workflow/pipeline-verify-gate-support.js';
|
|
11
|
+
// Slice `2026-09-14-audit-artifact-rid-scoping`: the audit/review evidence
|
|
12
|
+
// filenames carry the rid. The ridless names are the pre-rid locations and
|
|
13
|
+
// stay readable during the back-compat window. `mut/` is NOT rid-scoped —
|
|
14
|
+
// see `MUT_REPORT` in `artifact-prerequisites.ts`: no producer in the repo
|
|
15
|
+
// can write a rid-scoped mut report, so probing a templated name here would
|
|
16
|
+
// look for a file that no writer can create.
|
|
17
|
+
//
|
|
18
|
+
// The candidate lists are read FROM the contract — the same
|
|
19
|
+
// `contractEvidencePaths()` the verify pipeline resolves with — rather than
|
|
20
|
+
// hand-written here. Hand-written lists held two of the contract's THREE
|
|
21
|
+
// tiers, so a session whose security/perf evidence sits at the oldest tier
|
|
22
|
+
// (`rd/security-review.md`, `rd/perf-baseline.md` — the live shape of three
|
|
23
|
+
// sessions on disk) reported `block` while `peaks request transition`
|
|
24
|
+
// accepted the same tree, and the hint named a legacy location it had never
|
|
25
|
+
// probed. One table, one answer.
|
|
10
26
|
const MUT_REL = 'mut/mut-report.json';
|
|
11
27
|
const QA_REL = 'qa/test-reports';
|
|
28
|
+
/**
|
|
29
|
+
* The declared tiers for one artifact name, primary first. Probed by the name
|
|
30
|
+
* this command used before the rid-scoping (the table matches it as either
|
|
31
|
+
* the primary or a legacy tier), so the lookup survives the primary moving.
|
|
32
|
+
* `verdict aggregate` is request-type-agnostic and every fanout-trigger type
|
|
33
|
+
* declares these artifacts identically, so the default type's list is the
|
|
34
|
+
* full one; a name the table does not carry falls back to itself.
|
|
35
|
+
*/
|
|
36
|
+
function contractRels(probeName) {
|
|
37
|
+
return contractEvidencePaths('rd', 'qa-handoff', DEFAULT_REQUEST_TYPE, probeName) ?? [probeName];
|
|
38
|
+
}
|
|
39
|
+
const SECURITY_RELS = contractRels('audit/security.md');
|
|
40
|
+
const PERF_RELS = contractRels('audit/perf.md');
|
|
41
|
+
const KARPATHY_RELS = contractRels('rd/karpathy-review.md');
|
|
42
|
+
/**
|
|
43
|
+
* Sources whose absence must not read as `pass`. `mut` is excluded on
|
|
44
|
+
* purpose: `MUT_REPORT` carries `backCompat: true`, so the contract itself
|
|
45
|
+
* treats a missing mut report as a warning rather than a gate failure.
|
|
46
|
+
*/
|
|
47
|
+
const REQUIRED_SOURCES = [
|
|
48
|
+
{ key: 'security', source: 'security-audit', rels: SECURITY_RELS },
|
|
49
|
+
{ key: 'perf', source: 'perf-audit', rels: PERF_RELS },
|
|
50
|
+
{ key: 'karpathy', source: 'karpathy-reviewer', rels: KARPATHY_RELS }
|
|
51
|
+
];
|
|
12
52
|
export function registerVerdictAggregateCommands(program, io) {
|
|
13
53
|
const verdict = program.command('verdict').description('Aggregate the 5 envelope sources feeding peaks-code verdict logic');
|
|
14
54
|
addJsonOption(verdict
|
|
@@ -25,6 +65,27 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
25
65
|
process.exitCode = 1;
|
|
26
66
|
return;
|
|
27
67
|
}
|
|
68
|
+
// The rid SELECTS THE FILE, so it has to be one path segment. This is the
|
|
69
|
+
// repo's own request-id guard — the one `request-artifact-service.ts`
|
|
70
|
+
// applies to every request artifact and the reason `peaks workflow
|
|
71
|
+
// verify-pipeline` refuses a hostile rid — and it was not applied to the
|
|
72
|
+
// joins below. Measured before it was: `--from-rid
|
|
73
|
+
// '../../../../../../ONLY-HERE'` read `<project>/ONLY-HERE.md`, a file
|
|
74
|
+
// outside `.peaks/`, as this slice's security AND perf evidence.
|
|
75
|
+
if (!REQUEST_ID_PATTERN.test(rid)) {
|
|
76
|
+
printResult(io, fail('verdict.aggregate', 'RID_INVALID', `Invalid request id: ${rid} (expected letters, digits, dots, underscores, or dashes)`, {}, ['Pass the rid of the slice whose evidence you want aggregated, e.g. 2026-09-14-some-slug']), options.json);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// `--sid` names the session DIRECTORY and is the same axis. Reject
|
|
81
|
+
// anything that is not one safe path segment rather than pinning a
|
|
82
|
+
// format: the default here is the deliberately non-format value
|
|
83
|
+
// `default`.
|
|
84
|
+
if (isUnsafePathInput(sid)) {
|
|
85
|
+
printResult(io, fail('verdict.aggregate', 'SID_INVALID', `Invalid session id: ${sid} (must be a single path segment)`, {}, ['Pass the session id, e.g. 2026-09-14-session-abc123']), options.json);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
28
89
|
try {
|
|
29
90
|
const sources = {
|
|
30
91
|
// v2.13.3 AC-1: use the canonical markdown-aware parser from
|
|
@@ -34,9 +95,9 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
34
95
|
// returned `verdict: warn, violations: []` — the aggregator
|
|
35
96
|
// then produced `reasons: []` and `verdict: 'pass'` even when
|
|
36
97
|
// the audit had flagged HIGH violations.
|
|
37
|
-
security: readAudit(projectRoot, sid,
|
|
38
|
-
perf: readAudit(projectRoot, sid,
|
|
39
|
-
karpathy: readAudit(projectRoot, sid,
|
|
98
|
+
security: readAudit(projectRoot, sid, rid, SECURITY_RELS, parseSecurityEnvelope),
|
|
99
|
+
perf: readAudit(projectRoot, sid, rid, PERF_RELS, parsePerfEnvelope),
|
|
100
|
+
karpathy: readAudit(projectRoot, sid, rid, KARPATHY_RELS, (m) => parseKarpathyEnvelope(m)),
|
|
40
101
|
mut: await readMut(projectRoot, sid),
|
|
41
102
|
qa: readQa(projectRoot, sid, rid)
|
|
42
103
|
};
|
|
@@ -55,7 +116,26 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
55
116
|
mut: sources.mut !== null ? 'present' : 'missing',
|
|
56
117
|
qa: sources.qa !== null ? 'present' : 'missing'
|
|
57
118
|
};
|
|
58
|
-
|
|
119
|
+
// Fail closed. `aggregateVerdict()` starts its top-level verdict at
|
|
120
|
+
// `'pass'`, so a source that could not be read simply never enters the
|
|
121
|
+
// input and the run reports green. Before this slice the bare paths
|
|
122
|
+
// were the ones the writers wrote; after it the writers moved and this
|
|
123
|
+
// reader did not — so a missing security or perf audit became a `pass`
|
|
124
|
+
// for the loop evaluator (`services/loop/evaluator-dispatcher.ts`). A
|
|
125
|
+
// guard that cannot read its input must not report success.
|
|
126
|
+
const missingRequired = REQUIRED_SOURCES.filter((entry) => sources[entry.key] === null);
|
|
127
|
+
const missingReasons = missingRequired.map((entry) => ({
|
|
128
|
+
source: entry.source,
|
|
129
|
+
sources: [entry.source],
|
|
130
|
+
signal: 'block',
|
|
131
|
+
kind: 'missing-evidence',
|
|
132
|
+
hint: `${entry.rels.map((rel) => rel.replace('<rid>', rid)).join(' | ')} could not be read under .peaks/_runtime/${sid}/`
|
|
133
|
+
}));
|
|
134
|
+
printResult(io, ok('verdict.aggregate', {
|
|
135
|
+
verdict: missingReasons.length > 0 ? 'block' : result.verdict,
|
|
136
|
+
reasons: [...missingReasons, ...result.reasons],
|
|
137
|
+
sources: sourceFlags
|
|
138
|
+
}, [], []), options.json);
|
|
59
139
|
}
|
|
60
140
|
catch (error) {
|
|
61
141
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -64,12 +144,14 @@ export function registerVerdictAggregateCommands(program, io) {
|
|
|
64
144
|
}
|
|
65
145
|
});
|
|
66
146
|
}
|
|
67
|
-
function readAudit(projectRoot, sid,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
147
|
+
function readAudit(projectRoot, sid, rid, rels, parse) {
|
|
148
|
+
// Canonical rid-scoped location first, then the pre-rid locations.
|
|
149
|
+
for (const rel of rels) {
|
|
150
|
+
const path = join(projectRoot, '.peaks', '_runtime', sid, rel.replace('<rid>', rid));
|
|
151
|
+
if (existsSync(path))
|
|
152
|
+
return parse(readFileSync(path, 'utf8'));
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
73
155
|
}
|
|
74
156
|
async function readMut(projectRoot, sid) {
|
|
75
157
|
const path = join(projectRoot, '.peaks', '_runtime', sid, MUT_REL);
|
|
@@ -59,7 +59,7 @@ function detectHypervisor(requested) {
|
|
|
59
59
|
break;
|
|
60
60
|
}
|
|
61
61
|
try {
|
|
62
|
-
const v = execSync(`${binary} --version`, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
|
|
62
|
+
const v = execSync(`${binary} --version`, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', windowsHide: true });
|
|
63
63
|
if (requested === 'kvm' && !existsSync('/dev/kvm')) {
|
|
64
64
|
return { ok: false, stderr: 'KVM kernel module not loaded (/dev/kvm absent)' };
|
|
65
65
|
}
|
|
@@ -87,27 +87,27 @@ function spawnVmWithHypervisor(args) {
|
|
|
87
87
|
</domain>`;
|
|
88
88
|
const xmlPath = `${args.workdir}/.peaks-vm-${args.leaseId}.xml`;
|
|
89
89
|
require('node:fs').writeFileSync(xmlPath, xml, 'utf8');
|
|
90
|
-
const out = execSync(`virsh create ${xmlPath}`, { cwd: args.workdir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
|
|
90
|
+
const out = execSync(`virsh create ${xmlPath}`, { cwd: args.workdir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', windowsHide: true });
|
|
91
91
|
return { vmId: out.trim() };
|
|
92
92
|
}
|
|
93
93
|
if (args.hypervisor === 'hyperkit') {
|
|
94
|
-
const out = execSync(`hvftool create --image ${args.image} --mount ${args.mount}:/work --label peaks.leaseId=${args.leaseId} --label peaks.rid=${args.rid} --entrypoint sleep -- infinity`, { cwd: args.workdir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
|
|
94
|
+
const out = execSync(`hvftool create --image ${args.image} --mount ${args.mount}:/work --label peaks.leaseId=${args.leaseId} --label peaks.rid=${args.rid} --entrypoint sleep -- infinity`, { cwd: args.workdir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', windowsHide: true });
|
|
95
95
|
return { vmId: out.trim() };
|
|
96
96
|
}
|
|
97
97
|
// hyperv: the hvc shim writes a vhdx + emits the new VM id.
|
|
98
|
-
const out = execSync(`hvc create --image ${args.image} --mount ${args.mount} --label peaks.leaseId=${args.leaseId} --label peaks.rid=${args.rid}`, { cwd: args.workdir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
|
|
98
|
+
const out = execSync(`hvc create --image ${args.image} --mount ${args.mount} --label peaks.leaseId=${args.leaseId} --label peaks.rid=${args.rid}`, { cwd: args.workdir, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', windowsHide: true });
|
|
99
99
|
return { vmId: out.trim() };
|
|
100
100
|
}
|
|
101
101
|
function destroyVmWithHypervisor(args) {
|
|
102
102
|
try {
|
|
103
103
|
if (args.hypervisor === 'kvm') {
|
|
104
|
-
execSync(`virsh destroy ${args.vmId}`, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
104
|
+
execSync(`virsh destroy ${args.vmId}`, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
105
105
|
}
|
|
106
106
|
else if (args.hypervisor === 'hyperkit') {
|
|
107
|
-
execSync(`hvftool stop ${args.vmId}`, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
107
|
+
execSync(`hvftool stop ${args.vmId}`, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
108
108
|
}
|
|
109
109
|
else {
|
|
110
|
-
execSync(`hvc stop ${args.vmId}`, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
110
|
+
execSync(`hvc stop ${args.vmId}`, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
111
111
|
}
|
|
112
112
|
return true;
|
|
113
113
|
}
|
|
@@ -420,7 +420,7 @@ export function registerWorkflowCommands(program, io) {
|
|
|
420
420
|
// semantics, role-based auth).
|
|
421
421
|
addJsonOption(workflow
|
|
422
422
|
.command('skip')
|
|
423
|
-
.description('Skip specific gates for a request (RD/QA). Use --dry-run to preview without writing. Allowed gate names: QA / RD (phase shortcuts) or specific gate names (rd-request-exists,
|
|
423
|
+
.description('Skip specific gates for a request (RD/QA). Use --dry-run to preview without writing. Allowed gate names: QA / RD (phase shortcuts) or specific gate names (rd-request-exists, prd-handoff, bug-analysis, code-review, security-review, perf-baseline, qa-request-exists, test-cases, test-report). Three rules apply: (1) only docs/config/chore slices can skip; (2) skip is one-time per rid; (3) script callers must also pass --i-have-reviewed.')
|
|
424
424
|
.requiredOption('--rid <rid>', 'request identifier')
|
|
425
425
|
.requiredOption('--project <path>', 'project root path')
|
|
426
426
|
.requiredOption('--gates <list>', 'comma-separated gate names (e.g. "QA" or "QA,slice-check" or "code-review,security-review")')
|
|
@@ -22,7 +22,7 @@ import { initWorkspace, InvalidSessionIdError, ConflictingSessionError, LegacyCh
|
|
|
22
22
|
// session-id-shaped directories at the `.peaks/_runtime/` sibling
|
|
23
23
|
// level.
|
|
24
24
|
import { ensureSessionWithRotation } from '../../../services/session/session-manager.js';
|
|
25
|
-
import {
|
|
25
|
+
import { resolveWritableProjectRoot, UnsafeProjectRootError } from '../../../services/config/config-safety.js';
|
|
26
26
|
import { applyHookInstall, readHookStatus } from '../../../services/skills/hooks-settings-service.js';
|
|
27
27
|
import { clearStalePresenceOnRotation } from '../../../services/skills/skill-presence-service.js';
|
|
28
28
|
import { gcStalePresenceLeases } from '../../../services/skills/presence-lease-service.js';
|
|
@@ -69,7 +69,12 @@ export function registerWorkspaceInitCommand(workspace, io) {
|
|
|
69
69
|
// (the same regression that produced prompt-project/.peaks/ in
|
|
70
70
|
// the 5/27-5/29 sessions). When startPath is not inside any
|
|
71
71
|
// git repo, the helper falls through to the cwd verbatim.
|
|
72
|
-
|
|
72
|
+
// `resolveWritableProjectRoot` = `resolveCanonicalProjectRoot` plus the
|
|
73
|
+
// home-directory refusal. It has to be this one, and it has to run here:
|
|
74
|
+
// `ensureSessionWithRotation` below WRITES (it mints/binds a session), so
|
|
75
|
+
// a guard placed after it would leave a `.peaks/_runtime/` tree in the
|
|
76
|
+
// user's home even though the init was refused.
|
|
77
|
+
const projectRoot = resolveWritableProjectRoot(options.project);
|
|
73
78
|
// Slice 018: outer-session-mismatch auto-rotation. When the
|
|
74
79
|
// user did NOT pass --session-id explicitly, run
|
|
75
80
|
// `ensureSessionWithRotation` so the binding is rotated on
|
|
@@ -408,6 +413,23 @@ export function registerWorkspaceInitCommand(workspace, io) {
|
|
|
408
413
|
}, warningsForEnvelope, nextActions), options.json);
|
|
409
414
|
}
|
|
410
415
|
catch (error) {
|
|
416
|
+
if (error instanceof UnsafeProjectRootError) {
|
|
417
|
+
// A typed refusal, not a crash: "you are standing in your home
|
|
418
|
+
// directory, which is not a project" is a thing the caller can fix in
|
|
419
|
+
// one step, and the envelope has to say which directory was refused and
|
|
420
|
+
// what to pass instead. `WORKSPACE_INIT_FAILED` below would have buried
|
|
421
|
+
// it as an unexplained failure.
|
|
422
|
+
printResult(io, fail('workspace.init', 'UNSAFE_PROJECT_ROOT', error.message, {
|
|
423
|
+
resolvedProjectRoot: error.projectRoot,
|
|
424
|
+
projectOption: options.project ?? null
|
|
425
|
+
}, [
|
|
426
|
+
'Pass --project <path-to-your-project> instead of a path that resolves to your home directory.',
|
|
427
|
+
'This command creates .peaks/, .gitignore, .claude/settings.local.json and a codegraph index; none of those belong in $HOME.',
|
|
428
|
+
'If you meant to initialize the current directory, cd into a project directory first — peaks will not write into the home directory itself.'
|
|
429
|
+
]), options.json);
|
|
430
|
+
process.exitCode = 1;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
411
433
|
if (error instanceof InvalidSessionIdError) {
|
|
412
434
|
printResult(io, fail('workspace.init', error.code, error.message, { sessionId: options.sessionId }, ['Use a date-prefixed kebab slug like 2026-05-25-add-user-auth']), options.json);
|
|
413
435
|
process.exitCode = 1;
|
|
@@ -91,7 +91,7 @@ export function registerWorktreeLeaseCommands(auth, io) {
|
|
|
91
91
|
// and authorize this very `git worktree add`; for Part 1 we still
|
|
92
92
|
// require a current `peaks worktree auth grant` token to remain
|
|
93
93
|
// consistent with the slice-027 hard gate contract.
|
|
94
|
-
execSync(`git worktree add "${wtPath}" -b "${branch}"`, { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8' });
|
|
94
|
+
execSync(`git worktree add "${wtPath}" -b "${branch}"`, { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8', windowsHide: true });
|
|
95
95
|
atomicWriteJson(leaseFilePath(joinPathSession(projectRoot, sessionId), leaseId), lease);
|
|
96
96
|
// Part 4.A: emit spawn metric (fire-and-forget; never blocks).
|
|
97
97
|
emitLeaseEvent({
|
|
@@ -167,7 +167,7 @@ export function registerWorktreeLeaseCommands(auth, io) {
|
|
|
167
167
|
// released.
|
|
168
168
|
let gitWorktreeRemoveFailed = false;
|
|
169
169
|
try {
|
|
170
|
-
execSync(`git worktree remove --force "${lease.path}"`, { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8' });
|
|
170
|
+
execSync(`git worktree remove --force "${lease.path}"`, { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8', windowsHide: true });
|
|
171
171
|
}
|
|
172
172
|
catch {
|
|
173
173
|
gitWorktreeRemoveFailed = true;
|
|
@@ -379,14 +379,14 @@ export function registerWorktreeLeaseCommands(auth, io) {
|
|
|
379
379
|
// already gone we still mark the lease gc.
|
|
380
380
|
let gitWorktreeRemoveFailed = false;
|
|
381
381
|
try {
|
|
382
|
-
execSync(`git worktree remove --force "${updated.path}"`, { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8' });
|
|
382
|
+
execSync(`git worktree remove --force "${updated.path}"`, { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8', windowsHide: true });
|
|
383
383
|
}
|
|
384
384
|
catch {
|
|
385
385
|
gitWorktreeRemoveFailed = true;
|
|
386
386
|
}
|
|
387
387
|
// `git worktree prune` clears any stale admin entries. Best-effort.
|
|
388
388
|
try {
|
|
389
|
-
execSync('git worktree prune', { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8' });
|
|
389
|
+
execSync('git worktree prune', { cwd: projectRoot, stdio: 'pipe', encoding: 'utf8', windowsHide: true });
|
|
390
390
|
}
|
|
391
391
|
catch {
|
|
392
392
|
// ignore — prune is idempotent
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CommanderError } from 'commander';
|
|
2
2
|
import { createProgram } from './program.js';
|
|
3
3
|
import { getErrorMessage } from 'peaks-loop-shared/result';
|
|
4
|
-
import { printErrorEnvelope } from './cli-helpers.js';
|
|
4
|
+
import { printErrorEnvelope, printMissingRequiredOptionEnvelope, resolveInvokedCommandPath } from './cli-helpers.js';
|
|
5
5
|
const defaultIo = {
|
|
6
6
|
stdout: (text) => process.stdout.write(`${text}\n`),
|
|
7
7
|
stderr: (text) => process.stderr.write(`${text}\n`)
|
|
@@ -29,8 +29,8 @@ const defaultIo = {
|
|
|
29
29
|
const argv = process.argv.slice(2);
|
|
30
30
|
const hasHelp = argv.some((arg) => arg === '--help' || arg === '-h');
|
|
31
31
|
const firstPositional = argv.find((arg) => !arg.startsWith('-'));
|
|
32
|
+
const program = createProgram();
|
|
32
33
|
if (hasHelp && firstPositional !== undefined) {
|
|
33
|
-
const program = createProgram();
|
|
34
34
|
const registered = new Set(program.commands.map((c) => c.name()));
|
|
35
35
|
if (!registered.has(firstPositional)) {
|
|
36
36
|
// Defer the check by 0ms so Commander's own help handler runs first
|
|
@@ -41,7 +41,7 @@ if (hasHelp && firstPositional !== undefined) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
45
45
|
if (error instanceof CommanderError && error.code === 'commander.version') {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
@@ -59,6 +59,13 @@ createProgram().parseAsync(process.argv).catch((error) => {
|
|
|
59
59
|
if (error.code === 'commander.help' || error.code === 'commander.helpDisplayed') {
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
+
if (error.code === 'commander.missingMandatoryOptionValue') {
|
|
63
|
+
// See `printMissingRequiredOptionEnvelope` — the same builder backs the
|
|
64
|
+
// in-process test runner, so the envelope a test asserts is the envelope
|
|
65
|
+
// a user gets.
|
|
66
|
+
printMissingRequiredOptionEnvelope(defaultIo, resolveInvokedCommandPath(program, argv), getErrorMessage(error));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
62
69
|
if (error.code === 'commander.missingArgument' || error.code === 'commander.unknownCommand' || error.code === 'commander.unknownOption') {
|
|
63
70
|
// Emit a `COMMAND_NOT_FOUND` JSON envelope for the unknown-command
|
|
64
71
|
// path. The error text already went to stderr via Commander's
|
package/dist/cli/program.js
CHANGED
|
@@ -17,6 +17,7 @@ import { registerWorkspaceCommands } from './commands/workspace-commands.js';
|
|
|
17
17
|
import { registerSopCommands } from './commands/sop-commands.js';
|
|
18
18
|
import { registerSkillVisibilityCommand } from './commands/skill-visibility.js';
|
|
19
19
|
import { registerPrimerCommand } from './commands/primer-command.js';
|
|
20
|
+
import { registerReinjectCommand } from './commands/reinject-command.js';
|
|
20
21
|
import { applyRetention, cleanupEccCache } from '../services/log/retention.js';
|
|
21
22
|
import { writeLogEntry, maybeWriteStderr } from '../services/log/logger.js';
|
|
22
23
|
import { printSuperCommandCatalog } from './cli-helpers.js';
|
|
@@ -217,5 +218,9 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
217
218
|
// `program.command('session primer')` (that registers a single
|
|
218
219
|
// literal command name, not a child of the session group).
|
|
219
220
|
registerPrimerCommand(program, io);
|
|
221
|
+
// rid 2026-09-13-a2-post-compact-reinject: register `peaks session reinject`
|
|
222
|
+
// as a sibling child of the same `session` group. It is the transport for
|
|
223
|
+
// the SessionStart (matcher `compact`) hook's post-compaction card.
|
|
224
|
+
registerReinjectCommand(program, io);
|
|
220
225
|
return program;
|
|
221
226
|
}
|
|
@@ -59,7 +59,7 @@ export function runHookGuardSubprocess(prompt) {
|
|
|
59
59
|
'sub-agent-dispatch-guard',
|
|
60
60
|
'--prompt', prompt,
|
|
61
61
|
'--json'
|
|
62
|
-
], { encoding: 'utf8' });
|
|
62
|
+
], { encoding: 'utf8', windowsHide: true });
|
|
63
63
|
if (result.status !== 0) {
|
|
64
64
|
// Fallback: allow (don't block the dispatch on a guard subprocess failure).
|
|
65
65
|
return {
|
|
@@ -53,7 +53,7 @@ class ProcessVendorAdapter {
|
|
|
53
53
|
if (args.force === true)
|
|
54
54
|
argv.push('--force');
|
|
55
55
|
return new Promise((resolveRun) => {
|
|
56
|
-
const proc = spawn(this.binary, argv, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
56
|
+
const proc = spawn(this.binary, argv, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
57
57
|
let stdout = '';
|
|
58
58
|
let stderr = '';
|
|
59
59
|
proc.stdout.on('data', (chunk) => { stdout += chunk.toString('utf8'); });
|
|
@@ -29,18 +29,36 @@ export type ArtifactPrerequisite = {
|
|
|
29
29
|
*/
|
|
30
30
|
headingMustContain?: ReadonlyArray<string>;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
32
|
+
* The immediately previous location of this artifact. When
|
|
33
|
+
* `relativePath` does not resolve on disk, the resolver tries this
|
|
34
|
+
* path before reporting the prereq as missing.
|
|
35
|
+
*
|
|
36
|
+
* This field is a published surface, not just a private hint:
|
|
37
|
+
* `verify-pipeline`'s `contractEvidencePaths()`
|
|
38
|
+
* (`src/services/workflow/pipeline-verify-gate-support.ts`) reads it
|
|
39
|
+
* to build its probe list, so it must keep meaning "the one path a
|
|
40
|
+
* previous release wrote". Do not repurpose it as a general list —
|
|
41
|
+
* add older tiers to `legacyRelativePaths` instead.
|
|
38
42
|
*
|
|
39
43
|
* Body checks (`mustContain` / `mustContainAny` /
|
|
40
44
|
* `headingMustContain`) apply to whichever path resolved — the
|
|
41
|
-
* gate does not distinguish which path served the file.
|
|
45
|
+
* gate does not distinguish which path served the file. That is
|
|
46
|
+
* deliberate: a legacy hit must keep the gate open, or existing
|
|
47
|
+
* sessions would fail the transition on upgrade.
|
|
42
48
|
*/
|
|
43
49
|
legacyRelativePath?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Further, older locations, tried **in declared order** after
|
|
52
|
+
* `legacyRelativePath`. Slice `2026-09-14-audit-artifact-rid-scoping`
|
|
53
|
+
* added this because the two audit artifacts have a two-release
|
|
54
|
+
* history behind their rid-scoped path: the pre-rid-scoping
|
|
55
|
+
* `audit/security.md` (v2.12.0) and, behind it, the v2.11.x
|
|
56
|
+
* `rd/security-review.md`. Three sessions on disk
|
|
57
|
+
* (`2026-09-06-session-a87ca4`, `2026-09-10-session-528a63`,
|
|
58
|
+
* `2026-09-12-session-e37ef0`) hold their security evidence only at
|
|
59
|
+
* the last of those, so the oldest tier still has to resolve.
|
|
60
|
+
*/
|
|
61
|
+
legacyRelativePaths?: ReadonlyArray<string>;
|
|
44
62
|
};
|
|
45
63
|
export type PrerequisiteCheckResult = {
|
|
46
64
|
ok: boolean;
|
|
@@ -75,4 +93,17 @@ export type CheckPrerequisitesOptions = {
|
|
|
75
93
|
requestType?: RequestType;
|
|
76
94
|
};
|
|
77
95
|
export declare function getPrerequisitesFor(role: RequestArtifactRole, newState: RequestArtifactState, requestType?: RequestType): ReadonlyArray<ArtifactPrerequisite>;
|
|
96
|
+
/**
|
|
97
|
+
* The contract's BODY checks for one prerequisite, applied to `body`. Returns
|
|
98
|
+
* one human-readable message per failed check, empty when the body satisfies
|
|
99
|
+
* the contract.
|
|
100
|
+
*
|
|
101
|
+
* Exported because this contract has a SECOND enforcer: `peaks workflow
|
|
102
|
+
* verify-pipeline` probes the same table for the paths it checks
|
|
103
|
+
* (`pipeline-verify-gate-support.ts`), and a resolver that probed only
|
|
104
|
+
* `existsSync` passed files this function rejects — a guard laxer than the
|
|
105
|
+
* contract it claims to read. Both callers now share this one implementation,
|
|
106
|
+
* so the checker cannot drift from the table again.
|
|
107
|
+
*/
|
|
108
|
+
export declare function prerequisiteBodyViolations(prerequisite: ArtifactPrerequisite, body: string): string[];
|
|
78
109
|
export declare function checkPrerequisites(options: CheckPrerequisitesOptions): Promise<PrerequisiteCheckResult>;
|