peaks-cli 1.4.2 → 2.0.0
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/.claude-plugin/marketplace.json +51 -0
- package/CHANGELOG.md +238 -0
- package/README-en.md +226 -0
- package/README.md +152 -122
- package/dist/src/cli/commands/agent-commands.d.ts +20 -0
- package/dist/src/cli/commands/agent-commands.js +48 -0
- package/dist/src/cli/commands/audit-commands.d.ts +18 -0
- package/dist/src/cli/commands/audit-commands.js +138 -0
- package/dist/src/cli/commands/classify-classify-commands.d.ts +19 -0
- package/dist/src/cli/commands/classify-classify-commands.js +151 -0
- package/dist/src/cli/commands/code-review-commands.d.ts +34 -0
- package/dist/src/cli/commands/code-review-commands.js +83 -0
- package/dist/src/cli/commands/config-commands.js +90 -0
- package/dist/src/cli/commands/context-commands.d.ts +21 -0
- package/dist/src/cli/commands/context-commands.js +167 -0
- package/dist/src/cli/commands/core-artifact-commands.js +60 -2
- package/dist/src/cli/commands/hook-handle.js +50 -0
- package/dist/src/cli/commands/loop-commands.d.ts +21 -0
- package/dist/src/cli/commands/loop-commands.js +128 -0
- package/dist/src/cli/commands/openspec-commands.js +37 -0
- package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
- package/dist/src/cli/commands/preferences-commands.js +147 -0
- package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
- package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
- package/dist/src/cli/commands/understand-commands.js +34 -0
- package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
- package/dist/src/cli/commands/upgrade-commands.js +57 -0
- package/dist/src/cli/commands/workflow-commands.js +70 -0
- package/dist/src/cli/commands/workspace-commands.js +86 -0
- package/dist/src/cli/program.js +30 -0
- package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
- package/dist/src/services/agent/ecc-agent-service.js +143 -0
- package/dist/src/services/artifacts/request-artifact-service.js +14 -0
- package/dist/src/services/audit/backing-detector.d.ts +24 -0
- package/dist/src/services/audit/backing-detector.js +59 -0
- package/dist/src/services/audit/classifier.d.ts +38 -0
- package/dist/src/services/audit/classifier.js +127 -0
- package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
- package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
- package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
- package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
- package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
- package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
- package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
- package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
- package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
- package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
- package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
- package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
- package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
- package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
- package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
- package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
- package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
- package/dist/src/services/audit/enforcers/lint-style.js +173 -0
- package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
- package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
- package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
- package/dist/src/services/audit/enforcers/login-gate.js +40 -0
- package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
- package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
- package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
- package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
- package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
- package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
- package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
- package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
- package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
- package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
- package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
- package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
- package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
- package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
- package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
- package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
- package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
- package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
- package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
- package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
- package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
- package/dist/src/services/audit/red-line-catalog.js +210 -0
- package/dist/src/services/audit/red-lines-service.d.ts +23 -0
- package/dist/src/services/audit/red-lines-service.js +486 -0
- package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
- package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
- package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
- package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
- package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
- package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
- package/dist/src/services/audit/static-service.d.ts +57 -0
- package/dist/src/services/audit/static-service.js +125 -0
- package/dist/src/services/audit/types.d.ts +69 -0
- package/dist/src/services/audit/types.js +13 -0
- package/dist/src/services/classify/classify-service.d.ts +42 -0
- package/dist/src/services/classify/classify-service.js +122 -0
- package/dist/src/services/classify/classify-types.d.ts +79 -0
- package/dist/src/services/classify/classify-types.js +90 -0
- package/dist/src/services/code-review/ocr-service.d.ts +129 -0
- package/dist/src/services/code-review/ocr-service.js +362 -0
- package/dist/src/services/config/config-migration.d.ts +32 -0
- package/dist/src/services/config/config-migration.js +92 -0
- package/dist/src/services/config/config-restore.d.ts +10 -0
- package/dist/src/services/config/config-restore.js +47 -0
- package/dist/src/services/config/config-rollback.d.ts +13 -0
- package/dist/src/services/config/config-rollback.js +26 -0
- package/dist/src/services/config/config-service.d.ts +35 -2
- package/dist/src/services/config/config-service.js +81 -0
- package/dist/src/services/config/config-types.d.ts +58 -0
- package/dist/src/services/config/config-types.js +6 -0
- package/dist/src/services/doctor/doctor-service.js +96 -0
- package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
- package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
- package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
- package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
- package/dist/src/services/ide/ide-registry.js +7 -0
- package/dist/src/services/ide/ide-types.d.ts +1 -1
- package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
- package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
- package/dist/src/services/preferences/preferences-service.d.ts +6 -0
- package/dist/src/services/preferences/preferences-service.js +43 -0
- package/dist/src/services/preferences/preferences-types.d.ts +90 -0
- package/dist/src/services/preferences/preferences-types.js +38 -0
- package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
- package/dist/src/services/skills/skill-conformance-service.js +136 -0
- package/dist/src/services/skills/skill-runbook-service.js +44 -10
- package/dist/src/services/skills/sync-service.d.ts +43 -0
- package/dist/src/services/skills/sync-service.js +99 -0
- package/dist/src/services/slice/slice-check-service.js +166 -13
- package/dist/src/services/slice/slice-check-types.d.ts +1 -1
- package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
- package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
- package/dist/src/services/understand/understand-scan-service.js +15 -2
- package/dist/src/services/understand/understand-types.d.ts +26 -0
- package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
- package/dist/src/services/upgrade/1x-detector-service.js +94 -0
- package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
- package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
- package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
- package/dist/src/services/upgrade/upgrade-service.js +381 -0
- package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
- package/dist/src/services/workspace/sid-naming-guard.js +31 -0
- package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
- package/dist/src/services/workspace/workspace-archive-service.js +32 -0
- package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
- package/dist/src/services/workspace/workspace-clean-service.js +86 -0
- package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
- package/dist/src/services/workspace/workspace-state-service.js +43 -0
- package/dist/src/shared/change-id.js +4 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +8 -2
- package/schemas/doctor-report.schema.json +1 -1
- package/scripts/install-skills.mjs +296 -12
- package/skills/peaks-doctor/SKILL.md +59 -0
- package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
- package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
- package/skills/peaks-doctor/test_prompts.json +17 -0
- package/skills/peaks-ide/SKILL.md +2 -0
- package/skills/peaks-qa/SKILL.md +9 -7
- package/skills/peaks-qa/references/artifact-per-request.md +19 -5
- package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
- package/skills/peaks-qa/references/qa-runbook.md +1 -1
- package/skills/peaks-rd/SKILL.md +25 -10
- package/skills/peaks-rd/references/ocr-integration.md +214 -0
- package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
- package/skills/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/peaks-solo/SKILL.md +10 -4
- package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
- package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P2-a Theme F - workflow-bound shape enforcers.
|
|
3
|
+
*
|
|
4
|
+
* Static pattern scans of openspec/changes/STAR/proposal.md, the
|
|
5
|
+
* rd/tech-doc.md template, and the request-artifact-writing skills
|
|
6
|
+
* for canonical shape (acceptance bullets, spec reference,
|
|
7
|
+
* red-line scope, peaks-doctor acknowledgement).
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
const AC_BULLET_PATTERN = /^\s*-\s+\S/m;
|
|
12
|
+
const SPEC_REFERENCE_HEADING = /^##\s+Spec reference(?:\s+\(canonical\))?\s*$/im;
|
|
13
|
+
const RED_LINE_SCOPE_HEADING = /^##\s+Red-line scope\s*$/im;
|
|
14
|
+
const IMPL_EVIDENCE_HEADING = /^##\s+Implementation evidence\s*$/im;
|
|
15
|
+
const PEAKS_DOCTOR_PATTERN = /\bpeaks[- ]doctor\b/i;
|
|
16
|
+
export function lintOpenSpecAcceptanceBullets(projectRoot) {
|
|
17
|
+
const openspecDir = join(projectRoot, 'openspec', 'changes');
|
|
18
|
+
if (!existsSync(openspecDir))
|
|
19
|
+
return [];
|
|
20
|
+
const hits = [];
|
|
21
|
+
for (const entry of readdirSync(openspecDir)) {
|
|
22
|
+
const stat = statSync(join(openspecDir, entry));
|
|
23
|
+
if (!stat.isDirectory())
|
|
24
|
+
continue;
|
|
25
|
+
const proposal = join(openspecDir, entry, 'proposal.md');
|
|
26
|
+
if (!existsSync(proposal))
|
|
27
|
+
continue;
|
|
28
|
+
const body = readFileSync(proposal, 'utf8');
|
|
29
|
+
// Slice ## Acceptance Criteria heading.
|
|
30
|
+
const acMatch = /##\s+Acceptance Criteria\s*$/im.exec(body);
|
|
31
|
+
if (acMatch === null) {
|
|
32
|
+
hits.push({
|
|
33
|
+
catalogId: 'rl-openspec-proposal-has-acceptance-bullets-001',
|
|
34
|
+
rule: 'openspec proposal has non-empty Acceptance Criteria bullets',
|
|
35
|
+
file: proposal,
|
|
36
|
+
line: 1,
|
|
37
|
+
matchedText: '(missing ## Acceptance Criteria heading)'
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Capture everything from ## Acceptance Criteria up to the next ## heading.
|
|
42
|
+
const tail = body.slice(acMatch.index + acMatch[0].length);
|
|
43
|
+
const nextHeading = tail.search(/^##\s/m);
|
|
44
|
+
const acBlock = nextHeading === -1 ? tail : tail.slice(0, nextHeading);
|
|
45
|
+
if (!AC_BULLET_PATTERN.test(acBlock)) {
|
|
46
|
+
hits.push({
|
|
47
|
+
catalogId: 'rl-openspec-proposal-has-acceptance-bullets-001',
|
|
48
|
+
rule: 'openspec proposal has non-empty Acceptance Criteria bullets',
|
|
49
|
+
file: proposal,
|
|
50
|
+
line: 1,
|
|
51
|
+
matchedText: '(no `- ` bullet under ## Acceptance Criteria)'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return hits;
|
|
56
|
+
}
|
|
57
|
+
export function lintOpenSpecSpecReference(projectRoot) {
|
|
58
|
+
const openspecDir = join(projectRoot, 'openspec', 'changes');
|
|
59
|
+
if (!existsSync(openspecDir))
|
|
60
|
+
return [];
|
|
61
|
+
const hits = [];
|
|
62
|
+
for (const entry of readdirSync(openspecDir)) {
|
|
63
|
+
const stat = statSync(join(openspecDir, entry));
|
|
64
|
+
if (!stat.isDirectory())
|
|
65
|
+
continue;
|
|
66
|
+
const proposal = join(openspecDir, entry, 'proposal.md');
|
|
67
|
+
if (!existsSync(proposal))
|
|
68
|
+
continue;
|
|
69
|
+
const body = readFileSync(proposal, 'utf8');
|
|
70
|
+
if (!SPEC_REFERENCE_HEADING.test(body)) {
|
|
71
|
+
hits.push({
|
|
72
|
+
catalogId: 'rl-openspec-proposal-has-spec-changes-001',
|
|
73
|
+
rule: 'openspec proposal has Spec reference (canonical) link',
|
|
74
|
+
file: proposal,
|
|
75
|
+
line: 1,
|
|
76
|
+
matchedText: '(missing ## Spec reference (canonical) heading)'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return hits;
|
|
81
|
+
}
|
|
82
|
+
export function lintTechDocPresenceShape(projectRoot) {
|
|
83
|
+
// Scan every session's rd/tech-doc.md for the two required
|
|
84
|
+
// sections. The session dir is .peaks/_runtime/<sid>/.
|
|
85
|
+
// .peaks/_runtime/ also contains marker files like
|
|
86
|
+
// `active-skill.json`, `classify-audit.jsonl`, and a
|
|
87
|
+
// `current-change` symlink (per slice 2026-06-12-postinstall-
|
|
88
|
+
// 1x-detector-tdd, the current-change symlink can be broken
|
|
89
|
+
// across major session boundaries). Use lstatSync (does not
|
|
90
|
+
// follow symlinks) + isSymbolicLink guard so broken symlinks
|
|
91
|
+
// and marker files are skipped, not crashed on.
|
|
92
|
+
const runtimeDir = join(projectRoot, '.peaks', '_runtime');
|
|
93
|
+
if (!existsSync(runtimeDir))
|
|
94
|
+
return [];
|
|
95
|
+
const hits = [];
|
|
96
|
+
for (const sessionEntry of readdirSync(runtimeDir)) {
|
|
97
|
+
const sessionDir = join(runtimeDir, sessionEntry);
|
|
98
|
+
let stat;
|
|
99
|
+
try {
|
|
100
|
+
stat = lstatSync(sessionDir);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// broken symlink or unreadable entry — skip
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
107
|
+
continue;
|
|
108
|
+
const techDoc = join(sessionDir, 'rd', 'tech-doc.md');
|
|
109
|
+
if (!existsSync(techDoc))
|
|
110
|
+
continue;
|
|
111
|
+
const body = readFileSync(techDoc, 'utf8');
|
|
112
|
+
if (!RED_LINE_SCOPE_HEADING.test(body) || !IMPL_EVIDENCE_HEADING.test(body)) {
|
|
113
|
+
hits.push({
|
|
114
|
+
catalogId: 'rl-tech-doc-presence-pre-rd-001',
|
|
115
|
+
rule: 'rd/tech-doc.md has Red-line scope + Implementation evidence',
|
|
116
|
+
file: techDoc,
|
|
117
|
+
line: 1,
|
|
118
|
+
matchedText: '(missing ## Red-line scope or ## Implementation evidence)'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return hits;
|
|
123
|
+
}
|
|
124
|
+
export function lintPeaksDoctorAcknowledged(skill) {
|
|
125
|
+
// The enforcer fires only for skills that write a request
|
|
126
|
+
// artifact (rd / qa / prd / ui / sc / txt) — detected by the
|
|
127
|
+
// presence of `peaks request init` or `peaks request show` in
|
|
128
|
+
// the body.
|
|
129
|
+
const writesRequestArtifact = /\bpeaks\s+request\s+(init|show|transition)\b/.test(skill.body);
|
|
130
|
+
if (!writesRequestArtifact)
|
|
131
|
+
return [];
|
|
132
|
+
if (PEAKS_DOCTOR_PATTERN.test(skill.body))
|
|
133
|
+
return [];
|
|
134
|
+
return [{
|
|
135
|
+
catalogId: 'rl-peaks-doctor-skill-acknowledged-001',
|
|
136
|
+
rule: 'skill that writes a request artifact acknowledges peaks doctor',
|
|
137
|
+
file: skill.path,
|
|
138
|
+
line: 1,
|
|
139
|
+
matchedText: '(no peaks doctor / peaks-doctor mention in skill body)'
|
|
140
|
+
}];
|
|
141
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login-gate enforcer (L2.2 P1) — verifies destructive / auth-required
|
|
3
|
+
* paths require explicit user confirmation.
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-login-gate-001: destructive paths (uninstall, drop, force-push) must
|
|
7
|
+
* require user confirmation
|
|
8
|
+
* - rl-login-gate-002: protected paths (auth-required) must check session
|
|
9
|
+
*
|
|
10
|
+
* This is a static-check enforcer: the actual runtime gate is in
|
|
11
|
+
* `peaks mode-enforcement` (the requireUserConfirmation function). The
|
|
12
|
+
* catalog entry points to this file; the audit flags it as cli-backed
|
|
13
|
+
* when the integration is wired (L2.2 ships the wiring).
|
|
14
|
+
*/
|
|
15
|
+
export interface LoginGateInput {
|
|
16
|
+
readonly command: string;
|
|
17
|
+
}
|
|
18
|
+
export interface LoginGateResult {
|
|
19
|
+
readonly destructive: boolean;
|
|
20
|
+
readonly protected: boolean;
|
|
21
|
+
readonly matchedPattern: string | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function checkLoginGate(input: LoginGateInput): LoginGateResult;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login-gate enforcer (L2.2 P1) — verifies destructive / auth-required
|
|
3
|
+
* paths require explicit user confirmation.
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-login-gate-001: destructive paths (uninstall, drop, force-push) must
|
|
7
|
+
* require user confirmation
|
|
8
|
+
* - rl-login-gate-002: protected paths (auth-required) must check session
|
|
9
|
+
*
|
|
10
|
+
* This is a static-check enforcer: the actual runtime gate is in
|
|
11
|
+
* `peaks mode-enforcement` (the requireUserConfirmation function). The
|
|
12
|
+
* catalog entry points to this file; the audit flags it as cli-backed
|
|
13
|
+
* when the integration is wired (L2.2 ships the wiring).
|
|
14
|
+
*/
|
|
15
|
+
const DESTRUCTIVE_PATH_PATTERNS = [
|
|
16
|
+
/uninstall/i,
|
|
17
|
+
/\bdrop\b/i,
|
|
18
|
+
/force-push/i,
|
|
19
|
+
/--force\b/,
|
|
20
|
+
/--hard\b/,
|
|
21
|
+
/rm\s+-rf?\b/,
|
|
22
|
+
];
|
|
23
|
+
const PROTECTED_PATH_PATTERNS = [
|
|
24
|
+
/auth/i,
|
|
25
|
+
/login/i,
|
|
26
|
+
/session/i,
|
|
27
|
+
];
|
|
28
|
+
export function checkLoginGate(input) {
|
|
29
|
+
for (const pattern of DESTRUCTIVE_PATH_PATTERNS) {
|
|
30
|
+
if (pattern.test(input.command)) {
|
|
31
|
+
return { destructive: true, protected: false, matchedPattern: pattern.source };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const pattern of PROTECTED_PATH_PATTERNS) {
|
|
35
|
+
if (pattern.test(input.command)) {
|
|
36
|
+
return { destructive: false, protected: true, matchedPattern: pattern.source };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { destructive: false, protected: false, matchedPattern: null };
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mock-placement enforcer — scans changed files for inline mock-data
|
|
3
|
+
* patterns and fails the slice check if any changed file under `src/` or
|
|
4
|
+
* `skills/` contains `mockData: { ... }`, `fixtures = { ... }`, or a
|
|
5
|
+
* multi-line `const fooMock = { ... }` literal.
|
|
6
|
+
*
|
|
7
|
+
* Per L2 redesign §5.4. Mocks belong in `tests/fixtures/`, not inline.
|
|
8
|
+
* Per `references/mock-data-placement.md` from peaks-rd: framework-aware
|
|
9
|
+
* mock placement. peaks-cli has no UI framework, but the rule still
|
|
10
|
+
* applies for non-fixture files.
|
|
11
|
+
*/
|
|
12
|
+
export interface MockPlacementCheckInput {
|
|
13
|
+
readonly filePath: string;
|
|
14
|
+
readonly content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MockPlacementViolation {
|
|
17
|
+
readonly filePath: string;
|
|
18
|
+
readonly pattern: string;
|
|
19
|
+
readonly snippet: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function hasInlineMock(content: string): MockPlacementViolation | null;
|
|
22
|
+
export declare function findMockViolations(changedFiles: readonly {
|
|
23
|
+
filePath: string;
|
|
24
|
+
content: string;
|
|
25
|
+
}[]): readonly MockPlacementViolation[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mock-placement enforcer — scans changed files for inline mock-data
|
|
3
|
+
* patterns and fails the slice check if any changed file under `src/` or
|
|
4
|
+
* `skills/` contains `mockData: { ... }`, `fixtures = { ... }`, or a
|
|
5
|
+
* multi-line `const fooMock = { ... }` literal.
|
|
6
|
+
*
|
|
7
|
+
* Per L2 redesign §5.4. Mocks belong in `tests/fixtures/`, not inline.
|
|
8
|
+
* Per `references/mock-data-placement.md` from peaks-rd: framework-aware
|
|
9
|
+
* mock placement. peaks-cli has no UI framework, but the rule still
|
|
10
|
+
* applies for non-fixture files.
|
|
11
|
+
*/
|
|
12
|
+
const MOCK_PATTERNS = [
|
|
13
|
+
/\bmockData\s*[:=]\s*\{/,
|
|
14
|
+
/\bfixtures?\s*=\s*\{/,
|
|
15
|
+
/const\s+\w*[Mm]ock\w*\s*=\s*\{[\s\S]{20,}/,
|
|
16
|
+
];
|
|
17
|
+
export function hasInlineMock(content) {
|
|
18
|
+
for (const pattern of MOCK_PATTERNS) {
|
|
19
|
+
const match = pattern.exec(content);
|
|
20
|
+
if (match) {
|
|
21
|
+
return {
|
|
22
|
+
filePath: '',
|
|
23
|
+
pattern: pattern.source,
|
|
24
|
+
snippet: match[0].slice(0, 80),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export function findMockViolations(changedFiles) {
|
|
31
|
+
const violations = [];
|
|
32
|
+
for (const { filePath, content } of changedFiles) {
|
|
33
|
+
// Mocks are allowed in tests/fixtures/. The slice check is invoked
|
|
34
|
+
// with the diff-vs-scope output, which already filters out
|
|
35
|
+
// test/fixture paths; this guard is a safety net.
|
|
36
|
+
if (filePath.includes('tests/fixtures/'))
|
|
37
|
+
continue;
|
|
38
|
+
if (filePath.includes('__fixtures__'))
|
|
39
|
+
continue;
|
|
40
|
+
if (!filePath.startsWith('src/') && !filePath.startsWith('skills/'))
|
|
41
|
+
continue;
|
|
42
|
+
const violation = hasInlineMock(content);
|
|
43
|
+
if (violation) {
|
|
44
|
+
violations.push({ ...violation, filePath });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return violations;
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* no-root-pollution enforcer — PreToolUse Write/Edit guard.
|
|
3
|
+
*
|
|
4
|
+
* Per L2 redesign §5.4. Deny writes to <project>/root for files NOT in
|
|
5
|
+
* the documented allowlist. The allowlist is hand-maintained for v1; a
|
|
6
|
+
* follow-up slice can expose it via `peaks standards`.
|
|
7
|
+
*
|
|
8
|
+
* Trust red line: this hook MUST fail-open on registry / FS errors. The
|
|
9
|
+
* LLM is never bricked by a peaks bug.
|
|
10
|
+
*/
|
|
11
|
+
export interface RootWriteCheckInput {
|
|
12
|
+
readonly projectRoot: string;
|
|
13
|
+
readonly filePath: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RootWriteCheckResult {
|
|
16
|
+
readonly isRoot: boolean;
|
|
17
|
+
readonly allowed: boolean;
|
|
18
|
+
readonly topSegment: string;
|
|
19
|
+
readonly denyReason: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function isRootWrite(input: RootWriteCheckInput): RootWriteCheckResult;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* no-root-pollution enforcer — PreToolUse Write/Edit guard.
|
|
3
|
+
*
|
|
4
|
+
* Per L2 redesign §5.4. Deny writes to <project>/root for files NOT in
|
|
5
|
+
* the documented allowlist. The allowlist is hand-maintained for v1; a
|
|
6
|
+
* follow-up slice can expose it via `peaks standards`.
|
|
7
|
+
*
|
|
8
|
+
* Trust red line: this hook MUST fail-open on registry / FS errors. The
|
|
9
|
+
* LLM is never bricked by a peaks bug.
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
const ROOT_FILE_ALLOWLIST = new Set([
|
|
13
|
+
// Top-level docs
|
|
14
|
+
'README.md', 'README-en.md', 'LICENSE', 'LICENSE.md', 'NOTICE', 'CONTRIBUTING.md',
|
|
15
|
+
'CHANGELOG.md', 'AUTHORS', 'CONTRIBUTORS',
|
|
16
|
+
// Build / package manifests
|
|
17
|
+
'package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'tsconfig.json',
|
|
18
|
+
'tsconfig.*.json', 'vitest.config.ts', 'vite.config.ts', '.npmrc', '.nvmrc',
|
|
19
|
+
// VCS / editor
|
|
20
|
+
'.gitignore', '.gitattributes', '.editorconfig', '.gitkeep',
|
|
21
|
+
// Project-local config dirs (peaks-cli convention)
|
|
22
|
+
'openspec', '.peaks', '.claude', '.peaksrc',
|
|
23
|
+
// Source dirs (writes into them are normal)
|
|
24
|
+
'src', 'tests', 'bin', 'scripts', 'schemas', 'output-styles', 'docs',
|
|
25
|
+
// Skills are allowed at root
|
|
26
|
+
'skills',
|
|
27
|
+
// Generated / ignored
|
|
28
|
+
'dist', 'node_modules', 'coverage', '.nyc_output',
|
|
29
|
+
]);
|
|
30
|
+
export function isRootWrite(input) {
|
|
31
|
+
const projectRoot = resolve(input.projectRoot);
|
|
32
|
+
const filePath = resolve(input.filePath);
|
|
33
|
+
const rel = filePath.startsWith(projectRoot)
|
|
34
|
+
? filePath.slice(projectRoot.length).replace(/^[\\/]+/, '')
|
|
35
|
+
: filePath;
|
|
36
|
+
const topSegment = rel.split(/[\\/]/)[0] ?? '';
|
|
37
|
+
// If file is NOT at root (e.g. src/foo/bar.ts), the top segment is "src"
|
|
38
|
+
// which is in the allowlist. This handler only flags FILES AT THE ROOT
|
|
39
|
+
// (top-level), so check whether the file is exactly at root depth.
|
|
40
|
+
const segments = rel.split(/[\\/]/).filter(Boolean);
|
|
41
|
+
const isAtRoot = segments.length === 1;
|
|
42
|
+
if (!isAtRoot) {
|
|
43
|
+
return { isRoot: false, allowed: true, topSegment, denyReason: '' };
|
|
44
|
+
}
|
|
45
|
+
if (ROOT_FILE_ALLOWLIST.has(topSegment)) {
|
|
46
|
+
return { isRoot: true, allowed: true, topSegment, denyReason: '' };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
isRoot: true,
|
|
50
|
+
allowed: false,
|
|
51
|
+
topSegment,
|
|
52
|
+
denyReason: `no-root-pollution: file "${rel}" is not in the root allowlist. ` +
|
|
53
|
+
`Move it under docs/, tests/, skills/, or another documented directory, ` +
|
|
54
|
+
`or add it to ROOT_FILE_ALLOWLIST in src/services/audit/enforcers/no-root-pollution.ts.`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre-rd-scan enforcer (L2.2 P1) — verifies the project has been scanned
|
|
3
|
+
* before the rd implementation phase begins.
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-pre-rd-scan-001: peaks scan archetype must have been run
|
|
7
|
+
* - rl-pre-rd-scan-002: peaks standards preflight must have been run
|
|
8
|
+
*
|
|
9
|
+
* Detected by checking for the project-scan.md and standards reports in
|
|
10
|
+
* the session dir. Both are produced by the pre-RD workflow.
|
|
11
|
+
*/
|
|
12
|
+
export interface PreRdScanInput {
|
|
13
|
+
readonly projectRoot: string;
|
|
14
|
+
readonly sessionId: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PreRdScanResult {
|
|
17
|
+
readonly archetypeScanned: boolean;
|
|
18
|
+
readonly archetypeReportPath: string;
|
|
19
|
+
readonly standardsPreflightDone: boolean;
|
|
20
|
+
readonly standardsReportPath: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function checkPreRdScan(input: PreRdScanInput): PreRdScanResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre-rd-scan enforcer (L2.2 P1) — verifies the project has been scanned
|
|
3
|
+
* before the rd implementation phase begins.
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-pre-rd-scan-001: peaks scan archetype must have been run
|
|
7
|
+
* - rl-pre-rd-scan-002: peaks standards preflight must have been run
|
|
8
|
+
*
|
|
9
|
+
* Detected by checking for the project-scan.md and standards reports in
|
|
10
|
+
* the session dir. Both are produced by the pre-RD workflow.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
export function checkPreRdScan(input) {
|
|
15
|
+
const archetypeReportPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'rd/project-scan.md');
|
|
16
|
+
const standardsReportPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'standards-preflight.json');
|
|
17
|
+
return {
|
|
18
|
+
archetypeScanned: existsSync(archetypeReportPath),
|
|
19
|
+
archetypeReportPath,
|
|
20
|
+
standardsPreflightDone: existsSync(standardsReportPath),
|
|
21
|
+
standardsReportPath,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prototype-fidelity enforcer (L2.2 P1) — verifies a prototype is
|
|
3
|
+
* functional (not a stub).
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-prototype-fidelity-001: prototype files must not contain TODO/FIXME/XXX
|
|
7
|
+
* - rl-prototype-fidelity-002: prototype must have at least 1 passing test
|
|
8
|
+
*
|
|
9
|
+
* (L2.2 ships the source-level check; deeper test-running integration is
|
|
10
|
+
* deferred to a follow-up slice.)
|
|
11
|
+
*/
|
|
12
|
+
export interface PrototypeFidelityInput {
|
|
13
|
+
readonly projectRoot: string;
|
|
14
|
+
readonly filePaths: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface PrototypeFidelityResult {
|
|
17
|
+
readonly stubMarkers: readonly {
|
|
18
|
+
filePath: string;
|
|
19
|
+
pattern: string;
|
|
20
|
+
snippet: string;
|
|
21
|
+
}[];
|
|
22
|
+
readonly testFiles: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function findStubMarkers(input: PrototypeFidelityInput): PrototypeFidelityResult;
|
|
25
|
+
export declare function findTestFiles(projectRoot: string, sourceDir: string): readonly string[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prototype-fidelity enforcer (L2.2 P1) — verifies a prototype is
|
|
3
|
+
* functional (not a stub).
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-prototype-fidelity-001: prototype files must not contain TODO/FIXME/XXX
|
|
7
|
+
* - rl-prototype-fidelity-002: prototype must have at least 1 passing test
|
|
8
|
+
*
|
|
9
|
+
* (L2.2 ships the source-level check; deeper test-running integration is
|
|
10
|
+
* deferred to a follow-up slice.)
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
const STUB_MARKER_PATTERNS = [
|
|
15
|
+
/\bTODO\b/,
|
|
16
|
+
/\bFIXME\b/,
|
|
17
|
+
/\bXXX\b/,
|
|
18
|
+
/\bHACK\b/,
|
|
19
|
+
/\bstub\b\s*[:=]/i,
|
|
20
|
+
/\bnot implemented\b/i,
|
|
21
|
+
];
|
|
22
|
+
export function findStubMarkers(input) {
|
|
23
|
+
const markers = [];
|
|
24
|
+
for (const filePath of input.filePaths) {
|
|
25
|
+
const abs = join(input.projectRoot, filePath);
|
|
26
|
+
let content;
|
|
27
|
+
try {
|
|
28
|
+
content = readFileSync(abs, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
for (const pattern of STUB_MARKER_PATTERNS) {
|
|
34
|
+
const match = pattern.exec(content);
|
|
35
|
+
if (match !== null) {
|
|
36
|
+
markers.push({ filePath, pattern: pattern.source, snippet: match[0] });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { stubMarkers: markers, testFiles: [] };
|
|
41
|
+
}
|
|
42
|
+
export function findTestFiles(projectRoot, sourceDir) {
|
|
43
|
+
const testsDir = join(projectRoot, sourceDir.replace(/^src\//, 'tests/'));
|
|
44
|
+
if (!existsSync(testsDir))
|
|
45
|
+
return [];
|
|
46
|
+
try {
|
|
47
|
+
const stat = statSync(testsDir);
|
|
48
|
+
if (!stat.isDirectory())
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const result = [];
|
|
55
|
+
const walk = (dir) => {
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const full = join(dir, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
walk(full);
|
|
67
|
+
}
|
|
68
|
+
else if (entry.isFile() && /\.(test|spec)\.ts$/.test(entry.name)) {
|
|
69
|
+
result.push(full);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
walk(testsDir);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resume-detection enforcer (L2.2 P1) — verifies a slice can be resumed
|
|
3
|
+
* before the LLM continues work on it.
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-resume-detection-001: session-binding file must exist
|
|
7
|
+
* - rl-resume-detection-002: existing rd request state must be in
|
|
8
|
+
* {spec-locked, implemented, qa-handoff} (resumable states)
|
|
9
|
+
*/
|
|
10
|
+
export interface ResumeDetectionInput {
|
|
11
|
+
readonly projectRoot: string;
|
|
12
|
+
readonly sessionId: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ResumeDetectionResult {
|
|
15
|
+
readonly sessionBindingExists: boolean;
|
|
16
|
+
readonly sessionBindingPath: string;
|
|
17
|
+
readonly requestState: string | null;
|
|
18
|
+
readonly requestStatePath: string;
|
|
19
|
+
readonly canResume: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function checkResume(input: ResumeDetectionInput): ResumeDetectionResult;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resume-detection enforcer (L2.2 P1) — verifies a slice can be resumed
|
|
3
|
+
* before the LLM continues work on it.
|
|
4
|
+
*
|
|
5
|
+
* Two red lines:
|
|
6
|
+
* - rl-resume-detection-001: session-binding file must exist
|
|
7
|
+
* - rl-resume-detection-002: existing rd request state must be in
|
|
8
|
+
* {spec-locked, implemented, qa-handoff} (resumable states)
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
const RESUMABLE_STATES = new Set(['spec-locked', 'implemented', 'qa-handoff']);
|
|
13
|
+
export function checkResume(input) {
|
|
14
|
+
const sessionBindingPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'session.json');
|
|
15
|
+
const sessionBindingExists = existsSync(sessionBindingPath);
|
|
16
|
+
// The rd request artifact lives under .peaks/_runtime/<sid>/rd/requests/<rid>.md
|
|
17
|
+
// (canonical session layout per `src-services-session-canonical-workspace-resolver.md`).
|
|
18
|
+
// We look at the most-recent rd request to detect state. The full request-artifact
|
|
19
|
+
// service is the authoritative source; this is a lightweight check for the audit
|
|
20
|
+
// scanner.
|
|
21
|
+
const rdDir = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'rd/requests');
|
|
22
|
+
let requestState = null;
|
|
23
|
+
let requestStatePath = '';
|
|
24
|
+
if (existsSync(rdDir)) {
|
|
25
|
+
try {
|
|
26
|
+
const files = require('node:fs').readdirSync(rdDir);
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
if (!file.endsWith('.md'))
|
|
29
|
+
continue;
|
|
30
|
+
const path = join(rdDir, file);
|
|
31
|
+
const content = readFileSync(path, 'utf8');
|
|
32
|
+
const match = /state:\s*([a-z0-9-]+)/i.exec(content);
|
|
33
|
+
if (match !== null) {
|
|
34
|
+
requestState = match[1] ?? null;
|
|
35
|
+
requestStatePath = path;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// ignore read errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const canResume = sessionBindingExists && requestState !== null && RESUMABLE_STATES.has(requestState);
|
|
45
|
+
return {
|
|
46
|
+
sessionBindingExists,
|
|
47
|
+
sessionBindingPath,
|
|
48
|
+
requestState,
|
|
49
|
+
requestStatePath,
|
|
50
|
+
canResume,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solo-code-ban enforcer — PreToolUse Bash guard.
|
|
3
|
+
*
|
|
4
|
+
* Per L2 redesign §5.4. Deny `git commit` or `git apply` invocations from
|
|
5
|
+
* a peaks-* skill. The Solo Code-Change Red Line says peaks-solo / peaks-rd
|
|
6
|
+
* are orchestrators, not implementers; the actual `git commit` step must
|
|
7
|
+
* go through `peaks request transition`, which itself enforces spec-locked
|
|
8
|
+
* + tech-doc-presence.
|
|
9
|
+
*
|
|
10
|
+
* Trust red line (per `gate-enforcement-hook.md`): if the registry or
|
|
11
|
+
* manifest read fails, the hook must fail-OPEN (warn + allow). The LLM is
|
|
12
|
+
* never bricked by a peaks bug.
|
|
13
|
+
*/
|
|
14
|
+
export interface SoloCodeBanInput {
|
|
15
|
+
readonly skill: string;
|
|
16
|
+
readonly command: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SoloCodeBanResult {
|
|
19
|
+
readonly denied: boolean;
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function isSoloCodeCommit(skill: string, command: string): boolean;
|
|
23
|
+
export declare function evaluateSoloCodeBan(input: SoloCodeBanInput): SoloCodeBanResult;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solo-code-ban enforcer — PreToolUse Bash guard.
|
|
3
|
+
*
|
|
4
|
+
* Per L2 redesign §5.4. Deny `git commit` or `git apply` invocations from
|
|
5
|
+
* a peaks-* skill. The Solo Code-Change Red Line says peaks-solo / peaks-rd
|
|
6
|
+
* are orchestrators, not implementers; the actual `git commit` step must
|
|
7
|
+
* go through `peaks request transition`, which itself enforces spec-locked
|
|
8
|
+
* + tech-doc-presence.
|
|
9
|
+
*
|
|
10
|
+
* Trust red line (per `gate-enforcement-hook.md`): if the registry or
|
|
11
|
+
* manifest read fails, the hook must fail-OPEN (warn + allow). The LLM is
|
|
12
|
+
* never bricked by a peaks bug.
|
|
13
|
+
*/
|
|
14
|
+
const COMMIT_APPLY_PATTERN = /^\s*git\s+(commit|apply)\b/;
|
|
15
|
+
const DENY_REASON = 'Solo Code-Change Red Line: peaks-* skills must go through peaks-solo / peaks-rd. ' +
|
|
16
|
+
'Use `peaks request transition` instead of `git commit` / `git apply` directly.';
|
|
17
|
+
export function isSoloCodeCommit(skill, command) {
|
|
18
|
+
if (!skill.startsWith('peaks-'))
|
|
19
|
+
return false;
|
|
20
|
+
return COMMIT_APPLY_PATTERN.test(command);
|
|
21
|
+
}
|
|
22
|
+
export function evaluateSoloCodeBan(input) {
|
|
23
|
+
if (isSoloCodeCommit(input.skill, input.command)) {
|
|
24
|
+
return { denied: true, reason: DENY_REASON };
|
|
25
|
+
}
|
|
26
|
+
return { denied: false, reason: '' };
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sub-agent-sid enforcer — dogfood of Slice 0.5 sid-naming-guard.
|
|
3
|
+
*
|
|
4
|
+
* Per L2 redesign §5.4, "sub-agent-sid" is one of the 5 P0 red lines.
|
|
5
|
+
* Slice 0.5 (Task 7) shipped `isValidSessionId` in
|
|
6
|
+
* `src/services/workspace/sid-naming-guard.ts`. This enforcer exposes the
|
|
7
|
+
* same check to the red-line audit framework, so any invalid sid under
|
|
8
|
+
* `.peaks/_sub_agents/` shows up as a backable red line in the audit.
|
|
9
|
+
*/
|
|
10
|
+
export interface SubAgentSidCheckResult {
|
|
11
|
+
readonly invalid: readonly string[];
|
|
12
|
+
readonly valid: readonly string[];
|
|
13
|
+
readonly scanned: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Find sids under `.peaks/_sub_agents/<sid>/` that fail `isValidSessionId`.
|
|
17
|
+
* Bare forms (sid-3, unknown-sid) and date-mismatched sids all fail.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findInvalidSubAgentSids(projectRoot: string): SubAgentSidCheckResult;
|
|
20
|
+
/**
|
|
21
|
+
* Same check, for `.peaks/_runtime/<sid>/`. Slice 0.5's clean-service
|
|
22
|
+
* already handles the sub-agents dir; the audit framework also wants to
|
|
23
|
+
* know about invalid runtime sids (which would be a different failure mode).
|
|
24
|
+
*/
|
|
25
|
+
export declare function findInvalidRuntimeSids(projectRoot: string): SubAgentSidCheckResult;
|