peaks-loop 4.0.14 → 4.0.16
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 +62 -0
- package/config/eslint/.peaks-rules.cjs +8 -1
- package/dist/cli/commands/_register.js +2 -1
- package/dist/cli/commands/code-review-commands.d.ts +2 -30
- package/dist/cli/commands/code-review-commands.js +43 -65
- package/dist/cli/commands/lint-commands.d.ts +21 -0
- package/dist/cli/commands/lint-commands.js +146 -0
- package/dist/cli/commands/outer-cache-commands.js +13 -6
- package/dist/cli/commands/security-audit-commands.d.ts +1 -1
- package/dist/cli/commands/security-audit-commands.js +1 -1
- package/dist/services/code-review/ecc-bridge.d.ts +2 -7
- package/dist/services/config/config-service.d.ts +2 -19
- package/dist/services/config/config-service.js +0 -52
- package/dist/services/config/config-types.d.ts +4 -24
- package/dist/services/config/config-types.js +1 -10
- package/dist/services/lint/detect-eslint.d.ts +10 -0
- package/dist/services/lint/detect-eslint.js +59 -0
- package/dist/services/lint/detect-ocr-18.d.ts +10 -0
- package/dist/services/lint/detect-ocr-18.js +40 -0
- package/dist/services/lint/eslint-runner.d.ts +58 -0
- package/dist/services/lint/eslint-runner.js +307 -0
- package/dist/services/lint/ocr-multilang-adapter.d.ts +34 -0
- package/dist/services/lint/ocr-multilang-adapter.js +143 -0
- package/dist/services/session/binding-store.d.ts +6 -6
- package/dist/services/session/caller-binding-service.d.ts +7 -0
- package/dist/services/session/caller-binding-service.js +11 -7
- package/dist/services/session/session-binding-bridge.d.ts +17 -1
- package/dist/services/session/session-binding-bridge.js +185 -29
- package/dist/services/session/session-manager.js +37 -0
- package/dist/services/skills/skill-presence-service.js +13 -1
- package/package.json +5 -12
- package/skills/bee/peaks-rd/SKILL.md +1 -1
- package/skills/bee/peaks-rd/references/jsts-eslint-gate.md +197 -0
- package/skills/bee/peaks-rd/references/ocr-multilang-1.8.md +132 -0
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +1 -1
- package/skills/peaks-code/SKILL.md +1 -1
- package/dist/services/code-review/ocr-service.d.ts +0 -129
- package/dist/services/code-review/ocr-service.js +0 -372
- package/skills/bee/peaks-rd/references/ocr-integration.md +0 -229
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.16 — 2026-08-06 (ESLint JS/TS Gate + OCR 1.8.x multi-language reviewer + lint strictification)
|
|
4
|
+
|
|
5
|
+
**8 NEW CLI capabilities** (PRD-002 + PRD-002b; 11 commits on main, 26 files, +1509/-960 LOC):
|
|
6
|
+
|
|
7
|
+
- `peaks lint` (parent, un-hidden) — JS/TS ESLint verifier for peaks-rd Gate B5. Calls `npx --package eslint@10.8.0 --package @typescript-eslint/parser@8.66.0 --package @typescript-eslint/eslint-plugin@8.66.0 --package eslint-plugin-import@2.32.0 -- eslint`. Soft-fail (npx missing / errors never block a slice).
|
|
8
|
+
- `peaks lint check` (default) — diffOnly=true + baselineFile waiver + redLineMode='baseline-aware'. Envelope has `state` + `findings` + `summary` + `baselineWaived` + `redLine` + `durationMs`.
|
|
9
|
+
- `peaks lint baseline` — one-shot full-repo scan; writes `.peaks/lint/baseline.json` (project-level, gitignored by default; peak-loop ships its own as a TS CLI reference fixture).
|
|
10
|
+
- `peaks lint --red-line` (or `peaks lint check --red-line`) — generates `.peaks/memory/lint-redline-summary.md` so the next LLM invocation reads prior sibling-violation context before writing new code.
|
|
11
|
+
- `peaks code-review detect-ocr-18` — 5-state probe for OCR 1.8.x install + LLM config readiness.
|
|
12
|
+
- `peaks code-review run-ocr-18 --language <py|go|java|rust|cpp|csharp|ruby|php>` — 8-language multi-language reviewer (AACR-bench coverage).
|
|
13
|
+
- `peaks code-review ocr-18-delegate-preview` — Delegation Mode entry; no LLM key required (host agent's key suffices).
|
|
14
|
+
|
|
15
|
+
**3 NEW runner options** on `src/services/lint/eslint-runner.ts`:
|
|
16
|
+
- `diffOnly` (default `true`): filter ESLint findings to `git diff HEAD` hunks; 存量违规 silently skipped (no-touch-stockcode invariant).
|
|
17
|
+
- `baselineFile` (default `.peaks/lint/baseline.json`): waiver matching on ruleId + file + line; baseline-violations never counted in `summary`.
|
|
18
|
+
- `redLineMode` (default `'baseline-aware'`): aggregate baseline violations by ruleId; output `## Red-line` section for LLM.
|
|
19
|
+
|
|
20
|
+
**Rule severity promotion** (`config/eslint/.peaks-rules.cjs`):
|
|
21
|
+
- `max-lines: ['error', { max: 400, ... }]` (NEW, was implicit `eslint:recommended` only).
|
|
22
|
+
- `max-lines-per-function`: `['warn', 50]` → `['error', 50]`.
|
|
23
|
+
- `complexity: ['warn', 10]` unchanged (soft guidance, not hard limit).
|
|
24
|
+
|
|
25
|
+
**OCR 1.8.9 surface clear-zero** (PRD-002 S2):
|
|
26
|
+
- `@alibaba-group/open-code-review` 1.3.1 peer → 1.8.9 required dep (1.8.x per-platform `optionalDependencies` structurally removes the 2.0.3-era postinstall HTTPS problem).
|
|
27
|
+
- Deleted: `src/services/code-review/ocr-service.ts` (463 lines) + 4 config types + 4 functions + skill ref `ocr-integration.md` (229 lines).
|
|
28
|
+
- 8 languages now supported: Python / Go / Java / Rust / C++ / C# / Ruby / PHP.
|
|
29
|
+
|
|
30
|
+
**7 binding decisions** (PRD-002b):
|
|
31
|
+
- D1 with 4.0.16 release; D2 cost-unbounded single slice; D3 lint for LLM consumption; D4 incremental-first (diffOnly); D5 no-touch-stockcode (baseline waiver); D6 project-aware baseline (downstream MUST regenerate); D7 no downstream-migration slice (`npm update peaks-loop` propagates).
|
|
32
|
+
|
|
33
|
+
**Quality gates**: `pnpm build` exit 0; 19 new BDD tests (8 eslint-runner + 2 lint-commands + 9 ocr-multilang-adapter); cycle-1 stdout narrowing regression preserved at `src/services/lint/eslint-runner.ts:134/144/187`; verify-pipeline 10/10 gates PASS.
|
|
34
|
+
|
|
35
|
+
**5 pre-existing test timeouts** (NOT introduced by this slice; out of scope for 4.0.16; future slice `2026-08-06-fix-pre-existing-test-timeouts`):
|
|
36
|
+
- `tests/unit/services/auto-compact-orchestrator.test.ts:270`
|
|
37
|
+
- `tests/unit/services/skills/presence-lease-service.test.ts:163`
|
|
38
|
+
- `tests/unit/services/session-binding-bridge-path-canonicalize.test.ts:117`
|
|
39
|
+
- `tests/unit/cli/statusline-cli-integration.test.ts:895`
|
|
40
|
+
- (1 more, full-suite-only)
|
|
41
|
+
|
|
42
|
+
**Lockstep bump.** peaks-loop-shared `0.0.45 → 0.0.46` (CLI_VERSION re-stamped to 4.0.16).
|
|
43
|
+
|
|
44
|
+
## 4.0.15 — 2026-08-06 (rotation guards tightening + caller-derived workflowId + caller-binding primary source + atomic write hygiene)
|
|
45
|
+
|
|
46
|
+
**Tightens rotation guards with 4th same-process re-resolve short-circuit** (slice `2026-08-06-rotation-guards-and-caller-binding`, commit `f38a796f`):
|
|
47
|
+
- `src/services/session/session-binding-bridge.ts` introduces a module-scoped `lastResolvedOuter: { value: string | undefined; resolvedAt: number } | null` populated on every non-throw `getCurrentOuterSessionId` call (env hit / cache hit / undefined fallback). `ensureSessionWithRotation` now short-circuits on a 4th additive guard: when `lastResolvedOuter.value === currentOuterSessionId === boundOuter`, the rotation path is skipped. The 3 legacy guards (currentOuterSessionId === undefined / boundOuter empty / boundOuter === currentOuter) are preserved verbatim per PRD PB3. Test seam `_resetLastResolvedOuterForTest()` exported.
|
|
48
|
+
- Tests: `tests/unit/session/rotation-guards-tightening.test.ts` (11 cases: 3 legacy-guard regressions + 4th-guard flip A→B same-process + per-process isolation + writeAtomic fallback + skipRotate + module-state integrity).
|
|
49
|
+
|
|
50
|
+
**Caller-derived workflowId in legacy compat shim** (slice same, commit `2f6322a3`):
|
|
51
|
+
- `src/services/skills/skill-presence-service.ts:391` switches the legacy compat shim's `workflowId` from `wf-${sessionId}-compat` to `wf-${projection.callerId.slice(0, 189)}-compat`. The slice(0, 189) protects against `WORKFLOW_ID_REGEX` overflow (3 + 200 callerId + 7 = 210 chars > 200 regex cap). `presence-lease-service.ts` and the sub-agent graph workflowId generators at `workflow-presence-lifecycle.ts:95` + `workflow-lifecycle-commands.ts:125` are untouched (per-invocation `wf-${Date.now().toString(36)}`, NOT legacy compat shims).
|
|
52
|
+
- No migration of legacy `wf-<sid>-compat` leases from 4.0.14 installs; they become orphans and are reaped by the 24h30m stale-started GC at `presence-lease-service.ts:42`.
|
|
53
|
+
- Tests: `tests/unit/services/skills/workflow-id-caller-derivation.test.ts` (8 cases: short / long / 200-char callerId boundary / graphRef D4a validation / 2-caller key isolation / legacy back-compat read / slice loss-free regression).
|
|
54
|
+
|
|
55
|
+
**Caller-binding becomes primary binding source + atomic write hygiene** (slice same, commit `97caa66b`):
|
|
56
|
+
- `src/services/session/session-binding-bridge.ts:184-310` `ensureSession` now uses a 3-tier read order: **`callers/<callerId>.json` (if `resolveCallerProjection` succeeds)** → `session.json` (fallback) → fresh-generate. Write path: BOTH files written on first bind or rotation, with `callers/<callerId>.json` FIRST (source of truth) + `session.json` SECOND (denormalized cache for legacy consumers). The lazy `resolveCallerProjection` import is wrapped in try/catch so `PEAKS_CALLER_NOT_RESOLVED` falls through to `session.json` (preserves legacy behavior).
|
|
57
|
+
- `src/services/session/session-manager.ts` adds internal helper `getSessionIdFromCallerBinding` (NOT exported; called from `getSessionId` and `getSessionIdCanonical` BEFORE `readSessionFile` / `readSessionFileCanonical`). Public signatures unchanged — all 14 `getSessionId(` consumers continue to compile and run without modification.
|
|
58
|
+
- `src/services/session/caller-binding-service.ts:113-133` `setCallerBinding` swaps non-atomic `writeFileSync` for `atomicWriteJson` (single-line, plus import + removal of the now-redundant `mkdirSync` block — atomicWriteJson does its own `mkdirSync(dir, { recursive: true })`).
|
|
59
|
+
- `src/cli/commands/outer-cache-commands.ts:125` `peaks outer-cache write` swaps non-atomic `writeFileSync` for `atomicWriteJson` (carry-forward bug from 4.0.14 QA issue #1: a power-loss mid-write left the cache in "permanent bad state" until the next SessionStart).
|
|
60
|
+
- Tests: `tests/unit/session/caller-binding-primary-source.test.ts` (11 cases: 3-tier read order / dual-write ordering / callerId-unresolved fallback / malformed file graceful fallback / `getSessionId` preservation / `skipRotate` honored / 14-consumer regression / `setCallerBinding` atomic / temp-file cleanup) + `tests/unit/cli/outer-cache-atomic-write.test.ts` (4 cases: writeFileSync residue check / atomicWriteJson call / 4.0.14 AC1-AC7 regression / simulated write failure).
|
|
61
|
+
- All 5 modified files stay under the 800 LOC cap: `session-binding-bridge.ts` 527 / `skill-presence-service.ts` 695 / `session-manager.ts` 618 / `caller-binding-service.ts` 204 / `outer-cache-commands.ts` 201. Regression sweep: 229/230 PASS / 1 SKIP (pre-existing Win-only conditional). `tsc -p tsconfig.json --noEmit` clean for slice files.
|
|
62
|
+
|
|
63
|
+
**Lockstep bump.** peaks-loop-shared `0.0.44 → 0.0.45` (CLI_VERSION re-stamped to 4.0.15); peaks-loop-mut `0.1.17 → 0.1.18`; peaks-loop-shared-channel `0.0.21 → 0.0.22`.
|
|
64
|
+
|
|
3
65
|
## 4.0.14 — 2026-08-06 (outer-session cache + ensureSession meta over-coverage)
|
|
4
66
|
|
|
5
67
|
**Fixes the "5 terminals / 5 sessions stuck on 3fe1be" bug** (slice `2026-08-06-session-outer-cache-and-meta-coverage`, commit `f02a9b45`):
|
|
@@ -70,9 +70,16 @@ module.exports = {
|
|
|
70
70
|
],
|
|
71
71
|
rules: {
|
|
72
72
|
// L1 (eslint built-in) — always on, no plugin package required.
|
|
73
|
+
// PRD-002b slice: max-lines + max-lines-per-function promoted warn → error.
|
|
74
|
+
// D5 no-touch-stockcode invariant:存量违规通过 .peaks/lint/baseline.json 豁免
|
|
75
|
+
// (see src/services/lint/eslint-runner.ts baselineFile option).
|
|
76
|
+
'max-lines': [
|
|
77
|
+
'error',
|
|
78
|
+
{ max: 400, skipBlankLines: true, skipComments: true }
|
|
79
|
+
],
|
|
73
80
|
complexity: ['warn', { max: 10 }],
|
|
74
81
|
'max-lines-per-function': [
|
|
75
|
-
'
|
|
82
|
+
'error',
|
|
76
83
|
{ max: 50, skipComments: true, skipBlankLines: true }
|
|
77
84
|
],
|
|
78
85
|
'max-params': ['warn', { max: 4 }],
|
|
@@ -30,6 +30,7 @@ import { registerIdeCommands } from './ide-commands.js';
|
|
|
30
30
|
import { registerImpactCommands } from './impact-commands.js';
|
|
31
31
|
import { registerJobCommands } from './job-commands.js';
|
|
32
32
|
import { registerLegacyCommands } from './legacy-commands.js';
|
|
33
|
+
import { registerLintCommands } from './lint-commands.js';
|
|
33
34
|
import { registerLogCommands } from './log-commands.js';
|
|
34
35
|
import { registerLoopCommands } from './loop-commands.js';
|
|
35
36
|
import { registerWorkflowEvalCommands } from './loop-eval-commands.js';
|
|
@@ -99,7 +100,7 @@ const REGISTRATIONS = [
|
|
|
99
100
|
['prd-blocks-commands', registerPrdBlocksCommands], ['user-touchpoint-commands', registerUserTouchpointCommands],
|
|
100
101
|
['slice-review-commands', registerSliceReviewCommands], ['qa-business-review-commands', registerQaBusinessReviewCommands],
|
|
101
102
|
['slice-integrate-commands', registerSliceIntegrateCommands], ['doc-commands', registerDocCommands],
|
|
102
|
-
['legacy-commands', registerLegacyCommands], ['role-commands', registerRoleCommands],
|
|
103
|
+
['legacy-commands', registerLegacyCommands], ['lint-commands', registerLintCommands], ['role-commands', registerRoleCommands],
|
|
103
104
|
['complexity-commands', registerComplexityCommands], ['sub-agent-commands', registerSubAgentCommands],
|
|
104
105
|
['sub-agent-shutdown-commands', registerSubAgentShutdownCommands], ['e2e-verify', registerE2EVerifyCommand],
|
|
105
106
|
['governance-classify-contract-commands', registerContractCommands],
|
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* peaks code-review * CLI surface —
|
|
3
|
-
* for peaks-rd Gate B3.
|
|
4
|
-
*
|
|
5
|
-
* Per the "skill-first / CLI-auxiliary" tenet, peaks-rd SKILL.md
|
|
6
|
-
* is the primary surface; this CLI returns structured JSON the
|
|
7
|
-
* skill consumes to produce a second-opinion code review.
|
|
8
|
-
*
|
|
9
|
-
* Subcommands:
|
|
10
|
-
* - `peaks code-review detect-ocr` — JSON envelope describing the
|
|
11
|
-
* current ocr install + config state (5 reasons: ready /
|
|
12
|
-
* package-missing / binary-missing / config-missing /
|
|
13
|
-
* detection-failed). Source of truth for the LLM endpoint
|
|
14
|
-
* config is `peaksConfig.ocr.llm` in the user's peaks-loop
|
|
15
|
-
* config (NOT `~/.opencodereview/config.json`).
|
|
16
|
-
* - `peaks code-review run-ocr [--from --to --commit]` — invokes
|
|
17
|
-
* `ocr review --format json` and wraps the result in a peaks
|
|
18
|
-
* ResultEnvelope. Soft-fails when ocr isn't ready so peaks-rd
|
|
19
|
-
* can continue without the second-opinion review. Injects the
|
|
20
|
-
* LLM endpoint config as env vars (OCR_LLM_URL / OCR_LLM_TOKEN
|
|
21
|
-
* / OCR_LLM_MODEL / OCR_USE_ANTHROPIC / OCR_LLM_AUTH_HEADER)
|
|
22
|
-
* so the ocr subprocess never has to read from a file the
|
|
23
|
-
* user did not set up themselves.
|
|
24
|
-
* - `peaks code-review config-template` — prints the JSON snippet
|
|
25
|
-
* the user should paste into their peaks-loop config.json. It
|
|
26
|
-
* does NOT write anything. The user is in control of their
|
|
27
|
-
* LLM token / URL / model. No `peaks ocr config set`; the user
|
|
28
|
-
* either edits the JSON directly or uses `peaks config set
|
|
29
|
-
* --key ocr.llm.url --value '...'` (a peaks-loop command, not
|
|
30
|
-
* an ocr command).
|
|
2
|
+
* peaks code-review * CLI surface — ECC bridge + OCR 1.8.x.
|
|
31
3
|
*/
|
|
32
|
-
import { Command } from 'commander';
|
|
4
|
+
import type { Command } from 'commander';
|
|
33
5
|
import { type ProgramIO } from '../cli-helpers.js';
|
|
34
6
|
export declare function registerCodeReviewCommands(program: Command, io: ProgramIO): void;
|
|
@@ -1,83 +1,61 @@
|
|
|
1
|
-
import { getOcrConfigTemplate, detectOcr, runOcrReview } from '../../services/code-review/ocr-service.js';
|
|
2
|
-
import { getOcrLlmConfig, getUserConfigPath, redactConfigSecrets } from '../../services/config/config-service.js';
|
|
3
1
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
2
|
import { fail, ok } from 'peaks-loop-shared/result';
|
|
3
|
+
import { detectOcr18 } from '../../services/lint/detect-ocr-18.js';
|
|
4
|
+
import { OCR_18_LANGUAGES, runOcr18 } from '../../services/lint/ocr-multilang-adapter.js';
|
|
5
|
+
const SUPPORTED_LANGUAGES_SET = new Set(OCR_18_LANGUAGES);
|
|
5
6
|
export function registerCodeReviewCommands(program, io) {
|
|
6
7
|
const codeReview = program
|
|
7
8
|
.command('code-review', { hidden: true })
|
|
8
|
-
.description('Code-review primitives for peaks-rd Gate B3.
|
|
9
|
+
.description('Code-review primitives for peaks-rd Gate B3. ECC bridge (JS/TS) + OCR 1.8.x multi-language.');
|
|
9
10
|
addJsonOption(codeReview
|
|
10
|
-
.command('detect-ocr')
|
|
11
|
-
.description('Read-only probe: returns the
|
|
12
|
-
.option('--project <path>', 'project root (default: cwd)')).action((options) => {
|
|
13
|
-
const projectRoot = options.project ?? process.cwd();
|
|
11
|
+
.command('detect-ocr-18')
|
|
12
|
+
.description('Read-only probe: returns the 5-state OCR 1.8.x runtime envelope (ready / ocr18-missing / binary-missing / llm-config-missing / detection-failed).')).action((options) => {
|
|
14
13
|
try {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
20
|
-
const envelope = detect.state === 'ready'
|
|
21
|
-
? ok('code-review.detect-ocr', detect, [...detect.warnings], [...detect.nextActions])
|
|
22
|
-
: fail('code-review.detect-ocr', detect.state.toUpperCase().replace(/-/g, '_'), `ocr is not ready: ${detect.state}`, detect, [...detect.nextActions]);
|
|
14
|
+
const result = detectOcr18();
|
|
15
|
+
const envelope = result.state === 'ready'
|
|
16
|
+
? ok('code-review.detect-ocr-18', result, [...result.warnings], [...result.nextActions])
|
|
17
|
+
: fail('code-review.detect-ocr-18', result.state.toUpperCase().replace(/-/g, '_'), `ocr 1.8.x is not ready: ${result.state}`, result, [...result.nextActions]);
|
|
23
18
|
printResult(io, envelope, options.json);
|
|
24
|
-
if (
|
|
25
|
-
process.exitCode =
|
|
26
|
-
}
|
|
19
|
+
if (result.state !== 'ready')
|
|
20
|
+
process.exitCode = 0; // soft-fail
|
|
27
21
|
}
|
|
28
22
|
catch (error) {
|
|
29
|
-
printResult(io, fail('code-review.detect-ocr', '
|
|
30
|
-
process.exitCode = 1;
|
|
23
|
+
printResult(io, fail('code-review.detect-ocr-18', 'DETECT_FAILED', getErrorMessage(error), { state: 'detection-failed' }, ['Re-run with a valid npx on PATH.']), options.json);
|
|
31
24
|
}
|
|
32
25
|
});
|
|
33
26
|
addJsonOption(codeReview
|
|
34
|
-
.command('run-ocr')
|
|
35
|
-
.description('
|
|
36
|
-
.option('--
|
|
37
|
-
.option('--from <ref>', 'git ref to diff from (e.g. main
|
|
38
|
-
.option('--to <ref>', 'git ref to diff to (e.g. HEAD
|
|
39
|
-
.option('--commit <sha>', 'specific commit SHA to review')
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
peaksOcrConfig: getOcrLlmConfig(),
|
|
46
|
-
input: { projectRoot, ...(options.from !== undefined && { from: options.from }), ...(options.to !== undefined && { to: options.to }), ...(options.commit !== undefined && { commit: options.commit }) },
|
|
47
|
-
});
|
|
48
|
-
// Soft-fail policy: when ocr is not ready or the subprocess
|
|
49
|
-
// failed, we still return a JSON envelope (ok=false) but
|
|
50
|
-
// do NOT set process.exitCode — the caller is expected to
|
|
51
|
-
// pattern-match on the state and proceed without ocr.
|
|
52
|
-
const envelope = result.spawned && result.exitCode === 0
|
|
53
|
-
? ok('code-review.run-ocr', result, [...result.warnings], [...result.nextActions])
|
|
54
|
-
: fail('code-review.run-ocr', result.state.toUpperCase().replace(/-/g, '_'), result.spawned ? `ocr review exited ${result.exitCode}` : `ocr is not ready: ${result.state}`, result, [...result.nextActions]);
|
|
55
|
-
printResult(io, envelope, options.json);
|
|
56
|
-
// Intentionally do NOT set process.exitCode here — soft-fail.
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
printResult(io, fail('code-review.run-ocr', 'RUN_OCR_FAILED', getErrorMessage(error), { spawned: false, state: 'detection-failed', exitCode: null, stdout: '', stderr: '', durationMs: 0, parsed: null }, ['Run `peaks code-review detect-ocr --json` to inspect ocr install state.']), options.json);
|
|
60
|
-
// Soft-fail on exception too — peaks-rd should continue.
|
|
27
|
+
.command('run-ocr-18')
|
|
28
|
+
.description('Invoke `ocr review --filter-language <lang>` (or `ocr delegate preview` when --delegate) for one of 8 supported languages.')
|
|
29
|
+
.option('--language <lang>', `target language: ${OCR_18_LANGUAGES.join(' | ')}`)
|
|
30
|
+
.option('--from <ref>', 'git ref to diff from (e.g. main)')
|
|
31
|
+
.option('--to <ref>', 'git ref to diff to (e.g. HEAD)')
|
|
32
|
+
.option('--commit <sha>', 'specific commit SHA to review')
|
|
33
|
+
.option('--delegate', 'use Delegation Mode (no LLM key required)')).action((options) => {
|
|
34
|
+
const language = options.language;
|
|
35
|
+
if (language === undefined || !SUPPORTED_LANGUAGES_SET.has(language)) {
|
|
36
|
+
printResult(io, fail('code-review.run-ocr-18', 'LANGUAGE_UNSUPPORTED', `unsupported language: ${language ?? '<missing>'}`, { state: 'language-unsupported', supported: OCR_18_LANGUAGES }, [`Pick one of: ${OCR_18_LANGUAGES.join(', ')}`]), options.json);
|
|
37
|
+
return;
|
|
61
38
|
}
|
|
39
|
+
const result = runOcr18({
|
|
40
|
+
cwd: process.cwd(),
|
|
41
|
+
language: language,
|
|
42
|
+
...(options.from !== undefined ? { from: options.from } : {}),
|
|
43
|
+
...(options.to !== undefined ? { to: options.to } : {}),
|
|
44
|
+
...(options.commit !== undefined ? { commit: options.commit } : {}),
|
|
45
|
+
...(options.delegate === true ? { delegate: true } : {})
|
|
46
|
+
});
|
|
47
|
+
const envelope = result.state === 'ok'
|
|
48
|
+
? ok('code-review.run-ocr-18', result, [], [])
|
|
49
|
+
: fail('code-review.run-ocr-18', result.state.toUpperCase().replace(/-/g, '_'), `ocr 1.8.x state: ${result.state}`, result, ['Re-run `peaks code-review detect-ocr-18 --json` to refresh.']);
|
|
50
|
+
printResult(io, envelope, options.json);
|
|
62
51
|
});
|
|
63
52
|
addJsonOption(codeReview
|
|
64
|
-
.command('
|
|
65
|
-
.description('Print the
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
targetPath,
|
|
72
|
-
currentConfig: currentRedacted,
|
|
73
|
-
template,
|
|
74
|
-
nextActions: [
|
|
75
|
-
`Edit ${targetPath} and add the "ocr" block from the template above.`,
|
|
76
|
-
'OR use peaks-loop config set with one key at a time: `peaks config set --key ocr.llm.url --value \'<url>\'` etc.',
|
|
77
|
-
'The authToken field is sensitive and is stored in the user layer (`~/.peaks/config.json`); peaks-loop will not commit it.',
|
|
78
|
-
'Re-run `peaks code-review detect-ocr --json` to verify the new state is `ready`.',
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
printResult(io, ok('code-review.config-template', payload), options.json);
|
|
53
|
+
.command('ocr-18-delegate-preview')
|
|
54
|
+
.description('Print the Delegation Mode spec for the host agent (no LLM key required).')).action((options) => {
|
|
55
|
+
const result = runOcr18({ cwd: process.cwd(), language: 'python', delegate: true });
|
|
56
|
+
const envelope = result.state === 'ok'
|
|
57
|
+
? ok('code-review.ocr-18-delegate-preview', result, [], [])
|
|
58
|
+
: fail('code-review.ocr-18-delegate-preview', result.state.toUpperCase().replace(/-/g, '_'), `ocr 1.8.x state: ${result.state}`, result, ['Re-run `peaks code-review detect-ocr-18 --json` to refresh.']);
|
|
59
|
+
printResult(io, envelope, options.json);
|
|
82
60
|
});
|
|
83
61
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peaks code lint` — read-only ESLint verifier (Gate B5).
|
|
3
|
+
*
|
|
4
|
+
* PRD-002b slice: three subcommands expose the runner's new
|
|
5
|
+
* incremental-first / no-touch-stockcode options to the CLI surface.
|
|
6
|
+
*
|
|
7
|
+
* - `peaks lint baseline` — one-shot full-repo scan; writes
|
|
8
|
+
* `.peaks/lint/baseline.json` (project-level, gitignored by
|
|
9
|
+
* default). Each project regenerates its own baseline; the file
|
|
10
|
+
* is project-aware (D6 binding).
|
|
11
|
+
* - `peaks lint check` — default; diffOnly=true + baselineFile
|
|
12
|
+
* waiver + redLineMode='baseline-aware'. This is the LLM-facing
|
|
13
|
+
* Gate B5 entry point.
|
|
14
|
+
* - `peaks lint --red-line` — write
|
|
15
|
+
* `.peaks/memory/lint-redline-summary.md` so the next LLM
|
|
16
|
+
* invocation can read the cross-project red-line before
|
|
17
|
+
* writing more code (supplementary S2 dual-track).
|
|
18
|
+
*/
|
|
19
|
+
import type { Command } from 'commander';
|
|
20
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
21
|
+
export declare function registerLintCommands(program: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
5
|
+
import { detectEslint } from '../../services/lint/detect-eslint.js';
|
|
6
|
+
import { runEslint } from '../../services/lint/eslint-runner.js';
|
|
7
|
+
function parseTimeoutMs(value) {
|
|
8
|
+
if (value === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
const parsed = Number.parseInt(value, 10);
|
|
11
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
12
|
+
throw new Error(`--timeout-ms must be a positive integer; received "${value}"`);
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
function writeBaselineJson(cwd, baselineFile, result) {
|
|
17
|
+
const fullPath = join(cwd, baselineFile);
|
|
18
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
19
|
+
const violations = result.findings.map((f) => ({
|
|
20
|
+
ruleId: f.ruleId,
|
|
21
|
+
file: f.filePath,
|
|
22
|
+
line: f.line,
|
|
23
|
+
severity: f.severity,
|
|
24
|
+
message: f.message
|
|
25
|
+
}));
|
|
26
|
+
const payload = {
|
|
27
|
+
version: 1,
|
|
28
|
+
generatedAt: new Date().toISOString(),
|
|
29
|
+
toolVersion: 'peaks-loop-4.0.16+',
|
|
30
|
+
violations
|
|
31
|
+
};
|
|
32
|
+
writeFileSync(fullPath, JSON.stringify(payload, null, 2), 'utf8');
|
|
33
|
+
return { path: fullPath, violations: violations.length };
|
|
34
|
+
}
|
|
35
|
+
function writeRedLineSummary(cwd, result) {
|
|
36
|
+
const fullPath = join(cwd, '.peaks/memory/lint-redline-summary.md');
|
|
37
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
38
|
+
const lines = [];
|
|
39
|
+
lines.push('---');
|
|
40
|
+
lines.push('title: lint red-line summary (auto-generated)');
|
|
41
|
+
lines.push('rid: 2026-08-06-eslint-strict-metrics');
|
|
42
|
+
lines.push('generatedAt: ' + new Date().toISOString());
|
|
43
|
+
lines.push('source: `peaks lint --red-line` (PRD-002b slice)');
|
|
44
|
+
lines.push('---');
|
|
45
|
+
lines.push('');
|
|
46
|
+
lines.push('# Lint red-line summary');
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push('> LLM-facing card. The next LLM invocation MUST read this file before writing JS/TS code under the peaks-rd Gate B5 surface.');
|
|
49
|
+
lines.push('');
|
|
50
|
+
lines.push('## Top ruleIds (sorted by baseline violation count)');
|
|
51
|
+
lines.push('');
|
|
52
|
+
if (result.redLine.length === 0) {
|
|
53
|
+
lines.push('No baseline violations recorded — project is lint-clean against the current baseline.');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const entry of result.redLine) {
|
|
57
|
+
lines.push(`- \`${entry.ruleId}\`: ${entry.count} occurrence${entry.count === 1 ? '' : 's'}`);
|
|
58
|
+
for (const top of entry.topFiles) {
|
|
59
|
+
lines.push(` - ${top.file} (${top.count})`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
lines.push('');
|
|
64
|
+
writeFileSync(fullPath, lines.join('\n'), 'utf8');
|
|
65
|
+
return { path: fullPath, ruleIds: result.redLine.map((r) => r.ruleId) };
|
|
66
|
+
}
|
|
67
|
+
export function registerLintCommands(program, io) {
|
|
68
|
+
const lint = program
|
|
69
|
+
.command('lint')
|
|
70
|
+
.description('Read-only ESLint verifier (peaks-rd Gate B5). Soft-fails when the toolchain is missing.');
|
|
71
|
+
addJsonOption(lint
|
|
72
|
+
.command('detect-eslint', { isDefault: false })
|
|
73
|
+
.description('Read-only probe: returns the 5-state ESLint runtime envelope (ready / eslint-missing / config-error / npx-failed / detection-failed).')).action((options) => {
|
|
74
|
+
const result = detectEslint();
|
|
75
|
+
const envelope = result.state === 'ready'
|
|
76
|
+
? ok('code.lint.detect-eslint', result, [...result.warnings], [...result.nextActions])
|
|
77
|
+
: fail('code.lint.detect-eslint', result.state.toUpperCase().replace(/-/g, '_'), `eslint is not ready: ${result.state}`, result, [...result.nextActions]);
|
|
78
|
+
printResult(io, envelope, options.json);
|
|
79
|
+
if (result.state !== 'ready') {
|
|
80
|
+
process.exitCode = 0; // soft-fail
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
addJsonOption(lint
|
|
84
|
+
.command('check', { isDefault: true })
|
|
85
|
+
.description('Run the read-only ESLint verifier on diff hunks + apply baseline waiver + emit red-line (default Gate B5 entry).')
|
|
86
|
+
.option('--scope <path>', 'lint scope (default: project root)')
|
|
87
|
+
.option('--config <path>', 'explicit ESLint config path')
|
|
88
|
+
.option('--timeout-ms <ms>', 'subprocess timeout in milliseconds (default 60000)')
|
|
89
|
+
.option('--baseline-file <path>', 'baseline JSON path (default .peaks/lint/baseline.json)')
|
|
90
|
+
.option('--red-line', 'also write .peaks/memory/lint-redline-summary.md')).action((options) => {
|
|
91
|
+
let timeoutMs;
|
|
92
|
+
try {
|
|
93
|
+
timeoutMs = parseTimeoutMs(options.timeoutMs);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
printResult(io, fail('code.lint', 'INVALID_TIMEOUT', getErrorMessage(error), { state: 'execution-failed' }, ['Re-run with --timeout-ms <positive integer>.']), options.json);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const cwd = process.cwd();
|
|
100
|
+
const runOptions = {
|
|
101
|
+
cwd,
|
|
102
|
+
...(options.scope !== undefined ? { scope: options.scope } : {}),
|
|
103
|
+
...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
|
|
104
|
+
...(options.baselineFile !== undefined ? { baselineFile: options.baselineFile } : {}),
|
|
105
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {})
|
|
106
|
+
};
|
|
107
|
+
const result = runEslint(runOptions);
|
|
108
|
+
const extras = {};
|
|
109
|
+
if (options.redLine === true) {
|
|
110
|
+
extras.redLineSummary = writeRedLineSummary(cwd, result);
|
|
111
|
+
}
|
|
112
|
+
const envelope = result.state === 'ok' || result.state === 'baseline-missing'
|
|
113
|
+
? ok('code.lint', { ...result, ...extras }, [], [])
|
|
114
|
+
: fail('code.lint', result.state.toUpperCase().replace(/-/g, '_'), `peaks code lint state: ${result.state}`, { ...result, ...extras }, [`Re-run with --scope <path> or restore the ESLint toolchain.`]);
|
|
115
|
+
printResult(io, envelope, options.json);
|
|
116
|
+
});
|
|
117
|
+
addJsonOption(lint
|
|
118
|
+
.command('baseline')
|
|
119
|
+
.description('One-shot full-repo scan; writes .peaks/lint/baseline.json (project-level, gitignored by default).')
|
|
120
|
+
.option('--scope <path>', 'lint scope (default: project root)')
|
|
121
|
+
.option('--config <path>', 'explicit ESLint config path')
|
|
122
|
+
.option('--timeout-ms <ms>', 'subprocess timeout in milliseconds (default 60000)')
|
|
123
|
+
.option('--baseline-file <path>', 'baseline JSON output path (default .peaks/lint/baseline.json)')).action((options) => {
|
|
124
|
+
let timeoutMs;
|
|
125
|
+
try {
|
|
126
|
+
timeoutMs = parseTimeoutMs(options.timeoutMs);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
printResult(io, fail('code.lint.baseline', 'INVALID_TIMEOUT', getErrorMessage(error), { state: 'execution-failed' }, ['Re-run with --timeout-ms <positive integer>.']), options.json);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
const runOptions = {
|
|
134
|
+
cwd,
|
|
135
|
+
diffOnly: false,
|
|
136
|
+
...(options.scope !== undefined ? { scope: options.scope } : {}),
|
|
137
|
+
...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
|
|
138
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {})
|
|
139
|
+
};
|
|
140
|
+
const result = runEslint(runOptions);
|
|
141
|
+
const baselineFile = options.baselineFile ?? '.peaks/lint/baseline.json';
|
|
142
|
+
const written = writeBaselineJson(cwd, baselineFile, result);
|
|
143
|
+
const envelope = ok('code.lint.baseline', { state: result.state, findings: result.findings.length, ...written }, [], ['Commit baseline.json (or .gitignore it). Peak-loop ships its own baseline as a reference fixture.']);
|
|
144
|
+
printResult(io, envelope, options.json);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
* Both subcommands accept `--project <path>` (defaults to cwd / git
|
|
26
26
|
* root) and `--json` (default in TTY-less invocations).
|
|
27
27
|
*/
|
|
28
|
-
import { existsSync,
|
|
29
|
-
import {
|
|
28
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
30
|
import { fail, ok } from 'peaks-loop-shared/result';
|
|
31
|
+
import { atomicWriteJson } from '../../services/ide/shared/atomic-json.js';
|
|
31
32
|
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
32
33
|
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
33
34
|
const OUTER_SESSION_CACHE_REL = join('.peaks', '_runtime', '.outer-session-cache.json');
|
|
@@ -88,10 +89,16 @@ export function registerOuterCacheCommands(program, io) {
|
|
|
88
89
|
const capturedAt = new Date().toISOString();
|
|
89
90
|
const payload = { outerSessionId, capturedAt };
|
|
90
91
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Slice 2026-08-06-session-cacde8-A.5c: atomic write (temp +
|
|
93
|
+
// rename) so a power-loss mid-write cannot leave the cache file
|
|
94
|
+
// truncated. `atomicWriteJson` owns its own
|
|
95
|
+
// `mkdirSync(dir, { recursive: true })` so the inline mkdir
|
|
96
|
+
// block is removed. The previous `writeFileSync` path was the
|
|
97
|
+
// 4.0.14 carry-forward QA issue #1; the bridge's cache-miss
|
|
98
|
+
// fallback treated the truncated state as a miss (safe) but
|
|
99
|
+
// the file was stuck in "permanent bad state" until the next
|
|
100
|
+
// SessionStart fired.
|
|
101
|
+
atomicWriteJson(cachePath, payload);
|
|
95
102
|
}
|
|
96
103
|
catch (error) {
|
|
97
104
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - `peaks security-audit detect` — JSON envelope describing the
|
|
8
8
|
* 5-state security-audit runtime (ready / handoff-missing /
|
|
9
9
|
* template-missing / dispatch-failed / envelope-malformed).
|
|
10
|
-
* Mirrors
|
|
10
|
+
* Mirrors the ECC bridge detect semantics.
|
|
11
11
|
* - `peaks security-audit run` — runs the full pipeline:
|
|
12
12
|
* detect → read handoff + template → write audit artifact to
|
|
13
13
|
* `.peaks/_runtime/<sid>/audit/security-<rid>.md`. Reads the
|
|
@@ -40,7 +40,7 @@ export function registerSecurityAuditCommands(program, io) {
|
|
|
40
40
|
.description('Independent security audit skill driver. Reads the immutable peaks-prd handoff (sha256-locked) + the project-level security-template.md, then writes a structured security-<rid>.md artifact. Decoupled from peaks-rd 5-way fan-out per slice v2.12.0 (Group A, Tier 2). See skills/bee/peaks-security-audit/SKILL.md for the full workflow.');
|
|
41
41
|
addJsonOption(securityAudit
|
|
42
42
|
.command('detect')
|
|
43
|
-
.description('Read-only probe: returns the 5-state security-audit runtime as a JSON envelope (ready / handoff-missing / template-missing / dispatch-failed / envelope-malformed). The peaks-security-audit skill calls this first to decide whether to invoke `run`. Mirrors
|
|
43
|
+
.description('Read-only probe: returns the 5-state security-audit runtime as a JSON envelope (ready / handoff-missing / template-missing / dispatch-failed / envelope-malformed). The peaks-security-audit skill calls this first to decide whether to invoke `run`. Mirrors the ECC bridge detect semantics.')
|
|
44
44
|
.option('--rid <rid>', 'request id (e.g. 2026-06-27-v2-12-...)')
|
|
45
45
|
.option('--sid <sid>', 'session id (e.g. 2026-06-27-session-...)')
|
|
46
46
|
.option('--project <path>', 'project root (default: cwd)')).action((options) => {
|
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
* `rd/code-review.md` markdown format that `peaks request transition`
|
|
6
6
|
* Gate B3 already reads (`mustContain: ['## Findings', 'CRITICAL']`).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* (uses peaks-loop's own Agent tool surface), ocr is the external
|
|
10
|
-
* subprocess option. Both produce a second-opinion code review that
|
|
11
|
-
* the parent RD loop aggregates alongside its own inline review.
|
|
8
|
+
* ECC is the in-skill agent option and the production Gate B3 path.
|
|
12
9
|
*
|
|
13
10
|
* === Why a bridge (not a direct swap) ===
|
|
14
11
|
*
|
|
@@ -54,9 +51,7 @@ export interface EccEnvelope {
|
|
|
54
51
|
readonly gateAction: EccGateAction;
|
|
55
52
|
}
|
|
56
53
|
/**
|
|
57
|
-
* 5-state
|
|
58
|
-
* (ready / package-missing / binary-missing / config-missing /
|
|
59
|
-
* detection-failed). ECC's analogue:
|
|
54
|
+
* 5-state ECC detection result:
|
|
60
55
|
* - `ready` — ECC plugin is installed and reachable
|
|
61
56
|
* - `plugin-missing` — `everything-claude-code` not in skill list
|
|
62
57
|
* - `agent-missing` — plugin present but `code-review` agent absent
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigGetOptions, ConfigLayer, ConfigSetOptions, ConfigV2,
|
|
1
|
+
import type { ConfigGetOptions, ConfigLayer, ConfigSetOptions, ConfigV2, PeaksConfig, TokenRef, WorkspaceConfig } from './config-types.js';
|
|
2
2
|
export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
|
|
3
3
|
/**
|
|
4
4
|
* Load the slim 2.0 `~/.peaks/config.json` file. Returns the parsed
|
|
@@ -22,23 +22,6 @@ export type RedactedConfigValue = string | number | boolean | null | RedactedCon
|
|
|
22
22
|
[key: string]: RedactedConfigValue;
|
|
23
23
|
};
|
|
24
24
|
export declare function redactConfigSecrets(value: unknown, path?: string): RedactedConfigValue;
|
|
25
|
-
/**
|
|
26
|
-
* Read the ocr LLM endpoint config from the user-layer
|
|
27
|
-
* `~/.peaks/config.json`. The user populates this themselves by
|
|
28
|
-
* pasting the `peaks code-review config-template` output (or by
|
|
29
|
-
* running `peaks config set --key ocr.llm.url --value '...'`).
|
|
30
|
-
* peaks-loop never auto-writes these values.
|
|
31
|
-
*/
|
|
32
|
-
export declare function getOcrConfig(): OcrConfig;
|
|
33
|
-
/**
|
|
34
|
-
* Return the resolved `OcrLlmConfig` block (`peaksConfig.ocr.llm`)
|
|
35
|
-
* or `null` when the user has not populated the user config. The
|
|
36
|
-
* 5-state OCR detector uses this as the source of truth; when the
|
|
37
|
-
* returned block is missing required fields it produces a
|
|
38
|
-
* `config-missing` state with a templated `nextActions` payload
|
|
39
|
-
* the user can paste into their config.
|
|
40
|
-
*/
|
|
41
|
-
export declare function getOcrLlmConfig(): OcrLlmConfig | null;
|
|
42
25
|
export declare function bootstrapProjectLanguageConfig(projectRoot: string, language: string): void;
|
|
43
26
|
export declare function readConfig(projectRoot?: string | null): PeaksConfig;
|
|
44
27
|
export declare function writeConfig(partial: Partial<PeaksConfig>, layer?: ConfigLayer): void;
|
|
@@ -53,6 +36,6 @@ export declare function getWorkspaceConfigForPath(path?: string): WorkspaceConfi
|
|
|
53
36
|
export declare function ensureWorkspaceConfigForPath(path?: string): WorkspaceConfig | null;
|
|
54
37
|
export declare function getWorkspaceConfigForCurrentPath(): WorkspaceConfig | null;
|
|
55
38
|
export declare function ensureWorkspaceConfigForCurrentPath(): WorkspaceConfig | null;
|
|
56
|
-
export type {
|
|
39
|
+
export type { TokenRef, WorkspaceConfig, PeaksConfig, ConfigLayer };
|
|
57
40
|
export { getUserConfigPath } from './config-safety.js';
|
|
58
41
|
export { globalConfigPath } from './config-migration.js';
|
|
@@ -412,58 +412,6 @@ export function redactConfigSecrets(value, path = '') {
|
|
|
412
412
|
return [key, redactConfigSecrets(entry, nextPath)];
|
|
413
413
|
}));
|
|
414
414
|
}
|
|
415
|
-
const OCR_AUTH_HEADERS = new Set(['authorization', 'x-api-key', 'bearer']);
|
|
416
|
-
function toOcrLlmConfig(value) {
|
|
417
|
-
if (!isRecord(value))
|
|
418
|
-
return {};
|
|
419
|
-
const url = typeof value.url === 'string' && value.url.trim().length > 0 ? value.url.trim() : undefined;
|
|
420
|
-
const authToken = typeof value.authToken === 'string' && value.authToken.length > 0 ? value.authToken : undefined;
|
|
421
|
-
const model = typeof value.model === 'string' && value.model.trim().length > 0 ? value.model.trim() : undefined;
|
|
422
|
-
const useAnthropic = typeof value.useAnthropic === 'boolean' ? value.useAnthropic : undefined;
|
|
423
|
-
const rawAuthHeader = typeof value.authHeader === 'string' ? value.authHeader : undefined;
|
|
424
|
-
const authHeader = rawAuthHeader !== undefined && OCR_AUTH_HEADERS.has(rawAuthHeader)
|
|
425
|
-
? rawAuthHeader
|
|
426
|
-
: undefined;
|
|
427
|
-
return {
|
|
428
|
-
...(url !== undefined ? { url } : {}),
|
|
429
|
-
...(authToken !== undefined ? { authToken } : {}),
|
|
430
|
-
...(model !== undefined ? { model } : {}),
|
|
431
|
-
...(useAnthropic !== undefined ? { useAnthropic } : {}),
|
|
432
|
-
...(authHeader !== undefined ? { authHeader } : {})
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
function toOcrConfig(value) {
|
|
436
|
-
if (!isRecord(value))
|
|
437
|
-
return {};
|
|
438
|
-
return {
|
|
439
|
-
...(isRecord(value.llm) ? { llm: toOcrLlmConfig(value.llm) } : {})
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Read the ocr LLM endpoint config from the user-layer
|
|
444
|
-
* `~/.peaks/config.json`. The user populates this themselves by
|
|
445
|
-
* pasting the `peaks code-review config-template` output (or by
|
|
446
|
-
* running `peaks config set --key ocr.llm.url --value '...'`).
|
|
447
|
-
* peaks-loop never auto-writes these values.
|
|
448
|
-
*/
|
|
449
|
-
export function getOcrConfig() {
|
|
450
|
-
const userConfig = readUserJsonFile() ?? {};
|
|
451
|
-
return toOcrConfig(userConfig.ocr);
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Return the resolved `OcrLlmConfig` block (`peaksConfig.ocr.llm`)
|
|
455
|
-
* or `null` when the user has not populated the user config. The
|
|
456
|
-
* 5-state OCR detector uses this as the source of truth; when the
|
|
457
|
-
* returned block is missing required fields it produces a
|
|
458
|
-
* `config-missing` state with a templated `nextActions` payload
|
|
459
|
-
* the user can paste into their config.
|
|
460
|
-
*/
|
|
461
|
-
export function getOcrLlmConfig() {
|
|
462
|
-
const ocr = getOcrConfig();
|
|
463
|
-
if (!ocr.llm)
|
|
464
|
-
return null;
|
|
465
|
-
return ocr.llm;
|
|
466
|
-
}
|
|
467
415
|
function inferHumanLanguage(value) {
|
|
468
416
|
const normalized = value.trim();
|
|
469
417
|
if (!normalized) {
|