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.
- package/CHANGELOG.md +12 -0
- package/dist/cli/commands/_register.js +2 -0
- package/dist/cli/commands/baseline-commands.d.ts +3 -0
- package/dist/cli/commands/baseline-commands.js +146 -0
- package/dist/cli/commands/statusline-commands.js +1 -1
- package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
- package/dist/services/capability-audit-service/cross-check.js +11 -0
- package/dist/services/capability-audit-service/index.d.ts +3 -0
- package/dist/services/capability-audit-service/index.js +2 -0
- package/dist/services/capability-audit-service/runner.d.ts +27 -0
- package/dist/services/capability-audit-service/runner.js +40 -0
- package/dist/services/capability-audit-service/staleness.d.ts +1 -0
- package/dist/services/capability-audit-service/staleness.js +7 -0
- package/dist/services/capability-audit-service/types.d.ts +24 -0
- package/dist/services/capability-audit-service/types.js +1 -0
- package/dist/services/capability-baseline/store.d.ts +33 -0
- package/dist/services/capability-baseline/store.js +80 -0
- package/dist/services/capability-baseline/types.d.ts +45 -0
- package/dist/services/capability-baseline/types.js +5 -0
- package/dist/services/capability-baseline/validator.d.ts +19 -0
- package/dist/services/capability-baseline/validator.js +38 -0
- package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
- package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
- package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
- package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
- package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
- package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
- package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
- package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
- package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
- package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
- package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
- package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
- package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
- package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
- package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
- package/dist/services/capability-guard-runner/diff.d.ts +2 -0
- package/dist/services/capability-guard-runner/diff.js +7 -0
- package/dist/services/capability-guard-runner/runner.d.ts +9 -0
- package/dist/services/capability-guard-runner/runner.js +22 -0
- package/dist/services/capability-guard-runner/types.d.ts +39 -0
- package/dist/services/capability-guard-runner/types.js +1 -0
- package/dist/services/evolution/evolution-types.d.ts +16 -16
- package/dist/services/final-review/final-review-service.d.ts +8 -0
- package/dist/services/final-review/final-review-service.js +14 -0
- package/dist/services/final-review/final-review-types.d.ts +1 -1
- package/dist/services/job/job-progress-store.d.ts +2 -2
- package/dist/services/job/job-types.d.ts +26 -26
- package/dist/services/observability/observability-service.d.ts +2 -2
- package/dist/services/sediment/json-schema.d.ts +4 -4
- package/dist/services/session/session-resume-service.d.ts +4 -0
- package/dist/services/session/session-resume-service.js +4 -0
- package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
- package/dist/services/skills/skill-statusline-renderer.js +312 -100
- package/package.json +5 -4
|
@@ -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,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,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,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,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,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,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,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
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/services/capability-guard-runner/contracts/J10.ts
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const CANDIDATE_FILES = [
|
|
5
|
+
'src/cli/commands/hooks-commands.ts',
|
|
6
|
+
'src/cli/commands/ide-commands.ts',
|
|
7
|
+
'src/cli/commands/hook-handle.ts',
|
|
8
|
+
'src/hooks/pre-tool-use-sub-agent.ts'
|
|
9
|
+
];
|
|
10
|
+
export async function runJ10Contract(ctx) {
|
|
11
|
+
const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
|
|
12
|
+
if (existing.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
journeyId: 'J10',
|
|
15
|
+
contract: 'hook-assertion',
|
|
16
|
+
status: 'fail',
|
|
17
|
+
diff: { before: 'a hooks/ide/handle CLI file exists', after: 'no candidate found', reason: 'J10#1 broken: no IDE install/hook infrastructure is present' },
|
|
18
|
+
artifactPath: 'src/cli/commands/hooks-commands.ts'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const primary = existing.some((f) => {
|
|
22
|
+
const src = readFileSync(join(ctx.projectRoot, f), 'utf8').toLowerCase();
|
|
23
|
+
return src.includes('hook') || src.includes('install') || src.includes('ide') || src.includes('adapter');
|
|
24
|
+
});
|
|
25
|
+
if (!primary) {
|
|
26
|
+
return {
|
|
27
|
+
journeyId: 'J10',
|
|
28
|
+
contract: 'hook-assertion',
|
|
29
|
+
status: 'fail',
|
|
30
|
+
diff: { before: 'hooks/ide commands reference install/hook/ide/adapter', after: 'no such reference', reason: 'J10#1 broken: hooks/ide commands do not implement install/hook/ide/adapter' },
|
|
31
|
+
artifactPath: existing[0]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
journeyId: 'J10',
|
|
36
|
+
contract: 'hook-assertion',
|
|
37
|
+
status: 'pass',
|
|
38
|
+
artifactPath: existing[0]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const CANDIDATE_FILES = [
|
|
4
|
+
'src/services/doctor/doctor-service.ts',
|
|
5
|
+
'src/services/audit/audit-service.ts',
|
|
6
|
+
'src/cli/commands/openspec-commands.ts',
|
|
7
|
+
'src/services/openspec/openspec-propose-from-doctor-service.ts'
|
|
8
|
+
];
|
|
9
|
+
export async function runJ11Contract(ctx) {
|
|
10
|
+
const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
|
|
11
|
+
if (existing.length === 0) {
|
|
12
|
+
return {
|
|
13
|
+
journeyId: 'J11',
|
|
14
|
+
contract: 'cli-output-golden',
|
|
15
|
+
status: 'fail',
|
|
16
|
+
diff: { before: 'a doctor/audit/openspec service exists', after: 'no candidate found', reason: 'J11#1 broken: no doctor surface is present' },
|
|
17
|
+
artifactPath: 'src/services/doctor/doctor-service.ts'
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const primary = existing.some((f) => {
|
|
21
|
+
const src = readFileSync(join(ctx.projectRoot, f), 'utf8').toLowerCase();
|
|
22
|
+
return src.includes('doctor') || src.includes('audit') || src.includes('openspec') || src.includes('health');
|
|
23
|
+
});
|
|
24
|
+
if (!primary) {
|
|
25
|
+
return {
|
|
26
|
+
journeyId: 'J11',
|
|
27
|
+
contract: 'cli-output-golden',
|
|
28
|
+
status: 'fail',
|
|
29
|
+
diff: { before: 'doctor/audit/openspec references health/audit/openspec', after: 'no such reference', reason: 'J11#1 broken: doctor/audit/openspec services are not wired' },
|
|
30
|
+
artifactPath: existing[0]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
journeyId: 'J11',
|
|
35
|
+
contract: 'cli-output-golden',
|
|
36
|
+
status: 'pass',
|
|
37
|
+
artifactPath: existing[0]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const CANDIDATE_FILES = [
|
|
4
|
+
'src/services/worktree/lease-store.ts',
|
|
5
|
+
'src/services/worktree/worktree-lease.ts',
|
|
6
|
+
'src/services/dispatch/sub-agent-dispatcher.ts',
|
|
7
|
+
'src/services/lease-metrics/lease-metrics-service.ts'
|
|
8
|
+
];
|
|
9
|
+
const REQUIRED_FRAGMENTS = ['lease', 'release'];
|
|
10
|
+
export async function runJ12Contract(ctx) {
|
|
11
|
+
const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
|
|
12
|
+
if (existing.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
journeyId: 'J12',
|
|
15
|
+
contract: 'concurrency-lease',
|
|
16
|
+
status: 'fail',
|
|
17
|
+
diff: { before: 'a worktree/lease/dispatch service exists', after: 'no candidate found', reason: 'J12#1 broken: no lease infrastructure is present' },
|
|
18
|
+
artifactPath: 'src/services/worktree/'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Soft: at least one file references BOTH 'lease' AND 'release' in its content.
|
|
22
|
+
const fullCoverage = existing.some((f) => {
|
|
23
|
+
const src = readFileSync(join(ctx.projectRoot, f), 'utf8').toLowerCase();
|
|
24
|
+
return REQUIRED_FRAGMENTS.every((frag) => src.includes(frag));
|
|
25
|
+
});
|
|
26
|
+
const ok = fullCoverage;
|
|
27
|
+
return {
|
|
28
|
+
journeyId: 'J12',
|
|
29
|
+
contract: 'concurrency-lease',
|
|
30
|
+
status: ok ? 'pass' : 'fail',
|
|
31
|
+
...(ok ? {} : { diff: { before: 'a worktree/lease service references both lease and release', after: 'no such file', reason: 'J12#1 broken: lease lifecycle (lease + release) is incomplete' } }),
|
|
32
|
+
artifactPath: existing[0]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const CANDIDATE_FILES = [
|
|
4
|
+
'skills/peaks-content/SKILL.md',
|
|
5
|
+
'src/services/content/',
|
|
6
|
+
'src/services/workflow/workflow-router-service.ts',
|
|
7
|
+
'.peaks/standards/loop-engineering-guidelines.md'
|
|
8
|
+
];
|
|
9
|
+
const STAGES = ['draft', 'edit', 'tone', 'publish', 'archive'];
|
|
10
|
+
export async function runJ13Contract(ctx) {
|
|
11
|
+
const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
|
|
12
|
+
if (existing.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
journeyId: 'J13',
|
|
15
|
+
contract: 'workflow-trace',
|
|
16
|
+
status: 'fail',
|
|
17
|
+
diff: { before: 'a content / peaks-content skill exists', after: 'no candidate found', reason: 'J13#1 broken: no content production surface is present' },
|
|
18
|
+
artifactPath: 'skills/peaks-content/SKILL.md'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Soft check: at least 3 of 5 stages appear in any existing file's content.
|
|
22
|
+
const matchedStages = new Set();
|
|
23
|
+
for (const file of existing) {
|
|
24
|
+
const src = readFileSync(join(ctx.projectRoot, file), 'utf8').toLowerCase();
|
|
25
|
+
for (const stage of STAGES) {
|
|
26
|
+
if (src.includes(stage))
|
|
27
|
+
matchedStages.add(stage);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (matchedStages.size < 3) {
|
|
31
|
+
return {
|
|
32
|
+
journeyId: 'J13',
|
|
33
|
+
contract: 'workflow-trace',
|
|
34
|
+
status: 'fail',
|
|
35
|
+
diff: { before: 'at least 3 of draft/edit/tone/publish/archive stages are referenced', after: `only ${[...matchedStages].join(',')} found`, reason: 'J13#1 broken: content pipeline has too few stages' },
|
|
36
|
+
artifactPath: existing[0]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
journeyId: 'J13',
|
|
41
|
+
contract: 'workflow-trace',
|
|
42
|
+
status: 'pass',
|
|
43
|
+
artifactPath: existing[0]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const CANDIDATE_FILES = [
|
|
4
|
+
'skills/peaks-issue-fix-orchestrator/SKILL.md',
|
|
5
|
+
'.peaks/memory/peaks-loop-positioning-loop-engineering.md',
|
|
6
|
+
'.peaks/standards/loop-engineering-guidelines.md'
|
|
7
|
+
];
|
|
8
|
+
const REQUIRED_STAGES = ['triage', 'classify', 'fix', 'commit'];
|
|
9
|
+
export async function runJ14Contract(ctx) {
|
|
10
|
+
const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
|
|
11
|
+
if (existing.length === 0) {
|
|
12
|
+
return {
|
|
13
|
+
journeyId: 'J14',
|
|
14
|
+
contract: 'workflow-trace',
|
|
15
|
+
status: 'fail',
|
|
16
|
+
diff: { before: 'a peaks-issue-fix-orchestrator skill or memory file exists', after: 'no candidate found', reason: 'J14#1 broken: no issue-sweep surface is present' },
|
|
17
|
+
artifactPath: 'skills/peaks-issue-fix-orchestrator/SKILL.md'
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const matched = new Set();
|
|
21
|
+
for (const file of existing) {
|
|
22
|
+
const src = readFileSync(join(ctx.projectRoot, file), 'utf8').toLowerCase();
|
|
23
|
+
for (const stage of REQUIRED_STAGES) {
|
|
24
|
+
if (src.includes(stage))
|
|
25
|
+
matched.add(stage);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const ok = matched.size >= 2;
|
|
29
|
+
return {
|
|
30
|
+
journeyId: 'J14',
|
|
31
|
+
contract: 'workflow-trace',
|
|
32
|
+
status: ok ? 'pass' : 'fail',
|
|
33
|
+
...(ok ? {} : { diff: { before: 'at least 2 of triage/classify/fix/commit are referenced', after: `only ${[...matched].join(',')} found`, reason: 'J14#1 broken: issue-sweep pipeline has too few stages' } }),
|
|
34
|
+
artifactPath: existing[0]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const CANDIDATE_FILES = [
|
|
4
|
+
'src/services/openspec/openspec-archive-service.ts',
|
|
5
|
+
'src/services/openspec/coverage-evidence-reader.ts',
|
|
6
|
+
'src/cli/commands/openspec-commands.ts'
|
|
7
|
+
];
|
|
8
|
+
const REQUIRED_FRAGMENTS = ['Capability Mapping', 'coverage'];
|
|
9
|
+
export async function runJ15Contract(ctx) {
|
|
10
|
+
const existing = CANDIDATE_FILES.filter((f) => existsSync(join(ctx.projectRoot, f)));
|
|
11
|
+
if (existing.length === 0) {
|
|
12
|
+
return {
|
|
13
|
+
journeyId: 'J15',
|
|
14
|
+
contract: 'spec-coverage',
|
|
15
|
+
status: 'fail',
|
|
16
|
+
diff: { before: 'an openspec archive/coverage service exists', after: 'no candidate found', reason: 'J15#1 broken: no openspec/coverage surface is present' },
|
|
17
|
+
artifactPath: 'src/services/openspec/'
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// At least one file references BOTH "Capability Mapping" AND "coverage".
|
|
21
|
+
const fullCoverage = existing.some((f) => {
|
|
22
|
+
const src = readFileSync(join(ctx.projectRoot, f), 'utf8');
|
|
23
|
+
return REQUIRED_FRAGMENTS.every((frag) => src.includes(frag));
|
|
24
|
+
});
|
|
25
|
+
if (!fullCoverage) {
|
|
26
|
+
return {
|
|
27
|
+
journeyId: 'J15',
|
|
28
|
+
contract: 'spec-coverage',
|
|
29
|
+
status: 'fail',
|
|
30
|
+
diff: { before: 'openspec services reference both "Capability Mapping" and "coverage"', after: 'no such service', reason: 'J15#1 broken: openspec archive gate is missing the Capability Mapping + coverage cross-check' },
|
|
31
|
+
artifactPath: existing[0]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
journeyId: 'J15',
|
|
36
|
+
contract: 'spec-coverage',
|
|
37
|
+
status: 'pass',
|
|
38
|
+
artifactPath: existing[0]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GuardContract, GuardContext, GuardRunResult } from './types.js';
|
|
2
|
+
export declare function runGuard(contract: GuardContract, ctx: GuardContext): Promise<GuardRunResult>;
|
|
3
|
+
export declare function runAllGuards(contracts: ReadonlyArray<GuardContract>, ctx: GuardContext): Promise<{
|
|
4
|
+
readonly pass: number;
|
|
5
|
+
readonly fail: number;
|
|
6
|
+
readonly skipped: number;
|
|
7
|
+
readonly total: number;
|
|
8
|
+
readonly results: ReadonlyArray<GuardRunResult>;
|
|
9
|
+
}>;
|