peaks-loop 4.0.49 → 4.0.51
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 +38 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/dist/cli/commands/audit-commands.js +1 -0
- package/dist/cli/commands/baseline-commands.js +163 -25
- package/dist/cli/commands/core/skill-command.js +53 -4
- package/dist/cli/commands/core/standards-command.d.ts +24 -0
- package/dist/cli/commands/core/standards-command.js +74 -0
- package/dist/cli/commands/hooks-commands.js +55 -38
- package/dist/cli/commands/share-commands.js +113 -20
- package/dist/cli/commands/web-commands.js +8 -1
- package/dist/cli/commands/workflow-lifecycle-commands.d.ts +6 -0
- package/dist/cli/commands/workflow-lifecycle-commands.js +64 -3
- package/dist/services/adapter/adapter.d.ts +30 -0
- package/dist/services/adapter/auto-adapter.d.ts +13 -0
- package/dist/services/adapter/claude-adapter.js +12 -0
- package/dist/services/adapter/codex-adapter.d.ts +12 -0
- package/dist/services/adapter/codex-adapter.js +12 -0
- package/dist/services/adapter/copilot-adapter.d.ts +12 -0
- package/dist/services/adapter/copilot-adapter.js +12 -0
- package/dist/services/audit/backing-detector.d.ts +25 -7
- package/dist/services/audit/backing-detector.js +33 -17
- package/dist/services/audit/enforcer-liveness.d.ts +12 -0
- package/dist/services/audit/enforcer-liveness.js +100 -0
- package/dist/services/audit/enforcers/lint-catalog-governance.d.ts +23 -11
- package/dist/services/audit/enforcers/lint-catalog-governance.js +10 -14
- package/dist/services/audit/enforcers/lint-rd-handoff-coverage.d.ts +5 -15
- package/dist/services/audit/enforcers/lint-rd-handoff-coverage.js +94 -25
- package/dist/services/audit/enforcers/lint-style.d.ts +9 -1
- package/dist/services/audit/enforcers/lint-style.js +38 -2
- package/dist/services/audit/prose-ratio-calculator.d.ts +28 -17
- package/dist/services/audit/prose-ratio-calculator.js +25 -18
- package/dist/services/audit/red-line-catalog-p2-a.js +1 -1
- package/dist/services/audit/red-lines-service.js +51 -7
- package/dist/services/capability-audit-service/independent-checker.d.ts +15 -0
- package/dist/services/capability-audit-service/independent-checker.js +140 -0
- package/dist/services/capability-audit-service/index.d.ts +3 -1
- package/dist/services/capability-audit-service/index.js +1 -0
- package/dist/services/capability-audit-service/runner.d.ts +17 -13
- package/dist/services/capability-audit-service/runner.js +76 -15
- package/dist/services/capability-audit-service/types.d.ts +48 -0
- package/dist/services/capability-guard-runner/contracts/J01.js +21 -22
- package/dist/services/capability-guard-runner/contracts/J02.d.ts +1 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +114 -28
- package/dist/services/capability-guard-runner/contracts/J03.d.ts +13 -0
- package/dist/services/capability-guard-runner/contracts/J03.js +72 -21
- package/dist/services/capability-guard-runner/contracts/J04.d.ts +6 -0
- package/dist/services/capability-guard-runner/contracts/J04.js +65 -32
- package/dist/services/capability-guard-runner/contracts/J05.js +118 -16
- package/dist/services/capability-guard-runner/contracts/J06.d.ts +14 -0
- package/dist/services/capability-guard-runner/contracts/J06.js +57 -39
- package/dist/services/capability-guard-runner/contracts/J07.d.ts +9 -0
- package/dist/services/capability-guard-runner/contracts/J07.js +76 -47
- package/dist/services/capability-guard-runner/contracts/J08.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J08.js +66 -39
- package/dist/services/capability-guard-runner/contracts/J09.d.ts +13 -0
- package/dist/services/capability-guard-runner/contracts/J09.js +95 -39
- package/dist/services/capability-guard-runner/contracts/J10.d.ts +12 -0
- package/dist/services/capability-guard-runner/contracts/J10.js +69 -35
- package/dist/services/capability-guard-runner/contracts/J11.d.ts +8 -0
- package/dist/services/capability-guard-runner/contracts/J11.js +73 -33
- package/dist/services/capability-guard-runner/contracts/J12.d.ts +12 -0
- package/dist/services/capability-guard-runner/contracts/J12.js +66 -30
- package/dist/services/capability-guard-runner/contracts/J13.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J13.js +62 -40
- package/dist/services/capability-guard-runner/contracts/J14.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J14.js +60 -31
- package/dist/services/capability-guard-runner/contracts/J15.d.ts +11 -0
- package/dist/services/capability-guard-runner/contracts/J15.js +70 -35
- package/dist/services/capability-guard-runner/contracts/_shared.d.ts +24 -0
- package/dist/services/capability-guard-runner/contracts/_shared.js +67 -0
- package/dist/services/capability-guard-runner/registry.d.ts +5 -0
- package/dist/services/capability-guard-runner/registry.js +140 -0
- package/dist/services/capability-guard-runner/runner.d.ts +26 -0
- package/dist/services/capability-guard-runner/runner.js +63 -6
- package/dist/services/code/auto-compact-modes.d.ts +13 -2
- package/dist/services/code/auto-compact-modes.js +20 -4
- package/dist/services/code/post-compact-detector.js +20 -11
- package/dist/services/code/step-08-gate.js +21 -6
- package/dist/services/config/config-safety.js +11 -9
- package/dist/services/dispatch/sub-agent-dispatcher.d.ts +11 -30
- package/dist/services/dispatch/sub-agent-dispatcher.js +5 -48
- package/dist/services/final-review/pre-post-diff.js +10 -2
- package/dist/services/ide/adapters/claude-code-adapter.js +0 -1
- package/dist/services/ide/adapters/codex-adapter.js +1 -2
- package/dist/services/ide/adapters/cursor-adapter.js +1 -2
- package/dist/services/ide/adapters/hermes-adapter.js +1 -2
- package/dist/services/ide/adapters/openclaw-adapter.js +1 -2
- package/dist/services/ide/adapters/qoder-adapter.js +1 -2
- package/dist/services/ide/adapters/tongyi-lingma-adapter.js +1 -2
- package/dist/services/ide/adapters/trae-adapter.js +1 -2
- package/dist/services/ide/adapters/zcode-adapter.js +0 -1
- package/dist/services/ide/ide-types.d.ts +0 -2
- package/dist/services/observability/observability-service.d.ts +1 -1
- package/dist/services/scan/api-diff-types.js +20 -2
- package/dist/services/security/safe-settings-path.js +19 -1
- package/dist/services/skill/skill-search-service.d.ts +3 -3
- package/dist/services/standards/loop-engineering-lint.d.ts +1 -1
- package/dist/services/standards/loop-engineering-lint.js +6 -0
- package/dist/services/web/daemon-registry.js +27 -2
- package/dist/services/workspace/claude-settings-template.d.ts +53 -37
- package/dist/services/workspace/claude-settings-template.js +105 -83
- package/dist/services/workspace/generated-artifacts-stamp.d.ts +119 -0
- package/dist/services/workspace/generated-artifacts-stamp.js +167 -0
- package/dist/services/workspace/workspace-claude-settings-materializer.d.ts +8 -0
- package/dist/services/workspace/workspace-claude-settings-materializer.js +38 -3
- package/dist/services/workspace/workspace-service.js +11 -1
- package/dist/shared/fs-utils.d.ts +26 -0
- package/dist/shared/fs-utils.js +35 -0
- package/package.json +9 -7
- package/scripts/copy-templates.mjs +0 -12
- package/scripts/install-skills.mjs +154 -53
- package/skills/bee/peaks-perf-audit/SKILL.md +2 -2
- package/skills/bee/peaks-perf-audit/references/audit-protocol.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +3 -3
- package/skills/bee/peaks-prd/references/prd-for-multi-pass.md +1 -1
- package/skills/bee/peaks-prd/references/workflow.md +1 -1
- package/skills/bee/peaks-qa/SKILL.md +6 -7
- package/skills/bee/peaks-qa/references/external-capability-guidance.md +1 -1
- package/skills/bee/peaks-qa/references/qa-fanout-contract.md +1 -1
- package/skills/bee/peaks-qa/references/reading-handoff-frontmatter.md +2 -2
- package/skills/bee/peaks-rd/SKILL.md +2 -3
- package/skills/bee/peaks-rd/references/code-reviewer-4dim-hint.md +1 -1
- package/skills/bee/peaks-rd/references/external-references.md +1 -1
- package/skills/bee/peaks-rd/references/mandatory-perf-baseline.md +1 -1
- package/skills/bee/peaks-rd/references/ocr-multilang-1.8.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +2 -2
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +11 -8
- package/skills/bee/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +7 -7
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +1 -1
- package/skills/bee/peaks-rd/references/reading-v2-slice-results.md +1 -1
- package/skills/bee/peaks-rd/references/v2-12-fanout-collapse.md +7 -5
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +3 -3
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +3 -3
- package/skills/bee/peaks-security-audit/references/audit-protocol.md +1 -1
- package/skills/bee/peaks-txt/references/context-capsule.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-audit/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +20 -18
- package/skills/peaks-code/references/context-governance.md +1 -1
- package/skills/peaks-code/references/dag-orchestrator.md +3 -4
- package/skills/peaks-code/references/external-references.md +1 -1
- package/skills/peaks-code/references/external-skill-invocation.md +2 -2
- package/skills/peaks-code/references/fanout-mandatory.md +3 -3
- package/skills/peaks-code/references/frontend-only-mode.md +2 -2
- package/skills/peaks-code/references/gstack-integration.md +1 -1
- package/skills/peaks-code/references/micro-cycle.md +1 -1
- package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
- package/skills/peaks-code/references/project-scan-checklist.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +1 -1
- package/skills/peaks-code/references/runbook.md +6 -3
- package/skills/peaks-code/references/session-overload-signal-index.md +6 -4
- package/skills/peaks-code/references/startup-sequence.md +17 -17
- package/skills/peaks-code/references/step-0-8-gate.md +1 -1
- package/skills/peaks-code/references/step-11-memory-sediment.md +2 -2
- package/skills/peaks-code/references/sub-agent-dispatch.md +26 -25
- package/skills/peaks-code/references/swarm-dispatch-contract.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +3 -3
- package/skills/peaks-code/references/worktree-governance.md +1 -1
- package/skills/peaks-final-review/SKILL.md +3 -3
- package/skills/peaks-ide/references/audit-log-helper.md +5 -4
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +4 -4
- package/skills/peaks-slice-decompose/references/cross-pass-edge-interpretation.md +1 -1
- package/skills/peaks-slice-decompose/references/granularity-decision.md +1 -1
- package/skills/peaks-slice-decompose/references/v2-schema.md +2 -2
- package/skills/peaks-solo/SKILL.md +1 -2
- package/dist/cli/commands/context-builder-commands.d.ts +0 -11
- package/dist/cli/commands/context-builder-commands.js +0 -85
- package/dist/services/hooks/write-gate.js +0 -111
- package/skills/bee/peaks-prd/references/command-migration.md +0 -3
- package/skills/bee/peaks-qa/references/command-migration.md +0 -3
- package/skills/bee/peaks-rd/references/command-migration.md +0 -3
- package/skills/bee/peaks-sc/references/command-migration.md +0 -3
- package/skills/bee/peaks-txt/references/command-migration.md +0 -3
- package/skills/bee/peaks-ui/references/command-migration.md +0 -3
- package/skills/peaks-code/references/command-migration.md +0 -3
|
@@ -1,40 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { applyHookInstall, planHookInstall } from '../../skills/hooks-settings-service.js';
|
|
5
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
6
|
+
import { normalizePath } from '../../../shared/path-utils.js';
|
|
7
|
+
function countOccurrences(haystack, needle) {
|
|
8
|
+
if (needle.length === 0)
|
|
9
|
+
return 0;
|
|
10
|
+
let count = 0;
|
|
11
|
+
let from = 0;
|
|
12
|
+
for (;;) {
|
|
13
|
+
const at = haystack.indexOf(needle, from);
|
|
14
|
+
if (at < 0)
|
|
15
|
+
return count;
|
|
16
|
+
count += 1;
|
|
17
|
+
from = at + needle.length;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Behavioural probe of "a plan is produced before anything is written, so the
|
|
22
|
+
* diff is reviewable".
|
|
23
|
+
*
|
|
24
|
+
* The previous version listed four hook/IDE files and passed if any of them
|
|
25
|
+
* contained the word "hook", "install", "ide" or "adapter" — `hooks-commands.ts`
|
|
26
|
+
* satisfies that from its own filename.
|
|
27
|
+
*
|
|
28
|
+
* Here `planHookInstall` runs against a fresh tmp project and the settings file
|
|
29
|
+
* must NOT appear; `applyHookInstall` then writes it, and a second apply must
|
|
30
|
+
* be a no-op that does not duplicate the entry.
|
|
31
|
+
*/
|
|
10
32
|
export async function runJ10Contract(ctx) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
33
|
+
const row = requireBaselineRow(ctx);
|
|
34
|
+
const missing = missingSourceFiles(ctx, row);
|
|
35
|
+
const root = mkdtempSync(join(tmpdir(), 'cbl-J10-'));
|
|
36
|
+
try {
|
|
37
|
+
const plan = planHookInstall('project', root);
|
|
38
|
+
const settingsPath = plan.settingsPath;
|
|
39
|
+
const plannedInsideProject = normalizePath(settingsPath).startsWith(normalizePath(root));
|
|
40
|
+
const planIsReadOnly = !existsSync(settingsPath);
|
|
41
|
+
const freshProjectNotInstalled = plan.exists === false && plan.alreadyInstalled === false;
|
|
42
|
+
// Each managed hook entry is identified by its own sentinel; every one of
|
|
43
|
+
// them must land exactly once and stay at one across a re-apply.
|
|
44
|
+
const sentinels = plan.entryTargets.filter((t) => t.settingsPath === settingsPath).map((t) => t.sentinel);
|
|
45
|
+
const countsNow = () => existsSync(settingsPath) ? sentinels.map((s) => countOccurrences(readFileSync(settingsPath, 'utf8'), s)) : [];
|
|
46
|
+
const first = applyHookInstall('project', root);
|
|
47
|
+
const fileWritten = existsSync(settingsPath);
|
|
48
|
+
const installedCounts = countsNow();
|
|
49
|
+
const sentinelCount = installedCounts.length > 0 ? installedCounts[0] : -1;
|
|
50
|
+
const second = applyHookInstall('project', root);
|
|
51
|
+
const afterSecondApply = countsNow().length > 0 ? countsNow()[0] : -1;
|
|
52
|
+
const planAfterApply = planHookInstall('project', root);
|
|
53
|
+
const result = combineProbes([
|
|
54
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
55
|
+
probe(plannedInsideProject, `the plan targets a settings file inside the project (${settingsPath})`),
|
|
56
|
+
probe(freshProjectNotInstalled, 'a fresh project reports exists=false / alreadyInstalled=false'),
|
|
57
|
+
probe(planIsReadOnly, 'planning does not create the settings file — the diff is reviewable first'),
|
|
58
|
+
probe(first.applied === true, 'apply reports applied=true'),
|
|
59
|
+
probe(fileWritten, 'apply writes the settings file'),
|
|
60
|
+
probe(sentinels.length > 0, `the plan enumerates managed hook entries (${String(sentinels.length)})`),
|
|
61
|
+
probe(installedCounts.length > 0 && installedCounts.every((c) => c === 1), `apply installs every managed entry exactly once (counts=${installedCounts.join(',') || 'none'})`),
|
|
62
|
+
probe(second.applied === false, `a second apply is a no-op (applied=${String(second.applied)}, first sentinel count=${String(sentinelCount)})`),
|
|
63
|
+
probe(afterSecondApply === 1, `a duplicate apply does not duplicate the entry (count=${String(afterSecondApply)})`),
|
|
64
|
+
probe(planAfterApply.alreadyInstalled === true, 'the plan reports alreadyInstalled=true after a successful apply')
|
|
65
|
+
]);
|
|
66
|
+
const artifact = row.sourceFiles[0] ?? 'src/services/skills/hooks-settings-service.ts';
|
|
67
|
+
if (result.ok)
|
|
68
|
+
return pass(ctx, artifact);
|
|
69
|
+
return fail(ctx, artifact, 'planHookInstall() produces a reviewable plan before applyHookInstall() writes, and re-applying is a no-op', result.detail, 'J10 invariant broken: hook install is no longer plan-then-apply, or is not idempotent');
|
|
20
70
|
}
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
};
|
|
71
|
+
finally {
|
|
72
|
+
rmSync(root, { recursive: true, force: true });
|
|
33
73
|
}
|
|
34
|
-
return {
|
|
35
|
-
journeyId: 'J10',
|
|
36
|
-
contract: 'hook-assertion',
|
|
37
|
-
status: 'pass',
|
|
38
|
-
artifactPath: existing[0]
|
|
39
|
-
};
|
|
40
74
|
}
|
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of "doctor checks live under a code-driven fixed-registry
|
|
4
|
+
* tree, and each openspec change is generated from a real doctor finding".
|
|
5
|
+
*
|
|
6
|
+
* The previous version listed doctor/audit/openspec files and passed if any of
|
|
7
|
+
* them mentioned "doctor", "audit", "openspec" or "health" — `doctor-service.ts`
|
|
8
|
+
* satisfies that from its own filename.
|
|
9
|
+
*/
|
|
2
10
|
export declare function runJ11Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,39 +1,79 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { PLUGINS } from '../../doctor/doctor-service/plugin-registry.js';
|
|
5
|
+
import { proposeFromDoctor } from '../../openspec/openspec-propose-from-doctor-service.js';
|
|
6
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
7
|
+
const CHECKS_DIR = ['src', 'services', 'doctor', 'doctor-service', 'checks'];
|
|
8
|
+
const LEGACY_SHIM = ['src', 'services', 'doctor', 'doctor-service.ts'];
|
|
9
|
+
const FROZEN_CLOCK = () => '2020-01-01T00:00:00.000Z';
|
|
10
|
+
/** Strip block + line comments so only executable lines remain. */
|
|
11
|
+
function executableLines(source) {
|
|
12
|
+
return source
|
|
13
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
14
|
+
.split('\n')
|
|
15
|
+
.map((line) => line.replace(/\/\/.*$/, '').trim())
|
|
16
|
+
.filter((line) => line.length > 0);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Constructs a re-export-only shim must not contain. `export * from` /
|
|
20
|
+
* `export { a, b } from` / `export type { ... }` are the only allowed forms;
|
|
21
|
+
* anything below means logic moved back into the legacy path.
|
|
22
|
+
*/
|
|
23
|
+
const FORBIDDEN_SHIM_PATTERNS = [
|
|
24
|
+
/\bfunction\b/,
|
|
25
|
+
/\breturn\b/,
|
|
26
|
+
/\bif\s*\(/,
|
|
27
|
+
/\bfor\s*\(/,
|
|
28
|
+
/\bwhile\s*\(/,
|
|
29
|
+
/\bconst\s+\w+\s*=/,
|
|
30
|
+
/=>\s*\{/
|
|
8
31
|
];
|
|
32
|
+
/**
|
|
33
|
+
* Behavioural probe of "doctor checks live under a code-driven fixed-registry
|
|
34
|
+
* tree, and each openspec change is generated from a real doctor finding".
|
|
35
|
+
*
|
|
36
|
+
* The previous version listed doctor/audit/openspec files and passed if any of
|
|
37
|
+
* them mentioned "doctor", "audit", "openspec" or "health" — `doctor-service.ts`
|
|
38
|
+
* satisfies that from its own filename.
|
|
39
|
+
*/
|
|
9
40
|
export async function runJ11Contract(ctx) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
const row = requireBaselineRow(ctx);
|
|
42
|
+
const missing = missingSourceFiles(ctx, row);
|
|
43
|
+
const checksDirAbs = join(ctx.projectRoot, ...CHECKS_DIR);
|
|
44
|
+
const checkFiles = existsSync(checksDirAbs) ? readdirSync(checksDirAbs).filter((f) => f.endsWith('.ts')) : [];
|
|
45
|
+
const pluginIds = PLUGINS.map((p) => p.name);
|
|
46
|
+
const uniquePluginIds = new Set(pluginIds);
|
|
47
|
+
const shimAbs = join(ctx.projectRoot, ...LEGACY_SHIM);
|
|
48
|
+
const shimSource = existsSync(shimAbs) ? readFileSync(shimAbs, 'utf8') : '';
|
|
49
|
+
const shimLines = executableLines(shimSource);
|
|
50
|
+
const shimLogic = shimLines.filter((line) => FORBIDDEN_SHIM_PATTERNS.some((re) => re.test(line)));
|
|
51
|
+
const shimReexports = /export\s+(\*|\{[\s\S]*?\}|type\s*\{[\s\S]*?\})\s*from\s*'/.test(shimSource);
|
|
52
|
+
const shimIsReexportOnly = shimLines.length > 0 && shimLogic.length === 0 && shimReexports;
|
|
53
|
+
const root = mkdtempSync(join(tmpdir(), 'cbl-J11-'));
|
|
54
|
+
try {
|
|
55
|
+
const finding = { id: 'L3:l3-memory-health', rule: 'r', detail: 'd', severity: 'fail' };
|
|
56
|
+
const first = proposeFromDoctor({ projectRoot: root, finding, clock: FROZEN_CLOCK });
|
|
57
|
+
const second = proposeFromDoctor({ projectRoot: root, finding, clock: FROZEN_CLOCK });
|
|
58
|
+
const proposalMentionsFinding = existsSync(first.proposalPath)
|
|
59
|
+
? readFileSync(first.proposalPath, 'utf8').includes(finding.id)
|
|
60
|
+
: false;
|
|
61
|
+
const result = combineProbes([
|
|
62
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
63
|
+
probe(checkFiles.length > 0 && PLUGINS.length === checkFiles.length, `the plugin registry enumerates every check module (${String(PLUGINS.length)} plugins / ${String(checkFiles.length)} checks)`),
|
|
64
|
+
probe(uniquePluginIds.size === PLUGINS.length, `plugin ids are unique (${String(uniquePluginIds.size)}/${String(PLUGINS.length)})`),
|
|
65
|
+
probe(shimIsReexportOnly, `the legacy doctor-service.ts shim holds no logic (${String(shimLines.length)} executable lines, ${String(shimLogic.length)} with logic${shimLogic.length > 0 ? `: ${shimLogic.slice(0, 3).join(' | ')}` : ''})`),
|
|
66
|
+
probe(first.changeId === '2020-01-01-fix-l3-l3-memory-health', `the change id is derived from the doctor finding (${first.changeId})`),
|
|
67
|
+
probe(first.created === true, 'the first proposal is created'),
|
|
68
|
+
probe(proposalMentionsFinding, 'the proposal names the originating finding id'),
|
|
69
|
+
probe(second.created === false, 'regenerating from the same finding is idempotent (created=false)')
|
|
70
|
+
]);
|
|
71
|
+
const artifact = row.sourceFiles[0] ?? 'src/services/doctor/doctor-service/index.ts';
|
|
72
|
+
if (result.ok)
|
|
73
|
+
return pass(ctx, artifact);
|
|
74
|
+
return fail(ctx, artifact, 'doctor checks are a code-driven fixed-registry tree and openspec changes come from real findings', result.detail, 'J11 invariant broken: the doctor check registry or the finding-to-change conversion no longer holds');
|
|
19
75
|
}
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
};
|
|
76
|
+
finally {
|
|
77
|
+
rmSync(root, { recursive: true, force: true });
|
|
32
78
|
}
|
|
33
|
-
return {
|
|
34
|
-
journeyId: 'J11',
|
|
35
|
-
contract: 'cli-output-golden',
|
|
36
|
-
status: 'pass',
|
|
37
|
-
artifactPath: existing[0]
|
|
38
|
-
};
|
|
39
79
|
}
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the lease lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* The previous version passed if one of four worktree/dispatch files mentioned
|
|
6
|
+
* both "lease" and "release" — `worktree-lease.ts` satisfies that from its own
|
|
7
|
+
* filename.
|
|
8
|
+
*
|
|
9
|
+
* Here the real pure module is driven: a fresh lease must be active, releasing
|
|
10
|
+
* twice must be a no-op, a duplicate consumption must not append, an in-flight
|
|
11
|
+
* lease must never be gc-eligible, and a persisted lease must survive a
|
|
12
|
+
* serialize/deserialize round trip.
|
|
13
|
+
*/
|
|
2
14
|
export declare function runJ12Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,34 +1,70 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { deserializeLease, finalizeLease, isLeaseActive, isLeaseGcEligible, leaseFilePath, leaseStoreDir, listLeasesSync, markReleased, recordConsumption, renewLease, serializeLease, ttlForRole } from '../../worktree/worktree-lease.js';
|
|
5
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
6
|
+
const NOW = 1_700_000_000_000;
|
|
7
|
+
const DRAFT = {
|
|
8
|
+
leaseId: '0123456789abcdef',
|
|
9
|
+
rid: '2026-09-15-s1-gate-contracts-honest',
|
|
10
|
+
role: 'rd',
|
|
11
|
+
path: 'C:/tmp/worktrees/0123456789abcdef',
|
|
12
|
+
branch: 'peaks/guard-J12',
|
|
13
|
+
createdAt: NOW - 1000,
|
|
14
|
+
expiresAt: NOW + 60_000,
|
|
15
|
+
purpose: 'J12 guard probe'
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Behavioural probe of the lease lifecycle.
|
|
19
|
+
*
|
|
20
|
+
* The previous version passed if one of four worktree/dispatch files mentioned
|
|
21
|
+
* both "lease" and "release" — `worktree-lease.ts` satisfies that from its own
|
|
22
|
+
* filename.
|
|
23
|
+
*
|
|
24
|
+
* Here the real pure module is driven: a fresh lease must be active, releasing
|
|
25
|
+
* twice must be a no-op, a duplicate consumption must not append, an in-flight
|
|
26
|
+
* lease must never be gc-eligible, and a persisted lease must survive a
|
|
27
|
+
* serialize/deserialize round trip.
|
|
28
|
+
*/
|
|
10
29
|
export async function runJ12Contract(ctx) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
const row = requireBaselineRow(ctx);
|
|
31
|
+
const missing = missingSourceFiles(ctx, row);
|
|
32
|
+
const lease = finalizeLease(DRAFT);
|
|
33
|
+
const onceReleased = markReleased(lease);
|
|
34
|
+
const twiceReleased = markReleased(onceReleased);
|
|
35
|
+
const consumed = recordConsumption(lease, 'batch-1');
|
|
36
|
+
const consumedTwice = recordConsumption(consumed, 'batch-1');
|
|
37
|
+
const renewed = renewLease(onceReleased, NOW + 120_000);
|
|
38
|
+
const root = mkdtempSync(join(tmpdir(), 'cbl-J12-'));
|
|
39
|
+
try {
|
|
40
|
+
const storeDir = leaseStoreDir(root);
|
|
41
|
+
const missingStore = listLeasesSync(storeDir, { readdir: readdirSync, readFile: (p) => readFileSync(p, 'utf8'), existsSync });
|
|
42
|
+
mkdirSync(storeDir, { recursive: true });
|
|
43
|
+
writeFileSync(leaseFilePath(root, lease.leaseId), serializeLease(lease));
|
|
44
|
+
const listed = listLeasesSync(storeDir, { readdir: readdirSync, readFile: (p) => readFileSync(p, 'utf8'), existsSync });
|
|
45
|
+
const result = combineProbes([
|
|
46
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
47
|
+
probe(lease.status === 'active' && lease.consumedBySubAgents.length === 0, 'a fresh lease is active with no consumers'),
|
|
48
|
+
probe(leaseStoreDir(root).replace(/\\/g, '/').endsWith('/worktree-leases'), `leases live under the session worktree-leases dir (${leaseStoreDir(root)})`),
|
|
49
|
+
probe(missingStore.kind === 'store-missing', 'an absent lease store is reported, not invented'),
|
|
50
|
+
probe(listed.kind === 'ok' && listed.leases.length === 1, `a persisted lease is listed back (${listed.kind === 'ok' ? String(listed.leases.length) : listed.kind})`),
|
|
51
|
+
probe(JSON.stringify(deserializeLease(serializeLease(lease))) === JSON.stringify(lease), 'serialize/deserialize round-trips the lease'),
|
|
52
|
+
probe(onceReleased.status === 'released', 'release moves the lease to released'),
|
|
53
|
+
probe(JSON.stringify(twiceReleased) === JSON.stringify(onceReleased), 'a duplicate release is a no-op'),
|
|
54
|
+
probe(JSON.stringify(consumedTwice) === JSON.stringify(consumed) && consumed.consumedBySubAgents.length === 1, 'a duplicate consumption is not appended twice'),
|
|
55
|
+
probe(renewed.status === 'active' && renewed.expiresAt === NOW + 120_000, 'renew extends expiry and returns the lease to active'),
|
|
56
|
+
probe(isLeaseActive(lease, NOW), 'an unexpired active lease is in flight'),
|
|
57
|
+
probe(!isLeaseGcEligible(lease, NOW), 'gc is NOT eligible for an in-flight lease'),
|
|
58
|
+
probe(isLeaseGcEligible(onceReleased, NOW), 'a released lease is gc-eligible'),
|
|
59
|
+
probe(isLeaseGcEligible(finalizeLease({ ...DRAFT, expiresAt: NOW - 1 }), NOW), 'an expired lease is gc-eligible'),
|
|
60
|
+
probe(ttlForRole('rd') > 0 && ttlForRole('no-such-role') === ttlForRole('rd'), 'unknown roles fall back to the rd TTL')
|
|
61
|
+
]);
|
|
62
|
+
const artifact = row.sourceFiles[0] ?? 'src/services/worktree/worktree-lease.ts';
|
|
63
|
+
if (result.ok)
|
|
64
|
+
return pass(ctx, artifact);
|
|
65
|
+
return fail(ctx, artifact, 'the lease lifecycle is complete: release is idempotent and gc never touches an in-flight lease', result.detail, 'J12 invariant broken: the lease release lifecycle or its gc guard no longer holds');
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
rmSync(root, { recursive: true, force: true });
|
|
20
69
|
}
|
|
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
70
|
}
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the content-pipeline boundary.
|
|
4
|
+
*
|
|
5
|
+
* The previous version read the skill plus three unrelated files and passed if
|
|
6
|
+
* three of the five words draft/edit/tone/publish/archive appeared anywhere —
|
|
7
|
+
* and it counted "tone" (a GATE in the skill) as a STAGE.
|
|
8
|
+
*
|
|
9
|
+
* Here the skill's declared stages are anchored on the bees that implement
|
|
10
|
+
* them, and every file under the skill directory is scanned for a specifier
|
|
11
|
+
* that would reach into peaks-code internals.
|
|
12
|
+
*/
|
|
2
13
|
export declare function runJ13Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,45 +1,67 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
4
|
+
const SKILL_DIR = ['skills', 'peaks-content'];
|
|
5
|
+
const SKILL_MD = [...SKILL_DIR, 'SKILL.md'];
|
|
6
|
+
/**
|
|
7
|
+
* The content stages the skill must still declare, each anchored on the bee
|
|
8
|
+
* that implements it. "draft" appearing anywhere in the file is not evidence;
|
|
9
|
+
* `bee-content-draft` disappearing means the stage is gone.
|
|
10
|
+
*/
|
|
11
|
+
const STAGES = ['draft', 'edit', 'publish', 'archive'];
|
|
12
|
+
/** Specifiers that would mean the skill reached into peaks-code domain logic. */
|
|
13
|
+
const FORBIDDEN_SPECIFIER = /(^|[./\\])peaks-code([/\\]|$)|(^|\/)src\/(services|cli)\//;
|
|
14
|
+
function walk(dir, out = []) {
|
|
15
|
+
if (!existsSync(dir))
|
|
16
|
+
return out;
|
|
17
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
18
|
+
const abs = join(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory())
|
|
20
|
+
walk(abs, out);
|
|
21
|
+
else
|
|
22
|
+
out.push(abs);
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Behavioural probe of the content-pipeline boundary.
|
|
28
|
+
*
|
|
29
|
+
* The previous version read the skill plus three unrelated files and passed if
|
|
30
|
+
* three of the five words draft/edit/tone/publish/archive appeared anywhere —
|
|
31
|
+
* and it counted "tone" (a GATE in the skill) as a STAGE.
|
|
32
|
+
*
|
|
33
|
+
* Here the skill's declared stages are anchored on the bees that implement
|
|
34
|
+
* them, and every file under the skill directory is scanned for a specifier
|
|
35
|
+
* that would reach into peaks-code internals.
|
|
36
|
+
*/
|
|
37
|
+
export async function runJ13Contract(ctx) {
|
|
38
|
+
const row = requireBaselineRow(ctx);
|
|
39
|
+
const missing = missingSourceFiles(ctx, row);
|
|
40
|
+
const skillAbs = join(ctx.projectRoot, ...SKILL_MD);
|
|
41
|
+
const body = existsSync(skillAbs) ? readFileSync(skillAbs, 'utf8') : '';
|
|
42
|
+
const absentStages = STAGES.filter((stage) => !body.includes(`bee-content-${stage}`));
|
|
43
|
+
const toneGatePresent = body.includes('Tone gate');
|
|
44
|
+
const files = walk(join(ctx.projectRoot, ...SKILL_DIR));
|
|
45
|
+
const offenders = [];
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const text = readFileSync(file, 'utf8');
|
|
48
|
+
for (const line of text.split('\n')) {
|
|
49
|
+
const spec = /(?:from|import|require\()\s*['"]([^'"]+)['"]/.exec(line);
|
|
50
|
+
if (spec !== null && FORBIDDEN_SPECIFIER.test(spec[1])) {
|
|
51
|
+
offenders.push(`${file.replace(/\\/g, '/')} → ${spec[1]}`);
|
|
52
|
+
}
|
|
28
53
|
}
|
|
29
54
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
status: 'pass',
|
|
43
|
-
artifactPath: existing[0]
|
|
44
|
-
};
|
|
55
|
+
const result = combineProbes([
|
|
56
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
57
|
+
probe(body.length > 0, 'the peaks-content skill file is readable'),
|
|
58
|
+
probe(absentStages.length === 0, `every content stage is anchored on its bee (missing: ${absentStages.join(',') || 'none'})`),
|
|
59
|
+
probe(toneGatePresent, 'the tone gate is still declared between draft and edit'),
|
|
60
|
+
probe(files.length > 0, `the skill directory is non-empty (${String(files.length)} files)`),
|
|
61
|
+
probe(offenders.length === 0, `no skill file imports peaks-code internals (${offenders.join('; ') || 'none'})`)
|
|
62
|
+
]);
|
|
63
|
+
const artifact = row.sourceFiles[0] ?? 'skills/peaks-content/SKILL.md';
|
|
64
|
+
if (result.ok)
|
|
65
|
+
return pass(ctx, artifact);
|
|
66
|
+
return fail(ctx, artifact, 'peaks-content declares its four stages and imports no peaks-code internals', result.detail, 'J13 invariant broken: the content pipeline stages or the peaks-code boundary changed');
|
|
45
67
|
}
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the RL-3 AI-modified declaration rule.
|
|
4
|
+
*
|
|
5
|
+
* The previous version read the skill plus two markdown files and passed if at
|
|
6
|
+
* least two of the words triage/classify/fix/commit appeared — words that
|
|
7
|
+
* appear throughout the prose regardless of whether any rule survived.
|
|
8
|
+
*
|
|
9
|
+
* Here the rule is anchored on its heading, its literal commit-body markers,
|
|
10
|
+
* the forbidden-trailer list and the survey-banned globs. Deleting the rule
|
|
11
|
+
* section reddens the journey.
|
|
12
|
+
*/
|
|
2
13
|
export declare function runJ14Contract(ctx: GuardContext): Promise<GuardRunResult>;
|
|
@@ -1,36 +1,65 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { combineProbes, fail, missingSourceFiles, pass, probe, requireBaselineRow } from './_shared.js';
|
|
4
|
+
const SKILL_MD = ['skills', 'peaks-issue-fix-orchestrator', 'SKILL.md'];
|
|
5
|
+
/**
|
|
6
|
+
* The RL-3 declaration's literal lines. Anchored on the exact marker text the
|
|
7
|
+
* self-check greps for — "AI-modified" appearing once in the frontmatter
|
|
8
|
+
* description is not evidence that the rule survived.
|
|
9
|
+
*/
|
|
10
|
+
const REQUIRED_MARKERS = [
|
|
11
|
+
'AI-modified: yes',
|
|
12
|
+
'Repository:',
|
|
13
|
+
'Modifying tool: peaks-loop / peaks-issue-fix-orchestrator',
|
|
14
|
+
"grep -E 'Repository:|AI-modified:'",
|
|
15
|
+
'Forbidden trailers:'
|
|
7
16
|
];
|
|
8
|
-
|
|
17
|
+
/** Every trailer the repo's own red rule bans (CLAUDE.md, 2026-07-01). */
|
|
18
|
+
const FORBIDDEN_TRAILERS = [
|
|
19
|
+
'Co-Authored-By: Claude',
|
|
20
|
+
'Co-Authored-By: Anthropic'
|
|
21
|
+
];
|
|
22
|
+
/** Survey-banned target globs that must still be refused before any commit. */
|
|
23
|
+
const BANNED_TARGETS = [
|
|
24
|
+
'tools/mcp_oauth.py',
|
|
25
|
+
'agent/auth/*',
|
|
26
|
+
'agent/conversation_loop.py',
|
|
27
|
+
'agent/tool_guardrails.py',
|
|
28
|
+
'*/prompt_cache*',
|
|
29
|
+
'*/token_cache*'
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Behavioural probe of the RL-3 AI-modified declaration rule.
|
|
33
|
+
*
|
|
34
|
+
* The previous version read the skill plus two markdown files and passed if at
|
|
35
|
+
* least two of the words triage/classify/fix/commit appeared — words that
|
|
36
|
+
* appear throughout the prose regardless of whether any rule survived.
|
|
37
|
+
*
|
|
38
|
+
* Here the rule is anchored on its heading, its literal commit-body markers,
|
|
39
|
+
* the forbidden-trailer list and the survey-banned globs. Deleting the rule
|
|
40
|
+
* section reddens the journey.
|
|
41
|
+
*/
|
|
9
42
|
export async function runJ14Contract(ctx) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
};
|
|
43
|
+
const row = requireBaselineRow(ctx);
|
|
44
|
+
const missing = missingSourceFiles(ctx, row);
|
|
45
|
+
const abs = join(ctx.projectRoot, ...SKILL_MD);
|
|
46
|
+
const body = existsSync(abs) ? readFileSync(abs, 'utf8') : '';
|
|
47
|
+
// The heading carries an em dash, so match the two stable halves instead of
|
|
48
|
+
// the literal punctuation.
|
|
49
|
+
const headingPresent = /###\s+RL-3\b[^\n]*AI-modified declaration/.test(body);
|
|
50
|
+
const missingMarkers = REQUIRED_MARKERS.filter((m) => !body.includes(m));
|
|
51
|
+
const missingTrailers = FORBIDDEN_TRAILERS.filter((t) => !body.includes(t));
|
|
52
|
+
const missingTargets = BANNED_TARGETS.filter((t) => !body.includes(t));
|
|
53
|
+
const result = combineProbes([
|
|
54
|
+
probe(missing.length === 0, `baseline sourceFiles present (${row.sourceFiles.length})`),
|
|
55
|
+
probe(body.length > 0, 'the issue-fix orchestrator skill file is readable'),
|
|
56
|
+
probe(headingPresent, 'the RL-3 AI-modified declaration section is still present'),
|
|
57
|
+
probe(missingMarkers.length === 0, `the commit-body markers are intact (missing: ${missingMarkers.join(', ') || 'none'})`),
|
|
58
|
+
probe(missingTrailers.length === 0, `the AI trailer ban is intact (missing: ${missingTrailers.join(', ') || 'none'})`),
|
|
59
|
+
probe(missingTargets.length === 0, `the survey-banned target globs are intact (missing: ${missingTargets.join(', ') || 'none'})`)
|
|
60
|
+
]);
|
|
61
|
+
const artifact = row.sourceFiles[0] ?? 'skills/peaks-issue-fix-orchestrator/SKILL.md';
|
|
62
|
+
if (result.ok)
|
|
63
|
+
return pass(ctx, artifact);
|
|
64
|
+
return fail(ctx, artifact, 'every fix carries the RL-3 AI-modified declaration and the banned-target list survives', result.detail, 'J14 invariant broken: the AI-modified declaration rule or the banned-target list was removed');
|
|
36
65
|
}
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import type { GuardContext, GuardRunResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Behavioural probe of the fixed coverage-summary discovery order.
|
|
4
|
+
*
|
|
5
|
+
* The previous version listed three openspec files and passed if any of them
|
|
6
|
+
* mentioned "Capability Mapping" AND "coverage" — the string "coverage" appears
|
|
7
|
+
* in `coverage-evidence-reader.ts` from its own filename.
|
|
8
|
+
*
|
|
9
|
+
* Here the real resolver is driven against a tmp project where BOTH candidate
|
|
10
|
+
* files exist, then only the second, then neither. Only a resolver that honours
|
|
11
|
+
* the documented order can produce `coverage/` → `openspec/` → missing.
|
|
12
|
+
*/
|
|
2
13
|
export declare function runJ15Contract(ctx: GuardContext): Promise<GuardRunResult>;
|