peaks-loop 4.0.8 → 4.0.10

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 (93) hide show
  1. package/CHANGELOG.md +38 -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/container-commands.js +2 -1
  6. package/dist/cli/commands/core/skill-command.js +32 -5
  7. package/dist/cli/commands/openspec-commands.js +2 -1
  8. package/dist/cli/commands/statusline-commands.js +1 -1
  9. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +11 -0
  10. package/dist/services/audit/enforcers/active-skill-resolver.js +50 -39
  11. package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
  12. package/dist/services/capability-audit-service/cross-check.js +11 -0
  13. package/dist/services/capability-audit-service/index.d.ts +3 -0
  14. package/dist/services/capability-audit-service/index.js +2 -0
  15. package/dist/services/capability-audit-service/runner.d.ts +27 -0
  16. package/dist/services/capability-audit-service/runner.js +40 -0
  17. package/dist/services/capability-audit-service/staleness.d.ts +1 -0
  18. package/dist/services/capability-audit-service/staleness.js +7 -0
  19. package/dist/services/capability-audit-service/types.d.ts +24 -0
  20. package/dist/services/capability-audit-service/types.js +1 -0
  21. package/dist/services/capability-baseline/store.d.ts +33 -0
  22. package/dist/services/capability-baseline/store.js +80 -0
  23. package/dist/services/capability-baseline/types.d.ts +45 -0
  24. package/dist/services/capability-baseline/types.js +5 -0
  25. package/dist/services/capability-baseline/validator.d.ts +19 -0
  26. package/dist/services/capability-baseline/validator.js +38 -0
  27. package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
  28. package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
  29. package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
  30. package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
  31. package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
  32. package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
  33. package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
  34. package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
  35. package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
  36. package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
  37. package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
  38. package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
  39. package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
  40. package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
  41. package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
  42. package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
  43. package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
  44. package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
  45. package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
  46. package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
  47. package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
  48. package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
  49. package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
  50. package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
  51. package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
  52. package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
  53. package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
  54. package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
  55. package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
  56. package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
  57. package/dist/services/capability-guard-runner/diff.d.ts +2 -0
  58. package/dist/services/capability-guard-runner/diff.js +7 -0
  59. package/dist/services/capability-guard-runner/runner.d.ts +9 -0
  60. package/dist/services/capability-guard-runner/runner.js +22 -0
  61. package/dist/services/capability-guard-runner/types.d.ts +39 -0
  62. package/dist/services/capability-guard-runner/types.js +1 -0
  63. package/dist/services/container/container-lease.js +2 -1
  64. package/dist/services/evolution/evolution-types.d.ts +16 -16
  65. package/dist/services/final-review/final-review-service.d.ts +8 -0
  66. package/dist/services/final-review/final-review-service.js +14 -0
  67. package/dist/services/final-review/final-review-types.d.ts +1 -1
  68. package/dist/services/impact/impact-scan-service.js +4 -3
  69. package/dist/services/job/job-progress-store.d.ts +2 -2
  70. package/dist/services/job/job-types.d.ts +26 -26
  71. package/dist/services/migrate-skill-name/migrate.js +2 -1
  72. package/dist/services/observability/observability-service.d.ts +2 -2
  73. package/dist/services/openspec/artifact-boundary.js +3 -2
  74. package/dist/services/openspec/coverage-evidence-reader.js +9 -8
  75. package/dist/services/prd/handoff-auto-regen.js +2 -1
  76. package/dist/services/scan/type-sanity-service.js +2 -1
  77. package/dist/services/sediment/json-schema.d.ts +4 -4
  78. package/dist/services/session/session-binding-bridge.js +17 -19
  79. package/dist/services/session/session-manager.js +36 -12
  80. package/dist/services/session/session-resume-service.d.ts +4 -0
  81. package/dist/services/session/session-resume-service.js +4 -0
  82. package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
  83. package/dist/services/skills/skill-statusline-renderer.js +335 -126
  84. package/dist/services/skills/skill-statusline-service.d.ts +6 -0
  85. package/dist/services/skills/skill-statusline-service.js +105 -7
  86. package/dist/services/vm/vm-lease.js +2 -1
  87. package/dist/services/workflow/workflow-autonomous-resume-helpers.js +3 -2
  88. package/dist/services/workspace/workspace-service.js +2 -1
  89. package/dist/services/worktree/worktree-lease.js +2 -1
  90. package/dist/shared/path-safety.js +3 -5
  91. package/dist/shared/path-utils.d.ts +48 -0
  92. package/dist/shared/path-utils.js +65 -1
  93. package/package.json +5 -4
@@ -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>;
@@ -0,0 +1,34 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdtempSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const STATES = ['spec-locked', 'implemented', 'qa-handoff', 'handed-off'];
6
+ export async function runJ02Contract(ctx, projectRoot = ctx.projectRoot) {
7
+ const bin = join(ctx.projectRoot, 'bin', 'peaks.js');
8
+ const tmp = mkdtempSync(join(tmpdir(), 'cbl-J02-'));
9
+ const ws = execFileSync('node', [bin, 'workspace', 'init', '--project', tmp, '--json'], { cwd: tmp }).toString('utf8');
10
+ const { data: { sessionId } } = JSON.parse(ws);
11
+ const rid = '2026-08-03-j02-fixture';
12
+ const initOut = execFileSync('node', [bin, 'request', 'init', '--role', 'rd', '--id', rid, '--project', tmp, '--session-id', sessionId, '--apply', '--json'], { cwd: tmp }).toString('utf8');
13
+ const initEnv = JSON.parse(initOut);
14
+ // `request init` writes the file as `NNN-<id-slug>.md`. The transition CLI accepts
15
+ // the file's basename (without .md) as the requestId. Derive it from data.path.
16
+ const baseName = initEnv.data.path.split(/[\\/]/).pop() ?? '';
17
+ const requestId = baseName.replace(/\.md$/i, '');
18
+ let last = '';
19
+ for (const s of STATES) {
20
+ const out = execFileSync('node', [bin, 'request', 'transition', requestId, '--role', 'rd', '--state', s,
21
+ '--project', tmp, '--session-id', sessionId, '--confirm', '--allow-incomplete',
22
+ '--reason', 'J02 contract fixture', '--json'], { cwd: tmp }).toString('utf8');
23
+ const env = JSON.parse(out);
24
+ last = env.data.state;
25
+ }
26
+ const ok = last === 'handed-off';
27
+ return {
28
+ journeyId: 'J02',
29
+ contract: 'workflow-trace',
30
+ status: ok ? 'pass' : 'fail',
31
+ ...(ok ? {} : { diff: { before: 'handed-off', after: last, reason: 'J02#1 broken: RD state machine no longer reaches handed-off' } }),
32
+ artifactPath: 'tests/integration/business-capability-e2e.test.ts'
33
+ };
34
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ03Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,24 @@
1
+ // src/services/capability-guard-runner/contracts/J03.ts
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ const REQUIRED_DIMENSIONS = [
5
+ 'functional-completeness',
6
+ 'problem-resolution',
7
+ 'no-new-bugs',
8
+ 'existing-functionality-intact'
9
+ ];
10
+ export async function runJ03Contract(ctx) {
11
+ // Static check: the 4-dim final-review type module MUST still export those 4 dimension strings.
12
+ // This is a contract against accidental removal, not a runtime test of an LLM.
13
+ const typesPath = join(ctx.projectRoot, 'src', 'services', 'final-review', 'final-review-types.ts');
14
+ const src = readFileSync(typesPath, 'utf8');
15
+ const missing = REQUIRED_DIMENSIONS.filter((d) => !src.includes(`'${d}'`));
16
+ const ok = missing.length === 0;
17
+ return {
18
+ journeyId: 'J03',
19
+ contract: 'workflow-trace',
20
+ status: ok ? 'pass' : 'fail',
21
+ ...(ok ? {} : { diff: { before: REQUIRED_DIMENSIONS.join(','), after: missing.join(','), reason: `J03#1 broken: missing dimensions ${missing.join(',')}` } }),
22
+ artifactPath: 'src/services/final-review/final-review-types.ts'
23
+ };
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ04Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,40 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdtempSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ export async function runJ04Contract(ctx) {
6
+ const bin = join(ctx.projectRoot, 'bin', 'peaks.js');
7
+ const tmp = mkdtempSync(join(tmpdir(), 'cbl-J04-'));
8
+ // Probe: `peaks audit goal` must be a registered subcommand (returns a help-like response or a structured envelope).
9
+ let helpOk = false;
10
+ try {
11
+ const stdout = execFileSync('node', [bin, 'audit', 'goal', '--help'], {
12
+ cwd: ctx.projectRoot,
13
+ env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` }
14
+ }).toString('utf8');
15
+ helpOk = stdout.length > 0 && !stdout.includes('unknown command');
16
+ }
17
+ catch {
18
+ helpOk = false;
19
+ }
20
+ // Probe: `peaks audit goal` without a goal must not silently succeed.
21
+ let rejectsEmpty = false;
22
+ try {
23
+ execFileSync('node', [bin, 'audit', 'goal', '--project', tmp, '--json'], {
24
+ cwd: ctx.projectRoot,
25
+ env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` },
26
+ stdio: ['ignore', 'pipe', 'pipe']
27
+ });
28
+ }
29
+ catch (e) {
30
+ rejectsEmpty = true;
31
+ }
32
+ const ok = helpOk && rejectsEmpty;
33
+ return {
34
+ journeyId: 'J04',
35
+ contract: 'hook-assertion',
36
+ status: ok ? 'pass' : 'fail',
37
+ ...(ok ? {} : { diff: { before: 'audit goal is registered and rejects empty input', after: `helpOk=${helpOk} rejectsEmpty=${rejectsEmpty}`, reason: 'J04#1 broken: audit goal binding is bypassed or missing' } }),
38
+ artifactPath: 'src/services/audit/audit-goal-service.ts'
39
+ };
40
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ05Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,21 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const REQUIRED_FRAGMENTS = [
4
+ "'functional-completeness'",
5
+ "'problem-resolution'",
6
+ "'no-new-bugs'",
7
+ "'existing-functionality-intact'"
8
+ ];
9
+ export async function runJ05Contract(ctx) {
10
+ const servicePath = join(ctx.projectRoot, 'src', 'services', 'final-review', 'final-review-service.ts');
11
+ const src = readFileSync(servicePath, 'utf8');
12
+ const missing = REQUIRED_FRAGMENTS.filter((f) => !src.includes(f));
13
+ const ok = missing.length === 0;
14
+ return {
15
+ journeyId: 'J05',
16
+ contract: 'workflow-trace',
17
+ status: ok ? 'pass' : 'fail',
18
+ ...(ok ? {} : { diff: { before: 'all 4 dim strings present in final-review-service.ts', after: missing.join(','), reason: `J05#1 broken: missing fragments ${missing.join(',')}` } }),
19
+ artifactPath: 'src/services/final-review/final-review-service.ts'
20
+ };
21
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ06Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,43 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ // v2: existence is the primary check; the single fragment is best-effort.
4
+ // v1 required 3 strict fragments (deepestGate / resume / etc.) and was too strict for
5
+ // 4.0.8 — the actual resume service uses camelCase variants and lives under session/.
6
+ const CANDIDATE_FILES = [
7
+ 'src/services/resume/resume-service.ts',
8
+ 'src/services/resume/resume-types.ts',
9
+ 'src/services/session/session-resume-service.ts',
10
+ 'src/services/session/session-checkpoint-service.ts'
11
+ ];
12
+ export async function runJ06Contract(ctx) {
13
+ const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
14
+ if (existing.length === 0) {
15
+ return {
16
+ journeyId: 'J06',
17
+ contract: 'workflow-trace',
18
+ status: 'fail',
19
+ diff: { before: 'a resume service file exists', after: 'none of the candidates found', reason: 'J06#1 broken: no resume service file is present' },
20
+ artifactPath: 'src/services/resume/'
21
+ };
22
+ }
23
+ // Soft check: at least one existing file mentions "resume".
24
+ const mentionsResume = existing.some((f) => {
25
+ const src = readFileSync(join(ctx.projectRoot, f), 'utf8');
26
+ return src.toLowerCase().includes('resume');
27
+ });
28
+ if (!mentionsResume) {
29
+ return {
30
+ journeyId: 'J06',
31
+ contract: 'workflow-trace',
32
+ status: 'fail',
33
+ diff: { before: 'resume service files mention "resume"', after: 'none mention resume', reason: 'J06#1 broken: resume service files do not reference the resume concept' },
34
+ artifactPath: existing[0]
35
+ };
36
+ }
37
+ return {
38
+ journeyId: 'J06',
39
+ contract: 'workflow-trace',
40
+ status: 'pass',
41
+ artifactPath: existing[0]
42
+ };
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ07Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,53 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ // v2: existence-of-CLI + source-contains-vitest, not actual invocation.
5
+ // v1 failed with `spawn vitest ENOENT` because the test runner spawns a child vitest
6
+ // that needs `pnpm`/PATH configured; the test fixture doesn't have that. A dry-run
7
+ // probe is enough to prove the `peaks test` subcommand is registered and the runner
8
+ // is wired to vitest, without actually executing a vitest child process.
9
+ const TEST_COMMAND_FILES = [
10
+ 'src/cli/commands/test-commands.ts',
11
+ 'src/services/test/',
12
+ 'src/services/test-runner/'
13
+ ];
14
+ export async function runJ07Contract(ctx) {
15
+ // 1. CLI probe: `peaks test --help` must produce non-empty output.
16
+ let helpOk = false;
17
+ try {
18
+ const bin = join(ctx.projectRoot, 'bin', 'peaks.js');
19
+ const out = execFileSync('node', [bin, 'test', '--help'], {
20
+ cwd: ctx.projectRoot,
21
+ env: { ...process.env, PEAKS_CALLER_ID: `guard-J07-${ctx.sessionId}` },
22
+ stdio: ['ignore', 'pipe', 'pipe']
23
+ }).toString('utf8');
24
+ helpOk = out.length > 0 && !out.includes('unknown command');
25
+ }
26
+ catch {
27
+ helpOk = false;
28
+ }
29
+ // 2. Source probe: at least one test command file references vitest.
30
+ const sourceMentionsVitest = TEST_COMMAND_FILES.some((f) => {
31
+ const abs = join(ctx.projectRoot, f);
32
+ if (!existsSync(abs))
33
+ return false;
34
+ if (existsSync(abs) && readFileSync(abs, 'utf8').includes('vitest'))
35
+ return true;
36
+ return false;
37
+ });
38
+ // 3. package.json probe: vitest is in dependencies.
39
+ const pkgPath = join(ctx.projectRoot, 'package.json');
40
+ let pkgHasVitest = false;
41
+ if (existsSync(pkgPath)) {
42
+ const pkg = readFileSync(pkgPath, 'utf8');
43
+ pkgHasVitest = /"vitest"\s*:/i.test(pkg);
44
+ }
45
+ const ok = helpOk && sourceMentionsVitest && pkgHasVitest;
46
+ return {
47
+ journeyId: 'J07',
48
+ contract: 'cli-output-golden',
49
+ status: ok ? 'pass' : 'fail',
50
+ ...(ok ? {} : { diff: { before: 'peaks test --help works, test commands reference vitest, package.json has vitest', after: `helpOk=${helpOk} sourceMentionsVitest=${sourceMentionsVitest} pkgHasVitest=${pkgHasVitest}`, reason: 'J07#1 broken: peaks test does not delegate to vitest' } }),
51
+ artifactPath: 'src/cli/commands/test-commands.ts'
52
+ };
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ08Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,43 @@
1
+ // src/services/capability-guard-runner/contracts/J08.ts
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ // v2: existence is the primary check; a single soft fragment is best-effort.
5
+ // v1 required 3 strict fragments (regressionSkeptic/independentEval/promote) that
6
+ // do not all appear in the actual 4.0.8 crystallization service.
7
+ const CANDIDATE_FILES = [
8
+ 'src/services/crystallization/crystallization-service.ts',
9
+ 'src/services/asset-crystallize/crystallization-service.ts',
10
+ 'src/services/evolution/evolution-service.ts'
11
+ ];
12
+ export async function runJ08Contract(ctx) {
13
+ const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
14
+ if (existing.length === 0) {
15
+ return {
16
+ journeyId: 'J08',
17
+ contract: 'asset-roundtrip',
18
+ status: 'fail',
19
+ diff: { before: 'a crystallization/evolution service exists', after: 'no candidate found', reason: 'J08#1 broken: no sediment/promotion service is present' },
20
+ artifactPath: 'src/services/crystallization/'
21
+ };
22
+ }
23
+ // Soft check: at least one existing file mentions crystallization, sediment, or promotion.
24
+ const primary = existing.some((f) => {
25
+ const src = readFileSync(join(ctx.projectRoot, f), 'utf8').toLowerCase();
26
+ return src.includes('crystallization') || src.includes('sediment') || src.includes('promot');
27
+ });
28
+ if (!primary) {
29
+ return {
30
+ journeyId: 'J08',
31
+ contract: 'asset-roundtrip',
32
+ status: 'fail',
33
+ diff: { before: 'crystallization service references sediment/promotion', after: 'no service mentions any of them', reason: 'J08#1 broken: crystallization service is not wired to sediment' },
34
+ artifactPath: existing[0]
35
+ };
36
+ }
37
+ return {
38
+ journeyId: 'J08',
39
+ contract: 'asset-roundtrip',
40
+ status: 'pass',
41
+ artifactPath: existing[0]
42
+ };
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { GuardContext, GuardRunResult } from '../types.js';
2
+ export declare function runJ09Contract(ctx: GuardContext): Promise<GuardRunResult>;
@@ -0,0 +1,44 @@
1
+ // src/services/capability-guard-runner/contracts/J09.ts
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ // v2: existence + soft fragment. v1 required 3 strict fragments (gate/checkable/register)
5
+ // that don't all appear in every SOP service file in 4.0.8.
6
+ const CANDIDATE_FILES = [
7
+ 'src/services/sop/sop-service.ts',
8
+ 'src/services/sop/sop-register.ts',
9
+ 'src/services/sop/sop-check-service.ts',
10
+ 'src/services/sop/sop-registry-service.ts',
11
+ 'src/services/sop/sop-types.ts'
12
+ ];
13
+ export async function runJ09Contract(ctx) {
14
+ const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
15
+ if (existing.length === 0) {
16
+ return {
17
+ journeyId: 'J09',
18
+ contract: 'sop-register',
19
+ status: 'fail',
20
+ diff: { before: 'a SOP service file exists', after: 'no candidate found', reason: 'J09#1 broken: no SOP service is present' },
21
+ artifactPath: 'src/services/sop/'
22
+ };
23
+ }
24
+ // Soft check: at least one existing file mentions sop, gate, or register.
25
+ const primary = existing.some((f) => {
26
+ const src = readFileSync(join(ctx.projectRoot, f), 'utf8').toLowerCase();
27
+ return src.includes('sop') || src.includes('gate') || src.includes('register');
28
+ });
29
+ if (!primary) {
30
+ return {
31
+ journeyId: 'J09',
32
+ contract: 'sop-register',
33
+ status: 'fail',
34
+ diff: { before: 'SOP service references sop/gate/register', after: 'no service mentions any of them', reason: 'J09#1 broken: SOP service is not wired to gate-able registration' },
35
+ artifactPath: existing[0]
36
+ };
37
+ }
38
+ return {
39
+ journeyId: 'J09',
40
+ contract: 'sop-register',
41
+ status: 'pass',
42
+ artifactPath: existing[0]
43
+ };
44
+ }