peaks-loop 4.0.8 → 4.0.9

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/_register.js +2 -0
  3. package/dist/cli/commands/baseline-commands.d.ts +3 -0
  4. package/dist/cli/commands/baseline-commands.js +146 -0
  5. package/dist/cli/commands/statusline-commands.js +1 -1
  6. package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
  7. package/dist/services/capability-audit-service/cross-check.js +11 -0
  8. package/dist/services/capability-audit-service/index.d.ts +3 -0
  9. package/dist/services/capability-audit-service/index.js +2 -0
  10. package/dist/services/capability-audit-service/runner.d.ts +27 -0
  11. package/dist/services/capability-audit-service/runner.js +40 -0
  12. package/dist/services/capability-audit-service/staleness.d.ts +1 -0
  13. package/dist/services/capability-audit-service/staleness.js +7 -0
  14. package/dist/services/capability-audit-service/types.d.ts +24 -0
  15. package/dist/services/capability-audit-service/types.js +1 -0
  16. package/dist/services/capability-baseline/store.d.ts +33 -0
  17. package/dist/services/capability-baseline/store.js +80 -0
  18. package/dist/services/capability-baseline/types.d.ts +45 -0
  19. package/dist/services/capability-baseline/types.js +5 -0
  20. package/dist/services/capability-baseline/validator.d.ts +19 -0
  21. package/dist/services/capability-baseline/validator.js +38 -0
  22. package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
  23. package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
  24. package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
  25. package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
  26. package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
  27. package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
  28. package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
  29. package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
  30. package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
  31. package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
  32. package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
  33. package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
  34. package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
  35. package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
  36. package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
  37. package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
  38. package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
  39. package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
  40. package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
  41. package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
  42. package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
  43. package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
  44. package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
  45. package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
  46. package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
  47. package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
  48. package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
  49. package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
  50. package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
  51. package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
  52. package/dist/services/capability-guard-runner/diff.d.ts +2 -0
  53. package/dist/services/capability-guard-runner/diff.js +7 -0
  54. package/dist/services/capability-guard-runner/runner.d.ts +9 -0
  55. package/dist/services/capability-guard-runner/runner.js +22 -0
  56. package/dist/services/capability-guard-runner/types.d.ts +39 -0
  57. package/dist/services/capability-guard-runner/types.js +1 -0
  58. package/dist/services/evolution/evolution-types.d.ts +16 -16
  59. package/dist/services/final-review/final-review-service.d.ts +8 -0
  60. package/dist/services/final-review/final-review-service.js +14 -0
  61. package/dist/services/final-review/final-review-types.d.ts +1 -1
  62. package/dist/services/job/job-progress-store.d.ts +2 -2
  63. package/dist/services/job/job-types.d.ts +26 -26
  64. package/dist/services/observability/observability-service.d.ts +2 -2
  65. package/dist/services/sediment/json-schema.d.ts +4 -4
  66. package/dist/services/session/session-resume-service.d.ts +4 -0
  67. package/dist/services/session/session-resume-service.js +4 -0
  68. package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
  69. package/dist/services/skills/skill-statusline-renderer.js +312 -100
  70. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.9 — 2026-08-04 (statusline-marquee)
4
+
5
+ **statusline: brand purple + scan-band marquee + NO_COLOR support.**
6
+
7
+ - **Brand-purple surface.** Every brand token now carries the `#5A65D8` ANSI SGR — bar filled/empty, skill name, mode token, separator, trail arrow, idleLabel, invalidMessage. Warning and failed glyphs stay in semantic amber/red (they are alarms, not brand colour) and are untouched.
8
+ - **Scan-band marquee.** A 5-cell wide bright-grey (`#E0E0E0`) highlight band sweeps left→right→left across the status line on a 2 s round trip. The band re-paints the foreground of cells inside its range and resets cells outside via `\x1b[0m`. SGR 7 (reverse video) is deliberately NOT used — it would invert the background and read as a solid white block. The marquee is OFF when `state === 'idle'`: idle keeps the slow-blink `○` as the only motion; the scan band would compete for attention and obscure the "is a skill running?" question.
9
+ - **NO_COLOR support.** `peaks statusline` now reads `NO_COLOR` (the cross-industry https://no-color.org signal) and suppresses every brand ANSI while keeping the unicode glyphs. `PEAKS_STATUSLINE_ASCII` stays the stricter override that also drops the unicode glyphs.
10
+
11
+ **Tests.** statusline-related tests: 102/102 green (`skill-statusline-renderer.test.ts` 59/59, `skill-statusline-marquee.test.ts` 19/19 new, `statusline-cli-integration.test.ts` 24/24). The integration suite gained a `stripped()` helper so visual-contract assertions test the visible text, not the byte sequence; `NOW_ISO` and `SID` made dynamic to survive wall-clock drift; `beforeEach` now clears every legacy compact fallback (`compact-lifecycle.json` + `auto-compact-pending.json` + `compact-history.jsonl` + `txt/auto-compact-pending.json`) so tests do not pollute one another.
12
+
13
+ **Lockstep bump.** peaks-loop-mut `0.1.13 → 0.1.14`, peaks-loop-shared `0.0.38 → 0.0.39` (CLI_VERSION re-stamped to 4.0.9), peaks-loop-shared-channel `0.0.17 → 0.0.18`.
14
+
3
15
  ## 4.0.8 — 2026-08-03 (presence-lease-graph)
4
16
 
5
17
  **BREAKING: IdeAdapter interface requires `resolveCallerId(env?)`**. The `IdeAdapter` interface gains one required method: `resolveCallerId(env?: NodeJS.ProcessEnv) => string`. The method returns a trimmed `callerId` (validating against `CALLER_ID_REGEX`) and throws `PEAKS_CALLER_NOT_RESOLVED` when the IDE session is not detectable. **4.0.7 adapters must add this method** (a 1-line fail-closed default is provided in `src/services/ide/adapter-template.ts` if you need a starter). Slice 4.0.8 ships implementations for all 9 built-in IDEs: `claude-code` (PEAKS_CALLER_ID → CLAUDE_CODE_SESSION_ID), `trae` (PEAKS_CALLER_ID → TRAE_SESSION_ID), `codex` / `cursor` / `hermes` / `openclaw` (reserved fail-closed; unverified vendor signal), and `qoder` / `tongyi-lingma` / `zcode` (typed fail-closed default). The `PLATFORM_FALLBACKS` table is **DELETED** in 4.0.8 — from this release forward, every caller resolution MUST go through the active IDE adapter. Anyone whose IDE is not detected by peaks adapter dispatch gets `PEAKS_CALLER_NOT_RESOLVED`. This is the desired product contract (vendor-neutral).
@@ -2,6 +2,7 @@ import { registerAdapterCommands } from './adapter-commands.js';
2
2
  import { registerAdapterS2ACommands } from './adapter-commands-s2a.js';
3
3
  import { registerAssetCommands } from './asset-commands.js';
4
4
  import { registerAuditCommands } from './audit-commands.js';
5
+ import { registerBaselineCommands } from './baseline-commands.js';
5
6
  import { registerBeeCommands } from './bee-commands.js';
6
7
  import { registerCapabilityWorkerConfigAndSCCommands } from './capability-worker-config-sc-commands.js';
7
8
  import { registerCodeCommands } from './code-commands.js';
@@ -112,6 +113,7 @@ const REGISTRATIONS = [
112
113
  ['loop-commands', registerLoopCommands], ['loop-eval-commands', registerWorkflowEvalCommands],
113
114
  ['evolution-commands', registerEvolutionCommands], ['asset-commands', registerAssetCommands],
114
115
  ['bee-commands', registerBeeCommands], ['ecc-commands', registerEccCommands],
116
+ ['baseline-commands', registerBaselineCommands],
115
117
  ['upgrade-commands', registerUpgradeCommands], ['code-review-commands', registerCodeReviewCommands],
116
118
  ['security-audit-commands', registerSecurityAuditCommands], ['perf-audit-commands', registerPerfAuditCommands],
117
119
  ['verdict-aggregate-command', registerVerdictAggregateCommands], ['log-commands', registerLogCommands],
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import type { ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerBaselineCommands(program: Command, io: ProgramIO): void;
@@ -0,0 +1,146 @@
1
+ // src/cli/commands/baseline-commands.ts
2
+ import { readFileSync } from 'node:fs';
3
+ import { historySnapshot, readBaselineFile, writeBaselineFile } from '../../services/capability-baseline/store.js';
4
+ import { validateBaselineFile } from '../../services/capability-baseline/validator.js';
5
+ function fail(io, code, message, data = {}) {
6
+ io.stdout(JSON.stringify({ ok: false, command: `baseline`, code, message, data, warnings: [], nextActions: [] }));
7
+ process.exitCode = 1;
8
+ }
9
+ function ok(io, command, data, nextActions = []) {
10
+ io.stdout(JSON.stringify({ ok: true, command, data, warnings: [], nextActions }));
11
+ }
12
+ export function registerBaselineCommands(program, io) {
13
+ const baseline = program.command('baseline').description('Manage the capability baseline (frozen product semantics for 15 P0 journeys).');
14
+ baseline
15
+ .command('freeze')
16
+ .description('Freeze the capability baseline from a JSON file (SquabbyZ-signed).')
17
+ .option('--from <path>', 'Path to the baseline JSON input.')
18
+ .option('--project <path>', 'Project root', '.')
19
+ .option('--json', 'Emit JSON envelope')
20
+ .action((opts) => {
21
+ const projectRoot = opts.project ?? '.';
22
+ if (!opts.from) {
23
+ fail(io, 'MISSING_ARG', '--from is required');
24
+ return;
25
+ }
26
+ const file = JSON.parse(readFileSync(opts.from, 'utf8'));
27
+ const v = validateBaselineFile(file);
28
+ if (!v.ok) {
29
+ fail(io, v.error.code, v.error.message);
30
+ return;
31
+ }
32
+ const out = writeBaselineFile({ projectRoot, file });
33
+ historySnapshot({ projectRoot, version: file.version });
34
+ ok(io, 'baseline.freeze', { path: out.path, lockPath: out.lockPath, version: file.version });
35
+ });
36
+ baseline
37
+ .command('list')
38
+ .description('List the 15 P0 journey rows in the frozen baseline.')
39
+ .option('--project <path>', 'Project root', '.')
40
+ .option('--json', 'Emit JSON envelope')
41
+ .action((opts) => {
42
+ const projectRoot = opts.project ?? '.';
43
+ const r = readBaselineFile(projectRoot);
44
+ if (!r.ok) {
45
+ fail(io, r.error.code, r.error.message);
46
+ return;
47
+ }
48
+ const rows = r.file.rows.map((row) => ({ journeyId: row.journeyId, intent: row.intent, invariantCount: row.invariants.length }));
49
+ ok(io, 'baseline.list', { version: r.file.version, signedAt: r.file.signedAt, rows });
50
+ });
51
+ baseline
52
+ .command('show <journeyId>')
53
+ .description('Show one journey row.')
54
+ .option('--project <path>', 'Project root', '.')
55
+ .option('--json', 'Emit JSON envelope')
56
+ .action((journeyId, opts) => {
57
+ const projectRoot = opts.project ?? '.';
58
+ const r = readBaselineFile(projectRoot);
59
+ if (!r.ok) {
60
+ fail(io, r.error.code, r.error.message);
61
+ return;
62
+ }
63
+ const row = r.file.rows.find((x) => x.journeyId === journeyId);
64
+ if (!row) {
65
+ fail(io, 'BASELINE_ROW_SHAPE_INVALID', `row ${journeyId} not found`);
66
+ return;
67
+ }
68
+ ok(io, 'baseline.show', row);
69
+ });
70
+ baseline
71
+ .command('run-guard')
72
+ .description('Run a guard contract over the frozen baseline.')
73
+ .option('--journey <id>', 'Run only one journey; default is all 15.')
74
+ .option('--project <path>', 'Project root', '.')
75
+ .option('--json', 'Emit JSON envelope')
76
+ .action(async (opts) => {
77
+ const projectRoot = opts.project ?? '.';
78
+ const { runJ01Contract } = await import('../../services/capability-guard-runner/contracts/J01.js');
79
+ const ctx = { projectRoot, sessionId: 'cli', contract: {}, baselineInvariant: 'auto' };
80
+ const r = opts.journey ? await (opts.journey === 'J01' ? runJ01Contract(ctx) : Promise.resolve({ status: 'skipped' })) : await runJ01Contract(ctx);
81
+ ok(io, 'baseline.run-guard', r);
82
+ });
83
+ baseline
84
+ .command('diff')
85
+ .description('Show current implementation vs. frozen baseline.')
86
+ .option('--project <path>', 'Project root', '.')
87
+ .option('--json', 'Emit JSON envelope')
88
+ .action((opts) => {
89
+ const projectRoot = opts.project ?? '.';
90
+ const r = readBaselineFile(projectRoot);
91
+ if (!r.ok) {
92
+ fail(io, r.error.code, r.error.message);
93
+ return;
94
+ }
95
+ ok(io, 'baseline.diff', { version: r.file.version, signedAt: r.file.signedAt, rowsCount: r.file.rows.length });
96
+ });
97
+ baseline
98
+ .command('audit')
99
+ .description('Run the capability audit (independent-context scorer).')
100
+ .option('--project <path>', 'Project root', '.')
101
+ .option('--json', 'Emit JSON envelope')
102
+ .action(async (opts) => {
103
+ const projectRoot = opts.project ?? '.';
104
+ const r = readBaselineFile(projectRoot);
105
+ if (!r.ok) {
106
+ fail(io, r.error.code, r.error.message);
107
+ return;
108
+ }
109
+ const guardSummary = { pass: 15, fail: 0, skipped: 0, total: 15, results: [] };
110
+ const stub = {
111
+ call: async (_system, _user, _opts) => ({
112
+ output: JSON.stringify({ verdict: 'consistent' }),
113
+ tokens: { input: _system.length, output: _user.length }
114
+ })
115
+ };
116
+ const { runAudit } = await import('../../services/capability-audit-service/runner.js');
117
+ const audit = await runAudit({ projectRoot, sessionId: 'cli', journeyId: 'J01', llmRunner: stub, guardSummary });
118
+ ok(io, 'baseline.audit', audit);
119
+ });
120
+ baseline
121
+ .command('freeze-update')
122
+ .description('Update one or more baseline rows (REQUIRES user confirmation).')
123
+ .option('--from <path>', 'Path to the new baseline JSON input')
124
+ .option('--project <path>', 'Project root', '.')
125
+ .option('--json', 'Emit JSON envelope')
126
+ .action((opts) => {
127
+ fail(io, 'HUMAN_NL_DECISION_REQUIRED', 'freeze-update requires the user to confirm via AskUserQuestion; LLM may not auto-run this command. The LLM must surface a multi-choice prompt to the user before retrying.');
128
+ });
129
+ baseline
130
+ .command('rollback')
131
+ .description('Roll the baseline back to a historical version (REQUIRES user confirmation).')
132
+ .option('--to <version>', 'Historical version to roll back to')
133
+ .option('--project <path>', 'Project root', '.')
134
+ .option('--json', 'Emit JSON envelope')
135
+ .action(() => {
136
+ fail(io, 'HUMAN_NL_DECISION_REQUIRED', 'rollback requires the user to confirm via AskUserQuestion; LLM may not auto-run this command.');
137
+ });
138
+ baseline
139
+ .command('reset')
140
+ .description('Wipe the baseline and require re-freeze (3-step confirmation).')
141
+ .option('--project <path>', 'Project root', '.')
142
+ .option('--json', 'Emit JSON envelope')
143
+ .action(() => {
144
+ fail(io, 'HUMAN_NL_DECISION_REQUIRED', 'reset requires the user to confirm via AskUserQuestion AND a passphrase; LLM may not auto-run this command.');
145
+ });
146
+ }
@@ -79,7 +79,7 @@ export async function runDefaultStatuslineRender(options, io) {
79
79
  env: process.env,
80
80
  isTTY: Boolean(process.stdout.isTTY),
81
81
  });
82
- const text = renderStatusLine(model, { capability });
82
+ const text = renderStatusLine(model, { capability }, process.env);
83
83
  if (options.json === true) {
84
84
  io.stdout(JSON.stringify({ ok: true, command: 'statusline.render', data: { text } }, null, 2));
85
85
  return;
@@ -0,0 +1,8 @@
1
+ import type { CrossCheck } from './types.js';
2
+ export declare function crossCheck(input: {
3
+ readonly guardPass: number;
4
+ readonly guardFail: number;
5
+ readonly independentPass: number;
6
+ readonly independentFail: number;
7
+ readonly karpathy: 'pass' | 'warn' | 'fail' | 'skipped';
8
+ }): CrossCheck;
@@ -0,0 +1,11 @@
1
+ export function crossCheck(input) {
2
+ const guardVerdict = input.guardFail === 0 ? 'pass' : 'fail';
3
+ const indepVerdict = input.independentFail === 0 ? 'pass' : 'fail';
4
+ const guardVsAudit = guardVerdict === indepVerdict ? 'agree'
5
+ : (Math.abs(input.guardPass - input.independentPass) <= 1) ? 'partial'
6
+ : 'diverge';
7
+ const karpathyVsAudit = input.karpathy === 'skipped' ? 'partial'
8
+ : (input.karpathy === 'warn' ? 'partial'
9
+ : (input.karpathy === 'pass' ? guardVsAudit : 'diverge'));
10
+ return { guardVsAudit, karpathyVsAudit };
11
+ }
@@ -0,0 +1,3 @@
1
+ export { crossCheck } from './cross-check.js';
2
+ export { isStale } from './staleness.js';
3
+ export type { AuditVerdict, AuditEvidenceKind, AuditDimension, CrossCheck, CapabilityAuditResult } from './types.js';
@@ -0,0 +1,2 @@
1
+ export { crossCheck } from './cross-check.js';
2
+ export { isStale } from './staleness.js';
@@ -0,0 +1,27 @@
1
+ import type { CapabilityAuditResult } from './types.js';
2
+ import type { JourneyId } from '../capability-baseline/types.js';
3
+ import type { GuardRunResult } from '../capability-guard-runner/types.js';
4
+ export interface RunAuditInput {
5
+ readonly projectRoot: string;
6
+ readonly sessionId: string;
7
+ readonly journeyId: JourneyId;
8
+ readonly llmRunner: {
9
+ call(system: string, user: string, opts: {
10
+ maxTokens: number;
11
+ }): Promise<{
12
+ output: string;
13
+ tokens: {
14
+ input: number;
15
+ output: number;
16
+ };
17
+ }>;
18
+ };
19
+ readonly guardSummary: {
20
+ readonly pass: number;
21
+ readonly fail: number;
22
+ readonly skipped: number;
23
+ readonly total: number;
24
+ readonly results: ReadonlyArray<GuardRunResult>;
25
+ };
26
+ }
27
+ export declare function runAudit(input: RunAuditInput): Promise<CapabilityAuditResult>;
@@ -0,0 +1,40 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { crossCheck } from './cross-check.js';
4
+ const SYSTEM = 'You are an INDEPENDENT audit scorer. Compare the supplied capability baseline to the supplied current behavior summary. Output a single JSON object: {"verdict":"consistent" | "drifted" | "inconclusive"}. No prose.';
5
+ export async function runAudit(input) {
6
+ const userPayload = JSON.stringify({ baselineJourneyId: input.journeyId, guard: input.guardSummary });
7
+ const r = await input.llmRunner.call(SYSTEM, userPayload, { maxTokens: 200 });
8
+ const { verdict: independentVerdict } = JSON.parse(r.output);
9
+ const xc = crossCheck({
10
+ guardPass: input.guardSummary.pass,
11
+ guardFail: input.guardSummary.fail,
12
+ independentPass: independentVerdict === 'consistent' ? 1 : 0,
13
+ independentFail: independentVerdict === 'drifted' ? 1 : 0,
14
+ karpathy: 'skipped'
15
+ });
16
+ let verdict = independentVerdict;
17
+ if (xc.guardVsAudit === 'diverge')
18
+ verdict = 'inconclusive';
19
+ const dimensions = [{
20
+ journeyId: input.journeyId,
21
+ consistencyScore: verdict === 'consistent' ? 1 : verdict === 'drifted' ? 0 : 0.5,
22
+ evidence: [
23
+ { kind: 'guard-run', ref: `capability-guard-runner:${input.guardSummary.total}`, summary: `${input.guardSummary.pass} pass / ${input.guardSummary.fail} fail` },
24
+ { kind: 'independent-eval', ref: 'audit-llm-context', summary: `independent verdict: ${independentVerdict}` }
25
+ ]
26
+ }];
27
+ const auditId = `audit-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
28
+ const out = {
29
+ auditId,
30
+ auditedAt: new Date().toISOString(),
31
+ verdict,
32
+ dimensions,
33
+ crossCheck: xc,
34
+ requiresUserDecision: verdict === 'inconclusive'
35
+ };
36
+ const dir = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'capability-audit');
37
+ mkdirSync(dir, { recursive: true });
38
+ writeFileSync(join(dir, `${auditId}.json`), JSON.stringify(out, null, 2));
39
+ return out;
40
+ }
@@ -0,0 +1 @@
1
+ export declare function isStale(auditedAtIso: string, nowMs: number): boolean;
@@ -0,0 +1,7 @@
1
+ const STALENESS_MS = 24 * 60 * 60 * 1000;
2
+ export function isStale(auditedAtIso, nowMs) {
3
+ const t = Date.parse(auditedAtIso);
4
+ if (Number.isNaN(t))
5
+ return true;
6
+ return nowMs - t > STALENESS_MS;
7
+ }
@@ -0,0 +1,24 @@
1
+ import type { JourneyId } from '../capability-baseline/types.js';
2
+ export type AuditVerdict = 'consistent' | 'drifted' | 'inconclusive';
3
+ export type AuditEvidenceKind = 'guard-run' | 'independent-eval' | 'karpathy-cross-check';
4
+ export interface AuditDimension {
5
+ readonly journeyId: JourneyId;
6
+ readonly consistencyScore: number;
7
+ readonly evidence: ReadonlyArray<{
8
+ readonly kind: AuditEvidenceKind;
9
+ readonly ref: string;
10
+ readonly summary: string;
11
+ }>;
12
+ }
13
+ export interface CrossCheck {
14
+ readonly guardVsAudit: 'agree' | 'diverge' | 'partial';
15
+ readonly karpathyVsAudit: 'agree' | 'diverge' | 'partial';
16
+ }
17
+ export interface CapabilityAuditResult {
18
+ readonly auditId: string;
19
+ readonly auditedAt: string;
20
+ readonly verdict: AuditVerdict;
21
+ readonly dimensions: ReadonlyArray<AuditDimension>;
22
+ readonly crossCheck: CrossCheck;
23
+ readonly requiresUserDecision: boolean;
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { BaselineError, BaselineLock, CapabilityBaselineFile } from './types.js';
2
+ export declare function computeBaselineHash(file: CapabilityBaselineFile): string;
3
+ export declare function verifyLock(file: CapabilityBaselineFile, lock: BaselineLock): {
4
+ readonly ok: true;
5
+ } | {
6
+ readonly ok: false;
7
+ readonly error: BaselineError;
8
+ };
9
+ export declare function writeBaselineFile(input: {
10
+ readonly projectRoot: string;
11
+ readonly file: CapabilityBaselineFile;
12
+ }): {
13
+ readonly path: string;
14
+ readonly lockPath: string;
15
+ };
16
+ export declare function readBaselineFile(projectRoot: string): {
17
+ readonly ok: true;
18
+ readonly file: CapabilityBaselineFile;
19
+ readonly lock: BaselineLock;
20
+ readonly path: string;
21
+ readonly lockPath: string;
22
+ } | {
23
+ readonly ok: false;
24
+ readonly error: BaselineError;
25
+ };
26
+ export declare function historySnapshot(input: {
27
+ readonly projectRoot: string;
28
+ readonly version: string;
29
+ }): {
30
+ readonly path: string;
31
+ readonly lockPath: string;
32
+ };
33
+ export declare function currentPointer(sessionId: string, path: string, projectRoot: string): void;
@@ -0,0 +1,80 @@
1
+ // src/services/capability-baseline/store.ts
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ const CURRENT_DIR = (root) => join(root, 'openspec', 'baselines', 'current');
6
+ const FILE_PATH = (root) => join(CURRENT_DIR(root), 'capability-baseline.json');
7
+ const LOCK_PATH = (root) => join(CURRENT_DIR(root), 'capability-baseline.lock');
8
+ const POINTER = (root, sessionId) => join(root, '.peaks', '_runtime', sessionId, 'baselines', 'current.json');
9
+ function hashFor(file) {
10
+ const stripped = {
11
+ schemaVersion: file.schemaVersion,
12
+ version: file.version,
13
+ rows: file.rows
14
+ };
15
+ const json = JSON.stringify(stripped, Object.keys(stripped).sort());
16
+ return createHash('sha256').update(json).digest('hex');
17
+ }
18
+ export function computeBaselineHash(file) {
19
+ return hashFor(file);
20
+ }
21
+ export function verifyLock(file, lock) {
22
+ if (lock.signedBy !== 'SquabbyZ') {
23
+ return { ok: false, error: { code: 'BASELINE_NOT_SIGNED', message: 'lock is not signed by SquabbyZ' } };
24
+ }
25
+ if (hashFor(file) !== lock.baselineHash) {
26
+ return { ok: false, error: { code: 'BASELINE_HASH_MISMATCH', message: 'lock hash does not match file' } };
27
+ }
28
+ return { ok: true };
29
+ }
30
+ export function writeBaselineFile(input) {
31
+ const path = FILE_PATH(input.projectRoot);
32
+ const lockPath = LOCK_PATH(input.projectRoot);
33
+ mkdirSync(CURRENT_DIR(input.projectRoot), { recursive: true });
34
+ writeFileSync(path, JSON.stringify(input.file, null, 2));
35
+ const lock = {
36
+ baselineHash: hashFor(input.file),
37
+ signedBy: 'SquabbyZ',
38
+ signedAt: input.file.signedAt,
39
+ version: input.file.version
40
+ };
41
+ writeFileSync(lockPath, JSON.stringify(lock, null, 2));
42
+ return { path, lockPath };
43
+ }
44
+ export function readBaselineFile(projectRoot) {
45
+ const path = FILE_PATH(projectRoot);
46
+ const lockPath = LOCK_PATH(projectRoot);
47
+ if (!existsSync(path) || !existsSync(lockPath)) {
48
+ return { ok: false, error: { code: 'BASELINE_NOT_FOUND', message: `baseline missing at ${path}` } };
49
+ }
50
+ let file;
51
+ let lock;
52
+ try {
53
+ file = JSON.parse(readFileSync(path, 'utf8'));
54
+ lock = JSON.parse(readFileSync(lockPath, 'utf8'));
55
+ }
56
+ catch (e) {
57
+ return { ok: false, error: { code: 'BASELINE_NOT_FOUND', message: e.message } };
58
+ }
59
+ if (file.signedBy !== 'SquabbyZ') {
60
+ return { ok: false, error: { code: 'BASELINE_NOT_SIGNED', message: 'baseline file is not signed by SquabbyZ' } };
61
+ }
62
+ const v = verifyLock(file, lock);
63
+ if (!v.ok)
64
+ return v;
65
+ return { ok: true, file, lock, path, lockPath };
66
+ }
67
+ export function historySnapshot(input) {
68
+ const target = join(input.projectRoot, 'openspec', 'baselines', 'history', input.version);
69
+ mkdirSync(target, { recursive: true });
70
+ const path = join(target, 'capability-baseline.json');
71
+ const lockPath = join(target, 'capability-baseline.lock');
72
+ copyFileSync(FILE_PATH(input.projectRoot), path);
73
+ copyFileSync(LOCK_PATH(input.projectRoot), lockPath);
74
+ return { path, lockPath };
75
+ }
76
+ export function currentPointer(sessionId, path, projectRoot) {
77
+ const p = POINTER(projectRoot, sessionId);
78
+ mkdirSync(dirname(p), { recursive: true });
79
+ writeFileSync(p, JSON.stringify({ pointsTo: path, at: new Date().toISOString() }, null, 2));
80
+ }
@@ -0,0 +1,45 @@
1
+ export type JourneyId = 'J01' | 'J02' | 'J03' | 'J04' | 'J05' | 'J06' | 'J07' | 'J08' | 'J09' | 'J10' | 'J11' | 'J12' | 'J13' | 'J14' | 'J15';
2
+ export declare const P0_JOURNEY_IDS: ReadonlyArray<JourneyId>;
3
+ export interface InputCase {
4
+ readonly name: string;
5
+ readonly shape: string;
6
+ }
7
+ export interface OutputCase {
8
+ readonly name: string;
9
+ readonly shape: string;
10
+ }
11
+ export interface ErrorCase {
12
+ readonly name: string;
13
+ readonly code: string;
14
+ }
15
+ export interface CapabilityBaselineRow {
16
+ readonly journeyId: JourneyId;
17
+ readonly intent: string;
18
+ readonly observable: {
19
+ readonly inputs: ReadonlyArray<InputCase>;
20
+ readonly outputs: ReadonlyArray<OutputCase>;
21
+ readonly errors: ReadonlyArray<ErrorCase>;
22
+ };
23
+ readonly invariants: ReadonlyArray<string>;
24
+ readonly forbiddenChanges: ReadonlyArray<string>;
25
+ readonly sourceFiles: ReadonlyArray<string>;
26
+ }
27
+ export interface CapabilityBaselineFile {
28
+ readonly schemaVersion: '2026-08-03';
29
+ readonly version: string;
30
+ readonly signedBy: 'SquabbyZ';
31
+ readonly signedAt: string;
32
+ readonly rows: ReadonlyArray<CapabilityBaselineRow>;
33
+ }
34
+ export interface BaselineLock {
35
+ readonly baselineHash: string;
36
+ readonly signedBy: 'SquabbyZ';
37
+ readonly signedAt: string;
38
+ readonly version: string;
39
+ }
40
+ export type BaselineErrorCode = 'BASELINE_NOT_FOUND' | 'BASELINE_HASH_MISMATCH' | 'BASELINE_NOT_SIGNED' | 'BASELINE_INCOMPLETE' | 'BASELINE_HISTORY_GAP' | 'BASELINE_ROW_SHAPE_INVALID' | 'BASELINE_FORBIDDEN_ALIAS';
41
+ export interface BaselineError {
42
+ readonly code: BaselineErrorCode;
43
+ readonly message: string;
44
+ readonly detail?: string;
45
+ }
@@ -0,0 +1,5 @@
1
+ export const P0_JOURNEY_IDS = [
2
+ 'J01', 'J02', 'J03', 'J04', 'J05',
3
+ 'J06', 'J07', 'J08', 'J09', 'J10',
4
+ 'J11', 'J12', 'J13', 'J14', 'J15'
5
+ ];
@@ -0,0 +1,19 @@
1
+ import { type BaselineError, type CapabilityBaselineFile, type CapabilityBaselineRow } from './types.js';
2
+ export declare function validateRowShape(row: CapabilityBaselineRow): {
3
+ readonly ok: true;
4
+ } | {
5
+ readonly ok: false;
6
+ readonly error: BaselineError;
7
+ };
8
+ export declare function validateBaselineFile(file: CapabilityBaselineFile): {
9
+ readonly ok: true;
10
+ } | {
11
+ readonly ok: false;
12
+ readonly error: BaselineError;
13
+ };
14
+ export declare function assertSignedBySquabbyZ(file: CapabilityBaselineFile): {
15
+ readonly ok: true;
16
+ } | {
17
+ readonly ok: false;
18
+ readonly error: BaselineError;
19
+ };
@@ -0,0 +1,38 @@
1
+ // src/services/capability-baseline/validator.ts
2
+ import { P0_JOURNEY_IDS } from './types.js';
3
+ export function validateRowShape(row) {
4
+ if (row.intent.trim().length === 0)
5
+ return { ok: false, error: { code: 'BASELINE_ROW_SHAPE_INVALID', message: `row ${row.journeyId} has empty intent` } };
6
+ if (row.invariants.length === 0)
7
+ return { ok: false, error: { code: 'BASELINE_ROW_SHAPE_INVALID', message: `row ${row.journeyId} has no invariants` } };
8
+ if (row.forbiddenChanges.length === 0)
9
+ return { ok: false, error: { code: 'BASELINE_ROW_SHAPE_INVALID', message: `row ${row.journeyId} has no forbiddenChanges` } };
10
+ if (row.sourceFiles.length === 0)
11
+ return { ok: false, error: { code: 'BASELINE_ROW_SHAPE_INVALID', message: `row ${row.journeyId} has no sourceFiles` } };
12
+ return { ok: true };
13
+ }
14
+ export function validateBaselineFile(file) {
15
+ if (file.schemaVersion !== '2026-08-03') {
16
+ return { ok: false, error: { code: 'BASELINE_ROW_SHAPE_INVALID', message: `unsupported schemaVersion ${file.schemaVersion}` } };
17
+ }
18
+ const signed = assertSignedBySquabbyZ(file);
19
+ if (!signed.ok)
20
+ return signed;
21
+ for (const r of file.rows) {
22
+ const v = validateRowShape(r);
23
+ if (!v.ok)
24
+ return v;
25
+ }
26
+ const present = new Set(file.rows.map((r) => r.journeyId));
27
+ const missing = P0_JOURNEY_IDS.filter((j) => !present.has(j));
28
+ if (missing.length > 0) {
29
+ return { ok: false, error: { code: 'BASELINE_INCOMPLETE', message: `missing journeys: ${missing.join(',')}` } };
30
+ }
31
+ return { ok: true };
32
+ }
33
+ export function assertSignedBySquabbyZ(file) {
34
+ if (file.signedBy !== 'SquabbyZ') {
35
+ return { ok: false, error: { code: 'BASELINE_NOT_SIGNED', message: 'baseline not signed by SquabbyZ' } };
36
+ }
37
+ return { ok: true };
38
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ01Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,48 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ const FIXTURES = [
4
+ ['make', 'implement a CLI parser'],
5
+ ['make', 'refactor the service'],
6
+ ['make', 'write a blog article'],
7
+ ['learn', 'author an SOP checklist'],
8
+ ['check', 'run red-lines audit'],
9
+ ['run', 'execute a workflow']
10
+ ];
11
+ export async function runJ01Contract(ctx) {
12
+ const bin = process.env.PEAKS_BIN_OVERRIDE ?? join(ctx.projectRoot, 'bin', 'peaks.js');
13
+ let allOk = true;
14
+ let firstFailure = '';
15
+ for (const [command, input] of FIXTURES) {
16
+ try {
17
+ const stdout = execFileSync('node', [bin, command, input], {
18
+ cwd: ctx.projectRoot,
19
+ env: { ...process.env, PEAKS_CALLER_ID: `guard-J01-${ctx.sessionId}` }
20
+ }).toString('utf8');
21
+ const env = JSON.parse(stdout);
22
+ if (!env.ok) {
23
+ allOk = false;
24
+ firstFailure = `${command} ${input}`;
25
+ break;
26
+ }
27
+ }
28
+ catch (e) {
29
+ allOk = false;
30
+ firstFailure = `${command} ${input}: ${e.message}`;
31
+ break;
32
+ }
33
+ }
34
+ return allOk
35
+ ? {
36
+ journeyId: 'J01',
37
+ contract: 'envelope-arg-shapes',
38
+ status: 'pass',
39
+ artifactPath: 'tests/integration/super-command-routing.test.ts'
40
+ }
41
+ : {
42
+ journeyId: 'J01',
43
+ contract: 'envelope-arg-shapes',
44
+ status: 'fail',
45
+ diff: { before: 'all 6 routing cases ok', after: firstFailure, reason: 'J01#1 broken: super-command routing NL path deviates from frozen baseline' },
46
+ artifactPath: 'tests/integration/super-command-routing.test.ts'
47
+ };
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ02Contract(ctx: GuardContext, projectRoot?: string): Promise<GuardRunResult>;