peaks-cli 1.3.2 → 1.3.4
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/README.md +6 -2
- package/dist/src/cli/commands/core-artifact-commands.js +6 -3
- package/dist/src/cli/commands/gate-commands.js +28 -19
- package/dist/src/cli/commands/hook-handle.d.ts +17 -0
- package/dist/src/cli/commands/hook-handle.js +111 -0
- package/dist/src/cli/commands/hooks-commands.js +72 -21
- package/dist/src/cli/commands/progress-commands.js +9 -2
- package/dist/src/cli/commands/progress-start-spawn.js +30 -4
- package/dist/src/cli/commands/project-commands.js +8 -4
- package/dist/src/cli/commands/statusline-commands.js +75 -17
- package/dist/src/cli/commands/sub-agent-commands.d.ts +5 -0
- package/dist/src/cli/commands/sub-agent-commands.js +488 -0
- package/dist/src/cli/commands/sub-agent-dispatch-guard.d.ts +55 -0
- package/dist/src/cli/commands/sub-agent-dispatch-guard.js +57 -0
- package/dist/src/cli/commands/workflow-commands.js +2 -1
- package/dist/src/cli/commands/workspace-commands.js +3 -0
- package/dist/src/cli/program.js +9 -0
- package/dist/src/hooks/pre-tool-use-sub-agent.d.ts +28 -0
- package/dist/src/hooks/pre-tool-use-sub-agent.js +105 -0
- package/dist/src/services/config/config-types.d.ts +1 -1
- package/dist/src/services/context/artifact-meta.d.ts +72 -0
- package/dist/src/services/context/artifact-meta.js +105 -0
- package/dist/src/services/context/context-guard.d.ts +49 -0
- package/dist/src/services/context/context-guard.js +91 -0
- package/dist/src/services/context/dispatch-context-guard.d.ts +27 -0
- package/dist/src/services/context/dispatch-context-guard.js +192 -0
- package/dist/src/services/context/headroom-client.d.ts +34 -0
- package/dist/src/services/context/headroom-client.js +117 -0
- package/dist/src/services/context/shared-channel.d.ts +92 -0
- package/dist/src/services/context/shared-channel.js +285 -0
- package/dist/src/services/context/threshold.d.ts +35 -0
- package/dist/src/services/context/threshold.js +76 -0
- package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
- package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
- package/dist/src/services/dispatch/batch-counter.d.ts +27 -0
- package/dist/src/services/dispatch/batch-counter.js +85 -0
- package/dist/src/services/dispatch/dispatch-record-writer.d.ts +93 -0
- package/dist/src/services/dispatch/dispatch-record-writer.js +261 -0
- package/dist/src/services/dispatch/heartbeat-truncator.d.ts +26 -0
- package/dist/src/services/dispatch/heartbeat-truncator.js +13 -0
- package/dist/src/services/dispatch/leak-detector.d.ts +11 -0
- package/dist/src/services/dispatch/leak-detector.js +72 -0
- package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +127 -0
- package/dist/src/services/dispatch/sub-agent-dispatcher.js +98 -0
- package/dist/src/services/ide/adapters/claude-code-adapter.d.ts +18 -0
- package/dist/src/services/ide/adapters/claude-code-adapter.js +80 -0
- package/dist/src/services/ide/adapters/trae-adapter.d.ts +42 -0
- package/dist/src/services/ide/adapters/trae-adapter.js +98 -0
- package/dist/src/services/ide/hook-protocol.d.ts +47 -0
- package/dist/src/services/ide/hook-protocol.js +74 -0
- package/dist/src/services/ide/hook-translator.d.ts +72 -0
- package/dist/src/services/ide/hook-translator.js +128 -0
- package/dist/src/services/ide/ide-detector.d.ts +10 -0
- package/dist/src/services/ide/ide-detector.js +19 -0
- package/dist/src/services/ide/ide-registry.d.ts +14 -0
- package/dist/src/services/ide/ide-registry.js +45 -0
- package/dist/src/services/ide/ide-types.d.ts +180 -0
- package/dist/src/services/ide/ide-types.js +2 -0
- package/dist/src/services/ide/resource-profile.d.ts +52 -0
- package/dist/src/services/ide/resource-profile.js +33 -0
- package/dist/src/services/ide/shared/atomic-json.d.ts +15 -0
- package/dist/src/services/ide/shared/atomic-json.js +58 -0
- package/dist/src/services/ide/shared/safe-path.d.ts +11 -0
- package/dist/src/services/ide/shared/safe-path.js +29 -0
- package/dist/src/services/memory/project-context-service.js +2 -1
- package/dist/src/services/memory/project-memory-service.js +4 -3
- package/dist/src/services/perf/perf-baseline-service.js +2 -1
- package/dist/src/services/progress/progress-service.d.ts +1 -1
- package/dist/src/services/progress/progress-service.js +18 -14
- package/dist/src/services/security/safe-settings-path.d.ts +12 -0
- package/dist/src/services/security/safe-settings-path.js +104 -0
- package/dist/src/services/session/getSessionDir.d.ts +1 -0
- package/dist/src/services/session/getSessionDir.js +27 -0
- package/dist/src/services/session/index.d.ts +1 -0
- package/dist/src/services/session/index.js +1 -0
- package/dist/src/services/signal/cancel-handler.d.ts +14 -0
- package/dist/src/services/signal/cancel-handler.js +76 -0
- package/dist/src/services/skill/resume-detector.d.ts +54 -0
- package/dist/src/services/skill/resume-detector.js +334 -0
- package/dist/src/services/skill/skill-scheduler.d.ts +40 -0
- package/dist/src/services/skill/skill-scheduler.js +53 -0
- package/dist/src/services/skills/hooks-settings-service.d.ts +47 -29
- package/dist/src/services/skills/hooks-settings-service.js +190 -144
- package/dist/src/services/skills/statusline-settings-service.d.ts +33 -6
- package/dist/src/services/skills/statusline-settings-service.js +31 -34
- package/dist/src/services/slice/slice-archive-service.d.ts +20 -0
- package/dist/src/services/slice/slice-archive-service.js +111 -0
- package/dist/src/services/solo/batch-heartbeat-poller.d.ts +51 -0
- package/dist/src/services/solo/batch-heartbeat-poller.js +88 -0
- package/dist/src/services/solo/status-line-renderer.d.ts +34 -0
- package/dist/src/services/solo/status-line-renderer.js +55 -0
- package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
- package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
- package/dist/src/services/standards/project-standards-service.d.ts +1 -2
- package/dist/src/services/workspace/reconcile-service.d.ts +36 -0
- package/dist/src/services/workspace/reconcile-service.js +107 -6
- package/dist/src/services/workspace/reconcile-types.d.ts +12 -0
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +2 -1
- package/scripts/install-skills.mjs +112 -2
- package/skills/peaks-ide/SKILL.md +159 -0
- package/skills/peaks-ide/references/audit-log-helper.md +52 -0
- package/skills/peaks-qa/SKILL.md +153 -55
- package/skills/peaks-qa/references/qa-fanout-contract.md +150 -0
- package/skills/peaks-rd/SKILL.md +134 -62
- package/skills/peaks-solo/SKILL.md +124 -37
- package/skills/peaks-solo/references/browser-workflow.md +22 -20
- package/skills/peaks-solo/references/context-governance.md +144 -0
- package/skills/peaks-solo/references/headroom-integration.md +107 -0
- package/skills/peaks-solo/references/runbook.md +3 -3
- package/skills/peaks-solo/references/sub-agent-dispatch.md +261 -0
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +3 -37
- package/skills/peaks-txt/SKILL.md +17 -0
- package/skills/peaks-ui/SKILL.md +45 -10
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peaks sub-agent-dispatch-guard` — G9.5 / RL-30 strict hook-only atom.
|
|
3
|
+
*
|
|
4
|
+
* This is the **second-layer** gate (PreToolUse hook) for the G9 forced
|
|
5
|
+
* compression threshold. It re-validates the prompt size against the
|
|
6
|
+
* threshold table in `src/services/context/threshold.ts` and returns
|
|
7
|
+
* `{allow: true/false, reason, suggest}` JSON to the LLM platform.
|
|
8
|
+
*
|
|
9
|
+
* **NO `--force` flag is exposed at this layer** (RL-30 strict). The
|
|
10
|
+
* hook is the strictest layer in the G9 chain. If the CLI is bypassed
|
|
11
|
+
* (e.g. a user manually invokes the dispatch CLI with `--force` to
|
|
12
|
+
* override the 80% threshold), the hook catches it and returns
|
|
13
|
+
* `{allow: false}` regardless.
|
|
14
|
+
*
|
|
15
|
+
* This atom is **hidden from `peaks --help`** per dev-preference
|
|
16
|
+
* "skill-first / CLI-auxiliary" + PB-2 byte-stable. It is registered
|
|
17
|
+
* via the LLM platform's PreToolUse hook chain (e.g. Claude Code's
|
|
18
|
+
* `settings.json` `PreToolUse` array) and is not a user-facing command.
|
|
19
|
+
*
|
|
20
|
+
* The `peaks hooks install` command reads `IdeAdapter.promptSizeAware`
|
|
21
|
+
* to decide whether to register this hook for a given IDE.
|
|
22
|
+
*/
|
|
23
|
+
import { Command } from 'commander';
|
|
24
|
+
import { type ContextGuardDecision } from '../../services/context/context-guard.js';
|
|
25
|
+
export declare const HOOK_GUARD_RESULT_TYPE: "peaks-hook-guard/v1";
|
|
26
|
+
export interface HookGuardResult {
|
|
27
|
+
readonly schema: typeof HOOK_GUARD_RESULT_TYPE;
|
|
28
|
+
readonly allow: boolean;
|
|
29
|
+
readonly code: ContextGuardDecision['code'];
|
|
30
|
+
readonly reason: string;
|
|
31
|
+
readonly suggest: string | null;
|
|
32
|
+
readonly tier: ContextGuardDecision['evaluation']['tier'];
|
|
33
|
+
readonly ratio: number;
|
|
34
|
+
readonly bytesUsed: number;
|
|
35
|
+
readonly capacityBytes: number;
|
|
36
|
+
readonly warnings: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the hook-guard result for a given prompt size. Pure function;
|
|
40
|
+
* no IO. The CLI atom (registered below) calls this and prints JSON.
|
|
41
|
+
*
|
|
42
|
+
* Even if the caller passes `force = true` in the input (it shouldn't —
|
|
43
|
+
* the hook CLI doesn't expose that flag), this function ignores it
|
|
44
|
+
* and treats the prompt as if no override were available. This is the
|
|
45
|
+
* RL-30 strict semantics.
|
|
46
|
+
*/
|
|
47
|
+
export declare function evaluateHookGuard(promptSize: number): HookGuardResult;
|
|
48
|
+
/**
|
|
49
|
+
* Register the `peaks sub-agent-dispatch-guard` command. Intentionally
|
|
50
|
+
* NOT registered in the main `peaks --help` quickstart (dev-preference
|
|
51
|
+
* PB-2 byte-stable). The caller (the `peaks hooks install` flow) calls
|
|
52
|
+
* this directly via the imported function; the CLI registration in
|
|
53
|
+
* `src/cli/index.ts` uses a hidden command (no `description`, no help).
|
|
54
|
+
*/
|
|
55
|
+
export declare function registerSubAgentDispatchGuard(program: Command): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { evaluatePromptSize } from '../../services/context/context-guard.js';
|
|
2
|
+
export const HOOK_GUARD_RESULT_TYPE = 'peaks-hook-guard/v1';
|
|
3
|
+
/**
|
|
4
|
+
* Build the hook-guard result for a given prompt size. Pure function;
|
|
5
|
+
* no IO. The CLI atom (registered below) calls this and prints JSON.
|
|
6
|
+
*
|
|
7
|
+
* Even if the caller passes `force = true` in the input (it shouldn't —
|
|
8
|
+
* the hook CLI doesn't expose that flag), this function ignores it
|
|
9
|
+
* and treats the prompt as if no override were available. This is the
|
|
10
|
+
* RL-30 strict semantics.
|
|
11
|
+
*/
|
|
12
|
+
export function evaluateHookGuard(promptSize) {
|
|
13
|
+
// Intentionally pass `force: false` always. The hook layer is strict.
|
|
14
|
+
const decision = evaluatePromptSize(promptSize, { force: false });
|
|
15
|
+
return {
|
|
16
|
+
schema: HOOK_GUARD_RESULT_TYPE,
|
|
17
|
+
allow: decision.allow,
|
|
18
|
+
code: decision.code,
|
|
19
|
+
reason: decision.allow
|
|
20
|
+
? `prompt size ${promptSize} bytes within threshold (tier=${decision.evaluation.tier})`
|
|
21
|
+
: `prompt size ${promptSize} bytes exceeds threshold (tier=${decision.evaluation.tier}, ratio=${decision.evaluation.ratio.toFixed(3)})`,
|
|
22
|
+
suggest: decision.suggest,
|
|
23
|
+
tier: decision.evaluation.tier,
|
|
24
|
+
ratio: decision.evaluation.ratio,
|
|
25
|
+
bytesUsed: decision.evaluation.bytesUsed,
|
|
26
|
+
capacityBytes: decision.evaluation.capacityBytes,
|
|
27
|
+
warnings: decision.warnings
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register the `peaks sub-agent-dispatch-guard` command. Intentionally
|
|
32
|
+
* NOT registered in the main `peaks --help` quickstart (dev-preference
|
|
33
|
+
* PB-2 byte-stable). The caller (the `peaks hooks install` flow) calls
|
|
34
|
+
* this directly via the imported function; the CLI registration in
|
|
35
|
+
* `src/cli/index.ts` uses a hidden command (no `description`, no help).
|
|
36
|
+
*/
|
|
37
|
+
export function registerSubAgentDispatchGuard(program) {
|
|
38
|
+
program
|
|
39
|
+
.command('sub-agent-dispatch-guard')
|
|
40
|
+
.description('INTERNAL: PreToolUse hook guard (G9.5 / RL-30 strict)')
|
|
41
|
+
.requiredOption('--prompt <text>', 'the prompt to validate (size in bytes is what gets checked)')
|
|
42
|
+
.option('--prompt-length <bytes>', 'DOGFOOD ONLY: synthesize a prompt of this size (overrides --prompt content for size only)')
|
|
43
|
+
.action((options) => {
|
|
44
|
+
let prompt = options.prompt;
|
|
45
|
+
if (typeof options.promptLength === 'string' && options.promptLength.length > 0) {
|
|
46
|
+
const len = Number.parseInt(options.promptLength, 10);
|
|
47
|
+
if (Number.isInteger(len) && len > 0) {
|
|
48
|
+
prompt = 'x'.repeat(len);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const promptSize = Buffer.byteLength(prompt, 'utf8');
|
|
52
|
+
const result = evaluateHookGuard(promptSize);
|
|
53
|
+
// Always exit 0 — the LLM platform reads `allow` from JSON.
|
|
54
|
+
// The decision is encoded in `allow` / `code`, not the exit code.
|
|
55
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -10,6 +10,7 @@ import { validateChangeIdOrThrow } from '../../shared/change-id.js';
|
|
|
10
10
|
import { getEconomyAwareExecutionModelId } from '../../services/config/model-routing.js';
|
|
11
11
|
import { getLocalArtifactPath } from '../../services/artifacts/workspace-service.js';
|
|
12
12
|
import { getSessionId } from '../../services/session/session-manager.js';
|
|
13
|
+
import { getSessionDir } from '../../services/session/getSessionDir.js';
|
|
13
14
|
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
14
15
|
import { verifyPipeline } from '../../services/workflow/pipeline-verify-service.js';
|
|
15
16
|
import { fail, ok } from '../../shared/result.js';
|
|
@@ -18,7 +19,7 @@ function getCurrentWorkspaceContext() {
|
|
|
18
19
|
try {
|
|
19
20
|
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
20
21
|
const sessionId = getSessionId(projectRoot);
|
|
21
|
-
return sessionId ? { sessionId, sessionDir:
|
|
22
|
+
return sessionId ? { sessionId, sessionDir: getSessionDir(projectRoot, sessionId) } : {};
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
24
25
|
return {};
|
|
@@ -264,6 +264,9 @@ export function registerWorkspaceCommands(program, io) {
|
|
|
264
264
|
if (result.systemCleaned.length > 0) {
|
|
265
265
|
nextActions.push(`Removed ${result.systemCleaned.length} F3 system/ subdir(s).`);
|
|
266
266
|
}
|
|
267
|
+
if (result.subAgentStateMigrated > 0) {
|
|
268
|
+
nextActions.push(`Migrated ${result.subAgentStateMigrated} legacy sub-agent state file(s) into .peaks/_sub_agents/.`);
|
|
269
|
+
}
|
|
267
270
|
printResult(io, ok('workspace.reconcile', result, warnings, nextActions), options.json);
|
|
268
271
|
if (result.errors.length > 0) {
|
|
269
272
|
process.exitCode = 1;
|
package/dist/src/cli/program.js
CHANGED
|
@@ -17,7 +17,10 @@ import { registerScanCommands } from './commands/scan-commands.js';
|
|
|
17
17
|
import { registerShadcnCommands } from './commands/shadcn-commands.js';
|
|
18
18
|
import { registerSliceCommands } from './commands/slice-commands.js';
|
|
19
19
|
import { registerSopCommands } from './commands/sop-commands.js';
|
|
20
|
+
import { registerSubAgentCommands } from './commands/sub-agent-commands.js';
|
|
21
|
+
import { registerSubAgentDispatchGuard } from './commands/sub-agent-dispatch-guard.js';
|
|
20
22
|
import { registerGateCommands } from './commands/gate-commands.js';
|
|
23
|
+
import { registerHookHandleCommand } from './commands/hook-handle.js';
|
|
21
24
|
import { registerHooksCommands } from './commands/hooks-commands.js';
|
|
22
25
|
import { registerStatusLineCommands } from './commands/statusline-commands.js';
|
|
23
26
|
import { registerUnderstandCommands } from './commands/understand-commands.js';
|
|
@@ -92,7 +95,13 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
92
95
|
registerShadcnCommands(program, io);
|
|
93
96
|
registerSliceCommands(program, io);
|
|
94
97
|
registerSopCommands(program, io);
|
|
98
|
+
registerSubAgentCommands(program, io);
|
|
99
|
+
// Slice #010 G9.5: register the hook-only internal atom. Hidden from
|
|
100
|
+
// `peaks --help` (no description text); used by `peaks hooks install`
|
|
101
|
+
// to wire the PreToolUse hook chain.
|
|
102
|
+
registerSubAgentDispatchGuard(program);
|
|
95
103
|
registerGateCommands(program, io);
|
|
104
|
+
registerHookHandleCommand(program, io);
|
|
96
105
|
registerHooksCommands(program, io);
|
|
97
106
|
registerStatusLineCommands(program, io);
|
|
98
107
|
registerUnderstandCommands(program, io);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type HookGuardResult } from '../cli/commands/sub-agent-dispatch-guard.js';
|
|
2
|
+
/**
|
|
3
|
+
* Read the prompt size from the LLM platform's hook stdin. Different
|
|
4
|
+
* LLMs send different payload shapes; we accept the most common:
|
|
5
|
+
* - Claude Code: `{"tool_name": "Bash", "tool_input": {"command": "..."}}`
|
|
6
|
+
* - Trae: `{"tool_name": "terminal", "tool_input": {"command": "..."}}`
|
|
7
|
+
*
|
|
8
|
+
* The hook reads the `command` (or `prompt`) field and computes the
|
|
9
|
+
* byte length. If neither field is present, returns 0 (always passes).
|
|
10
|
+
*/
|
|
11
|
+
export declare function readPromptSizeFromHookStdin(stdin: unknown): number;
|
|
12
|
+
/**
|
|
13
|
+
* Execute the hook guard via spawnSync. Returns the parsed result or
|
|
14
|
+
* a fallback (allow: true) on subprocess failure.
|
|
15
|
+
*
|
|
16
|
+
* Prefer the in-process `evaluateHookGuard` (no subprocess) when the
|
|
17
|
+
* hook is called from a TypeScript context. Use `runHookGuardSubprocess`
|
|
18
|
+
* only when the hook needs to be invoked from a non-TypeScript caller
|
|
19
|
+
* (e.g. a shell script that wraps the peaks CLI).
|
|
20
|
+
*/
|
|
21
|
+
export declare function runHookGuardSubprocess(prompt: string): HookGuardResult;
|
|
22
|
+
/**
|
|
23
|
+
* Main entry point for the hook. Reads the LLM platform's stdin,
|
|
24
|
+
* computes the prompt size, and returns the guard result. Used by
|
|
25
|
+
* the LLM platform's hook JSON to decide whether to allow the tool
|
|
26
|
+
* call.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runHookGuard(stdin: unknown): HookGuardResult;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G9.5 PreToolUse hook execution body.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `peaks sub-agent-dispatch-guard` for LLM platform integration.
|
|
5
|
+
* The hook reads the prompt size from the LLM platform's hook stdin
|
|
6
|
+
* (Claude Code / Trae / etc.), invokes the guard CLI, and returns
|
|
7
|
+
* `{allow: true/false, reason, suggest}` JSON to the LLM platform.
|
|
8
|
+
*
|
|
9
|
+
* The hook is registered via `peaks hooks install` in the LLM
|
|
10
|
+
* platform's `settings.json` `PreToolUse` array. Only IDEs with
|
|
11
|
+
* `IdeAdapter.promptSizeAware: true` get the hook installed.
|
|
12
|
+
*
|
|
13
|
+
* The hook layer is the **strictest** layer in the G9 chain (RL-30).
|
|
14
|
+
* The CLI 兜底 layer (`peaks sub-agent dispatch --force`) can override
|
|
15
|
+
* the 80% threshold; the hook layer CANNOT.
|
|
16
|
+
*/
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import { evaluateHookGuard } from '../cli/commands/sub-agent-dispatch-guard.js';
|
|
19
|
+
/**
|
|
20
|
+
* Read the prompt size from the LLM platform's hook stdin. Different
|
|
21
|
+
* LLMs send different payload shapes; we accept the most common:
|
|
22
|
+
* - Claude Code: `{"tool_name": "Bash", "tool_input": {"command": "..."}}`
|
|
23
|
+
* - Trae: `{"tool_name": "terminal", "tool_input": {"command": "..."}}`
|
|
24
|
+
*
|
|
25
|
+
* The hook reads the `command` (or `prompt`) field and computes the
|
|
26
|
+
* byte length. If neither field is present, returns 0 (always passes).
|
|
27
|
+
*/
|
|
28
|
+
export function readPromptSizeFromHookStdin(stdin) {
|
|
29
|
+
if (stdin === null || typeof stdin !== 'object') {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const obj = stdin;
|
|
33
|
+
const toolInput = obj.tool_input;
|
|
34
|
+
if (toolInput === null || typeof toolInput !== 'object') {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
const ti = toolInput;
|
|
38
|
+
const candidates = ['command', 'prompt', 'text', 'input'];
|
|
39
|
+
for (const key of candidates) {
|
|
40
|
+
const v = ti[key];
|
|
41
|
+
if (typeof v === 'string') {
|
|
42
|
+
return Buffer.byteLength(v, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Execute the hook guard via spawnSync. Returns the parsed result or
|
|
49
|
+
* a fallback (allow: true) on subprocess failure.
|
|
50
|
+
*
|
|
51
|
+
* Prefer the in-process `evaluateHookGuard` (no subprocess) when the
|
|
52
|
+
* hook is called from a TypeScript context. Use `runHookGuardSubprocess`
|
|
53
|
+
* only when the hook needs to be invoked from a non-TypeScript caller
|
|
54
|
+
* (e.g. a shell script that wraps the peaks CLI).
|
|
55
|
+
*/
|
|
56
|
+
export function runHookGuardSubprocess(prompt) {
|
|
57
|
+
const result = spawnSync('node', [
|
|
58
|
+
process.argv[1] ?? 'peaks',
|
|
59
|
+
'sub-agent-dispatch-guard',
|
|
60
|
+
'--prompt', prompt,
|
|
61
|
+
'--json'
|
|
62
|
+
], { encoding: 'utf8' });
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
// Fallback: allow (don't block the dispatch on a guard subprocess failure).
|
|
65
|
+
return {
|
|
66
|
+
schema: 'peaks-hook-guard/v1',
|
|
67
|
+
allow: true,
|
|
68
|
+
code: 'OK',
|
|
69
|
+
reason: `guard subprocess failed (status ${result.status}); falling through`,
|
|
70
|
+
suggest: null,
|
|
71
|
+
tier: 'ok',
|
|
72
|
+
ratio: 0,
|
|
73
|
+
bytesUsed: 0,
|
|
74
|
+
capacityBytes: 0,
|
|
75
|
+
warnings: ['HOOK_GUARD_SUBPROCESS_FAILED']
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(result.stdout);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return {
|
|
83
|
+
schema: 'peaks-hook-guard/v1',
|
|
84
|
+
allow: true,
|
|
85
|
+
code: 'OK',
|
|
86
|
+
reason: 'guard subprocess produced unparseable JSON; falling through',
|
|
87
|
+
suggest: null,
|
|
88
|
+
tier: 'ok',
|
|
89
|
+
ratio: 0,
|
|
90
|
+
bytesUsed: 0,
|
|
91
|
+
capacityBytes: 0,
|
|
92
|
+
warnings: ['HOOK_GUARD_SUBPROCESS_INVALID_JSON']
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Main entry point for the hook. Reads the LLM platform's stdin,
|
|
98
|
+
* computes the prompt size, and returns the guard result. Used by
|
|
99
|
+
* the LLM platform's hook JSON to decide whether to allow the tool
|
|
100
|
+
* call.
|
|
101
|
+
*/
|
|
102
|
+
export function runHookGuard(stdin) {
|
|
103
|
+
const promptSize = readPromptSizeFromHookStdin(stdin);
|
|
104
|
+
return evaluateHookGuard(promptSize);
|
|
105
|
+
}
|
|
@@ -61,7 +61,7 @@ export type PeaksConfig = {
|
|
|
61
61
|
/**
|
|
62
62
|
* Sub-agent progress surfacing knobs. The `peaks progress watch`
|
|
63
63
|
* CLI (intended to be run in a separate terminal tab while the
|
|
64
|
-
* LLM is working) reads `.peaks/<sid>/
|
|
64
|
+
* LLM is working) reads `.peaks/_sub_agents/<sid>/subagent-progress.json`
|
|
65
65
|
* and renders elapsed / spinner / sub-step in real time. The
|
|
66
66
|
* `enabled` flag is a kill-switch for users who find the watch
|
|
67
67
|
* distracting; the `heartbeatIntervalMs` lets power users tune
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type ArtifactStatus = 'created' | 'finalized' | 'partial' | 'failed';
|
|
2
|
+
export interface ArtifactMeta {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly size: number;
|
|
5
|
+
readonly sha256: string;
|
|
6
|
+
readonly status: ArtifactStatus;
|
|
7
|
+
/** Mandatory literal `false`. The type system rejects `true`. */
|
|
8
|
+
readonly contentInlined: false;
|
|
9
|
+
/** 1-2 sentence description, ≤ 200 chars. Allowed in main context. */
|
|
10
|
+
readonly summary: string | null;
|
|
11
|
+
/** ISO8601. */
|
|
12
|
+
readonly writtenAt: string;
|
|
13
|
+
/** Request id this artifact belongs to. */
|
|
14
|
+
readonly rid: string;
|
|
15
|
+
/** Sub-agent role string. */
|
|
16
|
+
readonly role: string;
|
|
17
|
+
/** Sequence number when same role is dispatched multiple times. */
|
|
18
|
+
readonly idx: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ContextImpact {
|
|
21
|
+
readonly promptSize: number;
|
|
22
|
+
readonly artifactSizes: readonly number[];
|
|
23
|
+
readonly batchTotalSize: number;
|
|
24
|
+
/** `high` if `batchTotalSize > 4MB` OR any `artifactSize > 1MB`. */
|
|
25
|
+
readonly contextWarning: 'normal' | 'high' | 'critical';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compute the sha256 hex digest of a file. Throws if the file does not
|
|
29
|
+
* exist or is not readable. Caller is expected to handle ENOENT as
|
|
30
|
+
* `code: 'ARTIFACT_NOT_FOUND'` and treat 0-byte files as `status: 'failed'`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeSha256(filePath: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Build an `ArtifactMeta` from on-disk file. Computes size + sha256.
|
|
35
|
+
*
|
|
36
|
+
* `status` semantics:
|
|
37
|
+
* - `'created'` — file exists, non-empty, sha256 succeeded
|
|
38
|
+
* - `'failed'` — file is 0 bytes (R-2 / G7.4.e: do not silently succeed)
|
|
39
|
+
* - `'partial'` — caller-provided (e.g. sub-agent reports unfinished work)
|
|
40
|
+
* - `'finalized'` — caller-provided (e.g. sub-agent reports complete)
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildArtifactMeta(opts: {
|
|
43
|
+
path: string;
|
|
44
|
+
rid: string;
|
|
45
|
+
role: string;
|
|
46
|
+
idx: number;
|
|
47
|
+
summary: string | null;
|
|
48
|
+
status?: ArtifactStatus;
|
|
49
|
+
/** Override sha256 / size (e.g. when caller already computed them). */
|
|
50
|
+
precomputed?: {
|
|
51
|
+
size: number;
|
|
52
|
+
sha256: string;
|
|
53
|
+
};
|
|
54
|
+
/** Override the writtenAt timestamp. */
|
|
55
|
+
writtenAt?: string;
|
|
56
|
+
}): ArtifactMeta;
|
|
57
|
+
/**
|
|
58
|
+
* Build a `ContextImpact` from a prompt size + artifact sizes.
|
|
59
|
+
* Computes `contextWarning` per the G7.3 rule:
|
|
60
|
+
* - `'critical'` if any artifact > ARTIFACT_MAX_SIZE_BYTES
|
|
61
|
+
* - `'high'` if total > BATCH_TOTAL_HIGH_BYTES (4MB)
|
|
62
|
+
* - `'normal'` otherwise
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildContextImpact(opts: {
|
|
65
|
+
promptSize: number;
|
|
66
|
+
artifactSizes: readonly number[];
|
|
67
|
+
}): ContextImpact;
|
|
68
|
+
export declare const ARTIFACT_LIMITS: {
|
|
69
|
+
readonly ARTIFACT_MAX_SIZE_BYTES: number;
|
|
70
|
+
readonly BATCH_TOTAL_HIGH_BYTES: number;
|
|
71
|
+
readonly ARTIFACT_SUMMARY_MAX_CHARS: 200;
|
|
72
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G7 — sub-agent context minimal-occupation (RL-17..RL-22, AC-38..AC-43).
|
|
3
|
+
*
|
|
4
|
+
* `ArtifactMeta` is what the dispatch record stores per sub-agent artifact
|
|
5
|
+
* instead of the full content. The `contentInlined: false` literal is the
|
|
6
|
+
* API contract: the type system rejects `true`, so main LLM context can
|
|
7
|
+
* never accidentally be flooded with inlined artifact bodies.
|
|
8
|
+
*
|
|
9
|
+
* The artifact's content lives on disk at `path`; the meta is ~200 chars
|
|
10
|
+
* (path + size + sha256 + status + summary), so 3 sub-agents × ~200 chars
|
|
11
|
+
* = ~600 chars net context increase per batch instead of 3MB+.
|
|
12
|
+
*
|
|
13
|
+
* Path convention (G7.4.c):
|
|
14
|
+
* `.peaks/_sub_agents/<sid>/artifacts/<rid>-<role>-<idx>.<ext>`
|
|
15
|
+
*
|
|
16
|
+
* See: `.peaks/memory/sub-agent-context-minimal-occupation.md` for the
|
|
17
|
+
* full G7 rule.
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
21
|
+
const ARTIFACT_MAX_SIZE_BYTES = 1024 * 1024; // 1MB
|
|
22
|
+
const BATCH_TOTAL_HIGH_BYTES = 4 * 1024 * 1024; // 4MB
|
|
23
|
+
/**
|
|
24
|
+
* Compute the sha256 hex digest of a file. Throws if the file does not
|
|
25
|
+
* exist or is not readable. Caller is expected to handle ENOENT as
|
|
26
|
+
* `code: 'ARTIFACT_NOT_FOUND'` and treat 0-byte files as `status: 'failed'`.
|
|
27
|
+
*/
|
|
28
|
+
export function computeSha256(filePath) {
|
|
29
|
+
const content = readFileSync(filePath);
|
|
30
|
+
return createHash('sha256').update(content).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build an `ArtifactMeta` from on-disk file. Computes size + sha256.
|
|
34
|
+
*
|
|
35
|
+
* `status` semantics:
|
|
36
|
+
* - `'created'` — file exists, non-empty, sha256 succeeded
|
|
37
|
+
* - `'failed'` — file is 0 bytes (R-2 / G7.4.e: do not silently succeed)
|
|
38
|
+
* - `'partial'` — caller-provided (e.g. sub-agent reports unfinished work)
|
|
39
|
+
* - `'finalized'` — caller-provided (e.g. sub-agent reports complete)
|
|
40
|
+
*/
|
|
41
|
+
export function buildArtifactMeta(opts) {
|
|
42
|
+
let size;
|
|
43
|
+
let sha256;
|
|
44
|
+
let status = opts.status ?? 'created';
|
|
45
|
+
if (opts.precomputed) {
|
|
46
|
+
size = opts.precomputed.size;
|
|
47
|
+
sha256 = opts.precomputed.sha256;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const stat = statSync(opts.path);
|
|
51
|
+
size = stat.size;
|
|
52
|
+
if (size === 0) {
|
|
53
|
+
// 0-byte artifact: cannot compute meaningful sha256; mark as failed.
|
|
54
|
+
sha256 = '0'.repeat(64);
|
|
55
|
+
status = 'failed';
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
sha256 = computeSha256(opts.path);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const summary = opts.summary;
|
|
62
|
+
if (summary !== null && summary.length > 200) {
|
|
63
|
+
throw new Error(`ArtifactMeta summary must be ≤ 200 chars (got ${summary.length})`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
path: opts.path,
|
|
67
|
+
size,
|
|
68
|
+
sha256,
|
|
69
|
+
status,
|
|
70
|
+
contentInlined: false,
|
|
71
|
+
summary,
|
|
72
|
+
writtenAt: opts.writtenAt ?? new Date().toISOString(),
|
|
73
|
+
rid: opts.rid,
|
|
74
|
+
role: opts.role,
|
|
75
|
+
idx: opts.idx
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build a `ContextImpact` from a prompt size + artifact sizes.
|
|
80
|
+
* Computes `contextWarning` per the G7.3 rule:
|
|
81
|
+
* - `'critical'` if any artifact > ARTIFACT_MAX_SIZE_BYTES
|
|
82
|
+
* - `'high'` if total > BATCH_TOTAL_HIGH_BYTES (4MB)
|
|
83
|
+
* - `'normal'` otherwise
|
|
84
|
+
*/
|
|
85
|
+
export function buildContextImpact(opts) {
|
|
86
|
+
const batchTotalSize = opts.promptSize + opts.artifactSizes.reduce((a, b) => a + b, 0);
|
|
87
|
+
let contextWarning = 'normal';
|
|
88
|
+
if (opts.artifactSizes.some((s) => s > ARTIFACT_MAX_SIZE_BYTES)) {
|
|
89
|
+
contextWarning = 'critical';
|
|
90
|
+
}
|
|
91
|
+
else if (batchTotalSize > BATCH_TOTAL_HIGH_BYTES) {
|
|
92
|
+
contextWarning = 'high';
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
promptSize: opts.promptSize,
|
|
96
|
+
artifactSizes: opts.artifactSizes,
|
|
97
|
+
batchTotalSize,
|
|
98
|
+
contextWarning
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export const ARTIFACT_LIMITS = {
|
|
102
|
+
ARTIFACT_MAX_SIZE_BYTES,
|
|
103
|
+
BATCH_TOTAL_HIGH_BYTES,
|
|
104
|
+
ARTIFACT_SUMMARY_MAX_CHARS: 200
|
|
105
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G9 — forced compression gate (RL-27..RL-32).
|
|
3
|
+
*
|
|
4
|
+
* The CLI 兜底 layer. Validates prompt size against the threshold
|
|
5
|
+
* table in `threshold.ts` and returns a decision. The PreToolUse hook
|
|
6
|
+
* layer (`peaks sub-agent-dispatch-guard`) re-runs the same logic
|
|
7
|
+
* without `--force` (RL-30 strict).
|
|
8
|
+
*
|
|
9
|
+
* Decision codes:
|
|
10
|
+
* - `OK` — under 50%
|
|
11
|
+
* - `CONTEXT_SOFT_WARN` — 50-75%, suggest --use-headroom
|
|
12
|
+
* - `CONTEXT_NEAR_LIMIT` — 75-80%, mandatory --use-headroom suggestion
|
|
13
|
+
* - `PROMPT_TOO_LARGE` — 80-90%, hard reject (allow = false)
|
|
14
|
+
* - `PROMPT_EMERGENCY` — ≥ 90%, hard reject + emergency
|
|
15
|
+
* - `FORCED_OVER_THRESHOLD` — user passed --force at CLI; allow = true
|
|
16
|
+
*
|
|
17
|
+
* See: `.peaks/memory/sub-agent-headroom-forced-compression-gate.md`.
|
|
18
|
+
*/
|
|
19
|
+
import { tierToCode, type ThresholdEvaluation } from './threshold.js';
|
|
20
|
+
export type ContextGuardCode = 'OK' | 'CONTEXT_SOFT_WARN' | 'CONTEXT_NEAR_LIMIT' | 'PROMPT_TOO_LARGE' | 'PROMPT_EMERGENCY' | 'FORCED_OVER_THRESHOLD';
|
|
21
|
+
export interface ContextGuardDecision {
|
|
22
|
+
readonly allow: boolean;
|
|
23
|
+
readonly code: ContextGuardCode;
|
|
24
|
+
readonly warnings: readonly string[];
|
|
25
|
+
readonly suggest: string | null;
|
|
26
|
+
readonly evaluation: ThresholdEvaluation;
|
|
27
|
+
/** ISO8601 timestamp when --force override was applied. null otherwise. */
|
|
28
|
+
readonly forcedAt: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface ContextGuardOptions {
|
|
31
|
+
/** Pass `true` to allow override at the ≥ 80% tier. CLI-only; hook layer MUST NOT set this. */
|
|
32
|
+
readonly force?: boolean;
|
|
33
|
+
/** Override the default 256K context capacity (e.g. for tests). */
|
|
34
|
+
readonly capacityBytes?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Evaluate a prompt size against the G9.3 threshold table.
|
|
38
|
+
*
|
|
39
|
+
* The `force` option is the **only** path that lets a ≥ 80% prompt
|
|
40
|
+
* through. The hook layer (PreToolUse) MUST NOT accept this option;
|
|
41
|
+
* it is enforced by the `peaks sub-agent-dispatch-guard` atom's
|
|
42
|
+
* command-line parser, which does not declare a `--force` flag.
|
|
43
|
+
*/
|
|
44
|
+
export declare function evaluatePromptSize(promptSize: number, opts?: ContextGuardOptions): ContextGuardDecision;
|
|
45
|
+
/**
|
|
46
|
+
* Re-export of `tierToCode` for callers that want a stable mapping
|
|
47
|
+
* without depending on the threshold module directly.
|
|
48
|
+
*/
|
|
49
|
+
export { tierToCode };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G9 — forced compression gate (RL-27..RL-32).
|
|
3
|
+
*
|
|
4
|
+
* The CLI 兜底 layer. Validates prompt size against the threshold
|
|
5
|
+
* table in `threshold.ts` and returns a decision. The PreToolUse hook
|
|
6
|
+
* layer (`peaks sub-agent-dispatch-guard`) re-runs the same logic
|
|
7
|
+
* without `--force` (RL-30 strict).
|
|
8
|
+
*
|
|
9
|
+
* Decision codes:
|
|
10
|
+
* - `OK` — under 50%
|
|
11
|
+
* - `CONTEXT_SOFT_WARN` — 50-75%, suggest --use-headroom
|
|
12
|
+
* - `CONTEXT_NEAR_LIMIT` — 75-80%, mandatory --use-headroom suggestion
|
|
13
|
+
* - `PROMPT_TOO_LARGE` — 80-90%, hard reject (allow = false)
|
|
14
|
+
* - `PROMPT_EMERGENCY` — ≥ 90%, hard reject + emergency
|
|
15
|
+
* - `FORCED_OVER_THRESHOLD` — user passed --force at CLI; allow = true
|
|
16
|
+
*
|
|
17
|
+
* See: `.peaks/memory/sub-agent-headroom-forced-compression-gate.md`.
|
|
18
|
+
*/
|
|
19
|
+
import { CONTEXT_CAPACITY_DEFAULT_BYTES, evaluateThresholdTier, tierToCode } from './threshold.js';
|
|
20
|
+
const NEAR_LIMIT_SUGGEST = 'Consider --use-headroom to compress prompt.';
|
|
21
|
+
const SOFT_WARN_SUGGEST = 'Use --use-headroom to compress prompt proactively.';
|
|
22
|
+
const HARD_REJECT_SUGGEST = 'Trim prompt to < 80% of context capacity. Pass --force at CLI to override (NOT allowed at hook layer).';
|
|
23
|
+
const EMERGENCY_SUGGEST = 'Prompt exceeds 90% of context. Trim aggressively or split into multiple dispatches.';
|
|
24
|
+
/**
|
|
25
|
+
* Evaluate a prompt size against the G9.3 threshold table.
|
|
26
|
+
*
|
|
27
|
+
* The `force` option is the **only** path that lets a ≥ 80% prompt
|
|
28
|
+
* through. The hook layer (PreToolUse) MUST NOT accept this option;
|
|
29
|
+
* it is enforced by the `peaks sub-agent-dispatch-guard` atom's
|
|
30
|
+
* command-line parser, which does not declare a `--force` flag.
|
|
31
|
+
*/
|
|
32
|
+
export function evaluatePromptSize(promptSize, opts = {}) {
|
|
33
|
+
const capacity = opts.capacityBytes ?? CONTEXT_CAPACITY_DEFAULT_BYTES;
|
|
34
|
+
const evaluation = evaluateThresholdTier(promptSize, capacity);
|
|
35
|
+
const tier = evaluation.tier;
|
|
36
|
+
let allow;
|
|
37
|
+
let code;
|
|
38
|
+
let suggest;
|
|
39
|
+
let forcedAt = null;
|
|
40
|
+
switch (tier) {
|
|
41
|
+
case 'ok':
|
|
42
|
+
allow = true;
|
|
43
|
+
code = 'OK';
|
|
44
|
+
suggest = null;
|
|
45
|
+
break;
|
|
46
|
+
case 'soft-warn':
|
|
47
|
+
allow = true;
|
|
48
|
+
code = 'CONTEXT_SOFT_WARN';
|
|
49
|
+
suggest = SOFT_WARN_SUGGEST;
|
|
50
|
+
break;
|
|
51
|
+
case 'near-limit':
|
|
52
|
+
allow = true;
|
|
53
|
+
code = 'CONTEXT_NEAR_LIMIT';
|
|
54
|
+
suggest = NEAR_LIMIT_SUGGEST;
|
|
55
|
+
break;
|
|
56
|
+
case 'hard-reject':
|
|
57
|
+
allow = false;
|
|
58
|
+
code = 'PROMPT_TOO_LARGE';
|
|
59
|
+
suggest = HARD_REJECT_SUGGEST;
|
|
60
|
+
break;
|
|
61
|
+
case 'emergency':
|
|
62
|
+
allow = false;
|
|
63
|
+
code = 'PROMPT_EMERGENCY';
|
|
64
|
+
suggest = EMERGENCY_SUGGEST;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
// --force override (CLI only; hook layer never reaches this branch)
|
|
68
|
+
if (!allow && opts.force === true) {
|
|
69
|
+
allow = true;
|
|
70
|
+
code = 'FORCED_OVER_THRESHOLD';
|
|
71
|
+
suggest = 'Override applied at CLI. Hook layer will still reject.';
|
|
72
|
+
forcedAt = new Date().toISOString();
|
|
73
|
+
}
|
|
74
|
+
const warnings = [...evaluation.warnings];
|
|
75
|
+
if (forcedAt !== null && !warnings.includes('FORCED_OVER_THRESHOLD')) {
|
|
76
|
+
warnings.push('FORCED_OVER_THRESHOLD');
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
allow,
|
|
80
|
+
code,
|
|
81
|
+
warnings,
|
|
82
|
+
suggest,
|
|
83
|
+
evaluation,
|
|
84
|
+
forcedAt
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Re-export of `tierToCode` for callers that want a stable mapping
|
|
89
|
+
* without depending on the threshold module directly.
|
|
90
|
+
*/
|
|
91
|
+
export { tierToCode };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Build the canonical artifact path for a given session/rid/role/idx/ext. */
|
|
2
|
+
export declare function artifactPath(projectRoot: string, sid: string, rid: string, role: string, idx: number, ext?: string): string;
|
|
3
|
+
/** Build the canonical shared channel path. */
|
|
4
|
+
export declare function sharedChannelPath(projectRoot: string, sid: string, rid: string, batchId: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Assert that `artifactPath` lives under
|
|
7
|
+
* `projectRoot/.peaks/_sub_agents/<sid>/artifacts/`. Rejects
|
|
8
|
+
* symlink/junction escapes and `..` segments.
|
|
9
|
+
*
|
|
10
|
+
* Throws an Error with `.code = 'INVALID_ARTIFACT_PATH'` on rejection.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertSafeArtifactPath(artifactPathInput: string, projectRoot: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Assert that `channelPath` lives under
|
|
15
|
+
* `projectRoot/.peaks/_sub_agents/<sid>/shared/`. Same R-2 logic as
|
|
16
|
+
* `assertSafeArtifactPath` but with a different canonical subdir.
|
|
17
|
+
*
|
|
18
|
+
* Throws an Error with `.code = 'INVALID_SHARED_CHANNEL_PATH'` on rejection.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertSafeSharedChannelPath(channelPathInput: string, projectRoot: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Soft-warn check on the artifact file name pattern. Returns null if
|
|
23
|
+
* the name matches `<rid>-<role>-<idx>.<ext>`, otherwise returns a
|
|
24
|
+
* warning string. Does NOT reject (the path is still in the canonical
|
|
25
|
+
* dir; the warning is for human/audit readability per G7.4.c).
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkArtifactNameConvention(artifactPathInput: string): string | null;
|