peaks-cli 1.3.7 → 1.3.8
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/dist/src/cli/commands/core-artifact-commands.js +119 -14
- package/dist/src/cli/commands/request-commands.js +31 -1
- package/dist/src/services/artifacts/request-artifact-service.d.ts +16 -0
- package/dist/src/services/artifacts/request-artifact-service.js +18 -2
- package/dist/src/services/session/caller-binding-service.d.ts +70 -0
- package/dist/src/services/session/caller-binding-service.js +148 -0
- package/dist/src/services/session/caller-id-types.d.ts +77 -0
- package/dist/src/services/session/caller-id-types.js +46 -0
- package/dist/src/services/session/index.d.ts +4 -0
- package/dist/src/services/session/index.js +5 -0
- package/dist/src/services/session/platform-fallbacks.d.ts +31 -0
- package/dist/src/services/session/platform-fallbacks.js +35 -0
- package/dist/src/services/session/resolve-caller-id.d.ts +57 -0
- package/dist/src/services/session/resolve-caller-id.js +88 -0
- package/dist/src/services/skills/skill-presence-service.d.ts +11 -0
- package/dist/src/services/skills/skill-presence-service.js +59 -0
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-rd/SKILL.md +17 -137
- package/skills/peaks-rd/references/rd-transition-gates.md +148 -0
- package/skills/peaks-solo/SKILL.md +18 -209
- package/skills/peaks-solo/references/frontend-only-mode.md +73 -0
- package/skills/peaks-solo/references/project-scan-checklist.md +136 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLATFORM_FALLBACKS — the Level 3 fallback table for caller-id resolution.
|
|
3
|
+
*
|
|
4
|
+
* Slice 020 (D3): when neither `--caller-id` nor `PEAKS_CALLER_ID` is
|
|
5
|
+
* set, the resolver walks this table top-to-bottom and takes the
|
|
6
|
+
* first non-empty entry. Today there is exactly one entry: Claude
|
|
7
|
+
* Code (`CLAUDE_CODE_SESSION_ID`).
|
|
8
|
+
*
|
|
9
|
+
* To add a new platform (Cursor, Windsurf, peaks-ide, etc.):
|
|
10
|
+
*
|
|
11
|
+
* 1. Add a new entry below.
|
|
12
|
+
* 2. Bump the contract doc's A5 acceptance criterion
|
|
13
|
+
* (`.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`).
|
|
14
|
+
* 3. Add a regression test that asserts the new entry resolves
|
|
15
|
+
* correctly under D4 priority.
|
|
16
|
+
*
|
|
17
|
+
* The contract's A5 test (`tests/unit/services/session/caller-id-resolution.test.ts`)
|
|
18
|
+
* asserts `PLATFORM_FALLBACKS.length === 1`; adding a new entry will
|
|
19
|
+
* fail that test, forcing the contract bump.
|
|
20
|
+
*
|
|
21
|
+
* Adding an entry does NOT require code changes to read points
|
|
22
|
+
* (statusline, doctor, sc, session-info) — they all call the same
|
|
23
|
+
* resolver. Each entry is a one-line additive change.
|
|
24
|
+
*/
|
|
25
|
+
export const PLATFORM_FALLBACKS = [
|
|
26
|
+
{
|
|
27
|
+
envVar: 'CLAUDE_CODE_SESSION_ID',
|
|
28
|
+
description: 'Claude Code session id',
|
|
29
|
+
addedIn: '1.3.7'
|
|
30
|
+
}
|
|
31
|
+
// Future entries (do NOT add without bumping the contract's A5):
|
|
32
|
+
// { envVar: 'CURSOR_SESSION_ID', description: 'Cursor session id', addedIn: 'TBD' },
|
|
33
|
+
// { envVar: 'WINDSURF_SESSION_ID', description: 'Windsurf session id', addedIn: 'TBD' },
|
|
34
|
+
// { envVar: 'PEAKS_IDE_SESSION_ID', description: 'peaks-ide session id', addedIn: 'TBD' },
|
|
35
|
+
];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-Id Resolution (slice 020 — caller-keyed session binding).
|
|
3
|
+
*
|
|
4
|
+
* `resolveCallerId` is the single source of truth for "who is calling
|
|
5
|
+
* the CLI". The resolver applies D4 priority (flag > env > platform
|
|
6
|
+
* fallback > reject) and validates the winner against D1's regex;
|
|
7
|
+
* failures throw `CallerIdError` (D2 → exit 64, D5 → exit 65).
|
|
8
|
+
*
|
|
9
|
+
* The function is synchronous and pure. It does NOT touch the
|
|
10
|
+
* filesystem, does NOT read any caller binding file, and does NOT
|
|
11
|
+
* mutate state. The caller (a CLI command, a service, a test) decides
|
|
12
|
+
* what to do with the resolved id.
|
|
13
|
+
*
|
|
14
|
+
* See `.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`
|
|
15
|
+
* for the freeze-in contract (D1-D7).
|
|
16
|
+
*/
|
|
17
|
+
import { CallerIdError } from './caller-id-types.js';
|
|
18
|
+
export { CallerIdError };
|
|
19
|
+
export interface ResolveCallerIdOptions {
|
|
20
|
+
/**
|
|
21
|
+
* The `--caller-id <id>` flag value (per-invocation override).
|
|
22
|
+
* D4 priority level 1: flag wins.
|
|
23
|
+
*/
|
|
24
|
+
flagValue?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Override for the `PEAKS_CALLER_ID` environment variable. D4
|
|
27
|
+
* priority level 2: env wins. Defaults to `process.env.PEAKS_CALLER_ID`.
|
|
28
|
+
* The override exists so tests can run without mutating process.env.
|
|
29
|
+
*/
|
|
30
|
+
envOverride?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The env object to read. Defaults to `process.env`. Exists so
|
|
33
|
+
* tests can drive Level 3 (platform fallback) without mutating
|
|
34
|
+
* process.env.
|
|
35
|
+
*/
|
|
36
|
+
env?: NodeJS.ProcessEnv;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the calling process's callerId per D1-D5.
|
|
40
|
+
*
|
|
41
|
+
* D4 priority (strict, no merge):
|
|
42
|
+
* 1. `opts.flagValue` (per-invocation `--caller-id <id>` override)
|
|
43
|
+
* 2. `opts.envOverride ?? process.env.PEAKS_CALLER_ID` (per-process declaration)
|
|
44
|
+
* 3. First non-empty entry in `PLATFORM_FALLBACKS` (platform default)
|
|
45
|
+
* 4. → **D2 fires**: throw `CallerIdError` (EX_USAGE, exit 64)
|
|
46
|
+
*
|
|
47
|
+
* On success: returns the resolved id (matches D1's regex, validated).
|
|
48
|
+
* On D2 (nothing set): throws `CallerIdError` (EX_USAGE, exit 64).
|
|
49
|
+
* On D5 (regex fail): throws `CallerIdError` (EX_DATAERR, exit 65).
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* resolveCallerId({ flagValue: 'foo-bar' }) // → 'foo-bar'
|
|
53
|
+
* resolveCallerId({ envOverride: 'baz' }) // → 'baz'
|
|
54
|
+
* resolveCallerId({ env: { CLAUDE_CODE_SESSION_ID: 'sid-123' } }) // → 'sid-123'
|
|
55
|
+
* resolveCallerId() // → throws CallerIdError (EX_USAGE)
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveCallerId(opts?: ResolveCallerIdOptions): string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-Id Resolution (slice 020 — caller-keyed session binding).
|
|
3
|
+
*
|
|
4
|
+
* `resolveCallerId` is the single source of truth for "who is calling
|
|
5
|
+
* the CLI". The resolver applies D4 priority (flag > env > platform
|
|
6
|
+
* fallback > reject) and validates the winner against D1's regex;
|
|
7
|
+
* failures throw `CallerIdError` (D2 → exit 64, D5 → exit 65).
|
|
8
|
+
*
|
|
9
|
+
* The function is synchronous and pure. It does NOT touch the
|
|
10
|
+
* filesystem, does NOT read any caller binding file, and does NOT
|
|
11
|
+
* mutate state. The caller (a CLI command, a service, a test) decides
|
|
12
|
+
* what to do with the resolved id.
|
|
13
|
+
*
|
|
14
|
+
* See `.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`
|
|
15
|
+
* for the freeze-in contract (D1-D7).
|
|
16
|
+
*/
|
|
17
|
+
import { CALLER_ID_REGEX, CallerIdError } from './caller-id-types.js';
|
|
18
|
+
import { PLATFORM_FALLBACKS } from './platform-fallbacks.js';
|
|
19
|
+
// Re-export for CLI consumers (avoids a second import line).
|
|
20
|
+
export { CallerIdError };
|
|
21
|
+
/**
|
|
22
|
+
* Check whether `value` looks like a callerId (non-empty, matches D1).
|
|
23
|
+
* Returns the trimmed value if so, undefined otherwise. Does not throw.
|
|
24
|
+
*/
|
|
25
|
+
function isNonEmpty(value) {
|
|
26
|
+
return typeof value === 'string' && value.length > 0;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read a single platform-fallback env var from `env`. Returns the
|
|
30
|
+
* non-empty trimmed value or `undefined`. Logs nothing.
|
|
31
|
+
*/
|
|
32
|
+
function readPlatformFallback(env) {
|
|
33
|
+
for (let i = 0; i < PLATFORM_FALLBACKS.length; i++) {
|
|
34
|
+
const candidate = env[PLATFORM_FALLBACKS[i].envVar];
|
|
35
|
+
if (isNonEmpty(candidate)) {
|
|
36
|
+
return { value: candidate, index: i };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate `value` against D1's regex. Returns the value on success,
|
|
43
|
+
* throws `CallerIdError` (EX_DATAERR, exit 65) on failure.
|
|
44
|
+
*/
|
|
45
|
+
function validateCallerId(value, source) {
|
|
46
|
+
if (!CALLER_ID_REGEX.test(value)) {
|
|
47
|
+
throw new CallerIdError('EX_DATAERR', source, `Invalid caller id "${value}" (source: ${source}). callerId must match ^[a-zA-Z0-9._-]{1,200}$.`, value);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the calling process's callerId per D1-D5.
|
|
53
|
+
*
|
|
54
|
+
* D4 priority (strict, no merge):
|
|
55
|
+
* 1. `opts.flagValue` (per-invocation `--caller-id <id>` override)
|
|
56
|
+
* 2. `opts.envOverride ?? process.env.PEAKS_CALLER_ID` (per-process declaration)
|
|
57
|
+
* 3. First non-empty entry in `PLATFORM_FALLBACKS` (platform default)
|
|
58
|
+
* 4. → **D2 fires**: throw `CallerIdError` (EX_USAGE, exit 64)
|
|
59
|
+
*
|
|
60
|
+
* On success: returns the resolved id (matches D1's regex, validated).
|
|
61
|
+
* On D2 (nothing set): throws `CallerIdError` (EX_USAGE, exit 64).
|
|
62
|
+
* On D5 (regex fail): throws `CallerIdError` (EX_DATAERR, exit 65).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* resolveCallerId({ flagValue: 'foo-bar' }) // → 'foo-bar'
|
|
66
|
+
* resolveCallerId({ envOverride: 'baz' }) // → 'baz'
|
|
67
|
+
* resolveCallerId({ env: { CLAUDE_CODE_SESSION_ID: 'sid-123' } }) // → 'sid-123'
|
|
68
|
+
* resolveCallerId() // → throws CallerIdError (EX_USAGE)
|
|
69
|
+
*/
|
|
70
|
+
export function resolveCallerId(opts = {}) {
|
|
71
|
+
const env = opts.env ?? process.env;
|
|
72
|
+
// D4 level 1: flag value
|
|
73
|
+
if (isNonEmpty(opts.flagValue)) {
|
|
74
|
+
return validateCallerId(opts.flagValue, 'flag');
|
|
75
|
+
}
|
|
76
|
+
// D4 level 2: env var
|
|
77
|
+
const envValue = isNonEmpty(opts.envOverride) ? opts.envOverride : env.PEAKS_CALLER_ID;
|
|
78
|
+
if (isNonEmpty(envValue)) {
|
|
79
|
+
return validateCallerId(envValue, 'env');
|
|
80
|
+
}
|
|
81
|
+
// D4 level 3: PLATFORM_FALLBACKS table (top-to-bottom)
|
|
82
|
+
const fallback = readPlatformFallback(env);
|
|
83
|
+
if (fallback !== undefined) {
|
|
84
|
+
return validateCallerId(fallback.value, 'fallback');
|
|
85
|
+
}
|
|
86
|
+
// D4 level 4: D2 fires — no callerId available
|
|
87
|
+
throw new CallerIdError('EX_USAGE', 'none', 'No caller id available. Set PEAKS_CALLER_ID or pass --caller-id.');
|
|
88
|
+
}
|
|
@@ -43,6 +43,17 @@ export type SkillPresence = {
|
|
|
43
43
|
lastHeartbeat?: string;
|
|
44
44
|
};
|
|
45
45
|
export declare function exportSkillPresence(projectRootOverride?: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Write the per-caller active-skill marker to
|
|
48
|
+
* `.peaks/_runtime/<peakSid>/active-skill-<callerId>.json` (D6). Returns
|
|
49
|
+
* the written presence with the `callerId` field set.
|
|
50
|
+
*
|
|
51
|
+
* The caller is responsible for resolving the `callerId` (via
|
|
52
|
+
* `resolveCallerId` from `src/services/session/resolve-caller-id.ts`)
|
|
53
|
+
* and the `peakSessionId` (via `getCallerBinding` then reading
|
|
54
|
+
* `peakSessionId`, OR via `ensureSession` for the first-time case).
|
|
55
|
+
*/
|
|
56
|
+
export declare function setSkillPresenceForCaller(projectRootOverride: string, callerId: string, peakSessionId: string, skill: string, mode?: string, gate?: string): SkillPresence;
|
|
46
57
|
export declare function setSkillPresence(skill: string, mode?: string, gate?: string, projectRootOverride?: string): SkillPresence;
|
|
47
58
|
export declare function getSkillPresence(projectRootOverride?: string): SkillPresence | null;
|
|
48
59
|
export declare function touchSkillHeartbeat(projectRootOverride?: string): SkillPresence | null;
|
|
@@ -160,6 +160,65 @@ function getPreviousOuterSessionId(projectRootOverride) {
|
|
|
160
160
|
export function exportSkillPresence(projectRootOverride) {
|
|
161
161
|
return resolvePresencePath(projectRootOverride);
|
|
162
162
|
}
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Slice 020 — caller-keyed active-skill marker (D6).
|
|
165
|
+
// ============================================================================
|
|
166
|
+
//
|
|
167
|
+
// Today's per-project active-skill marker (`.peaks/_runtime/active-skill.json`)
|
|
168
|
+
// races when multiple Claude Code windows (or different platforms) drive the
|
|
169
|
+
// same project concurrently. Slice 020 introduces a per-caller file at
|
|
170
|
+
// `.peaks/_runtime/<peakSid>/active-skill-<callerId>.json` (D6). Two callers
|
|
171
|
+
// bound to the same peak session never clobber each other.
|
|
172
|
+
//
|
|
173
|
+
// The single-file marker is RETAINED for one minor release as read-only
|
|
174
|
+
// back-compat (M1, M4). The new write path is `setSkillPresenceForCaller`;
|
|
175
|
+
// the legacy `setSkillPresence` is now a thin wrapper that synthesises a
|
|
176
|
+
// legacy callerId from `process.env.CLAUDE_CODE_SESSION_ID` (or
|
|
177
|
+
// `projectRoot` for the truly-anonymous case) and delegates.
|
|
178
|
+
/**
|
|
179
|
+
* Write the per-caller active-skill marker to
|
|
180
|
+
* `.peaks/_runtime/<peakSid>/active-skill-<callerId>.json` (D6). Returns
|
|
181
|
+
* the written presence with the `callerId` field set.
|
|
182
|
+
*
|
|
183
|
+
* The caller is responsible for resolving the `callerId` (via
|
|
184
|
+
* `resolveCallerId` from `src/services/session/resolve-caller-id.ts`)
|
|
185
|
+
* and the `peakSessionId` (via `getCallerBinding` then reading
|
|
186
|
+
* `peakSessionId`, OR via `ensureSession` for the first-time case).
|
|
187
|
+
*/
|
|
188
|
+
export function setSkillPresenceForCaller(projectRootOverride, callerId, peakSessionId, skill, mode, gate) {
|
|
189
|
+
const validatedMode = mode && isSkillPresenceMode(mode) ? mode : undefined;
|
|
190
|
+
const now = new Date().toISOString();
|
|
191
|
+
const presence = {
|
|
192
|
+
skill,
|
|
193
|
+
...(validatedMode ? { mode: validatedMode } : {}),
|
|
194
|
+
...(gate ? { gate } : {}),
|
|
195
|
+
...(peakSessionId ? { sessionId: peakSessionId } : {}),
|
|
196
|
+
...(callerId ? { outerSessionId: callerId } : {}),
|
|
197
|
+
setAt: now,
|
|
198
|
+
lastHeartbeat: now
|
|
199
|
+
};
|
|
200
|
+
const presencePath = getActiveSkillFileForCallerPath(resolveProjectRoot(projectRootOverride), peakSessionId, callerId);
|
|
201
|
+
const presenceDir = dirname(presencePath);
|
|
202
|
+
if (!existsSync(presenceDir)) {
|
|
203
|
+
mkdirSync(presenceDir, { recursive: true });
|
|
204
|
+
}
|
|
205
|
+
writeFileSync(presencePath, JSON.stringify(presence, null, 2), 'utf8');
|
|
206
|
+
// Skill-activation side effect: bring the memory store into existence for
|
|
207
|
+
// fresh projects. Same fail-open contract as the legacy path.
|
|
208
|
+
ensureMemoryBootstrap(resolveProjectRoot(projectRootOverride));
|
|
209
|
+
return presence;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Compute the per-caller active-skill file path. Re-exported for test
|
|
213
|
+
* ergonomics; canonical path lives in
|
|
214
|
+
* `src/services/session/caller-binding-service.ts` but inlined here to
|
|
215
|
+
* avoid a circular import (`caller-binding-service` reads
|
|
216
|
+
* `skill-presence-service` for the `setCallerBinding` integration in
|
|
217
|
+
* future slices; the inverse import would deadlock).
|
|
218
|
+
*/
|
|
219
|
+
function getActiveSkillFileForCallerPath(projectRoot, peakSessionId, callerId) {
|
|
220
|
+
return resolve(projectRoot, '.peaks', '_runtime', peakSessionId, `active-skill-${callerId}.json`);
|
|
221
|
+
}
|
|
163
222
|
export function setSkillPresence(skill, mode, gate, projectRootOverride) {
|
|
164
223
|
const validatedMode = mode && isSkillPresenceMode(mode) ? mode : undefined;
|
|
165
224
|
const sessionId = getCurrentSessionId(projectRootOverride);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.3.
|
|
1
|
+
export declare const CLI_VERSION = "1.3.8";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.3.
|
|
1
|
+
export const CLI_VERSION = "1.3.8";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "Cross-AI-IDE workflow-gating CLI + skill family (Claude Code shipped, Trae in progress; Codex / Cursor / Qoder / Tongyi Lingma on the roadmap).",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"license": "MIT",
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -295,146 +295,26 @@ For refactor work, the coverage ≥ 95% gate in `Refactor hard gates` still appl
|
|
|
295
295
|
|
|
296
296
|
You cannot declare a phase complete from memory. Each gate below is a `ls` or `grep` command you **MUST run** and whose output you **MUST see** before proceeding. If any file shows "No such file" or any command returns empty, the phase is incomplete.
|
|
297
297
|
|
|
298
|
-
|
|
299
|
-
>
|
|
300
|
-
> | Type | rd:implemented requires | rd:qa-handoff also requires |
|
|
301
|
-
> |---|---|---|
|
|
302
|
-
> | feature / refactor | `rd/tech-doc.md` | `rd/code-review.md` + `rd/security-review.md` + `rd/perf-baseline.md` (filled Results table, or `N/A — no perf surface` in Notes) + **`qa/test-cases/<rid>.md`** (added in slice 004; pre-drafted by the 4th sub-agent in the parallel fan-out) |
|
|
303
|
-
> | bugfix | `rd/bug-analysis.md` (lighter than tech-doc; root cause + fix + regression test plan) | `rd/code-review.md` + `rd/security-review.md` + **`qa/test-cases/<rid>.md`**; `rd/perf-baseline.md` only when the bug is performance-shaped (matches the L449-452 "When this applies" criteria) |
|
|
304
|
-
> | config | (none) | `rd/security-review.md` only |
|
|
305
|
-
> | docs / chore | (none) | (none) |
|
|
306
|
-
>
|
|
307
|
-
> The escape hatch `--allow-incomplete --reason "<text>"` still exists for one-off exceptions; the bypass is recorded in the artifact transition note.
|
|
308
|
-
|
|
309
|
-
**Peaks-Cli Gate A — After project-scan read (before any implementation):**
|
|
310
|
-
```bash
|
|
311
|
-
ls .peaks/<changeId>/rd/project-scan.md
|
|
312
|
-
# Expected output: .peaks/<changeId>/rd/project-scan.md
|
|
313
|
-
# "No such file" → STOP, create the project-scan first. Do not write code.
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
**Peaks-Cli Gate A2 — Before tech-doc write: project structure verified (PATH CORRECTNESS — CRITICAL):**
|
|
317
|
-
```bash
|
|
318
|
-
# Verify EVERY file path and directory in the tech-doc exists in the actual project.
|
|
319
|
-
# Do not assume paths. Do not guess directory structures. Open the files and verify.
|
|
320
|
-
# Example verification (adapt paths to the actual tech-doc):
|
|
321
|
-
ls <every-single-directory-path-in-tech-doc> 2>&1 | grep -c "No such file"
|
|
322
|
-
# Expected: 0 (zero "No such file" errors)
|
|
323
|
-
# Any "No such file" → WRONG PATH. Fix the tech-doc BEFORE writing another word.
|
|
324
|
-
# This gate exists because a tech-doc with wrong paths wastes QA time,
|
|
325
|
-
# breaks the implementation, and forces the user to correct the engineer.
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
**Peaks-Cli Gate A3 — Before implementation: project standards files exist (CLAUDE.md + .claude/rules/):**
|
|
329
|
-
```bash
|
|
330
|
-
ls CLAUDE.md .claude/rules/common/coding-style.md .claude/rules/common/code-review.md .claude/rules/common/security.md 2>&1 | grep -c "No such file"
|
|
331
|
-
# Expected: 0 (all four files exist)
|
|
332
|
-
# Any missing → BLOCKED. Run `peaks standards init --project .` to generate them FIRST.
|
|
333
|
-
# Do not write a single line of implementation code without standards files in place.
|
|
334
|
-
# Without CLAUDE.md and .claude/rules/, code review and security review triggers won't fire.
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
**Peaks-Cli Gate B — Before QA handoff:**
|
|
338
|
-
```bash
|
|
339
|
-
ls .peaks/<changeId>/rd/requests/<rid>.md \
|
|
340
|
-
.peaks/<changeId>/rd/tech-doc.md
|
|
341
|
-
# Both must exist. Missing either → BLOCKED, do not hand off to QA
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
**Peaks-Cli Gate B2 — Before QA handoff: unit tests exist and pass for the changed surface:**
|
|
345
|
-
```bash
|
|
346
|
-
# Run the project's test command against changed files. Record the output.
|
|
347
|
-
# Example (adapt to project test runner):
|
|
348
|
-
npx vitest run --changed --reporter=verbose 2>&1 | tail -20
|
|
349
|
-
# Expected: exit code 0, all changed-surface tests passing, coverage for new/changed code recorded
|
|
350
|
-
# Any failing test or zero tests for new code → BLOCKED. Write tests, then re-run.
|
|
351
|
-
#
|
|
352
|
-
# To run the FULL suite (slower; not the default for `peaks slice check`),
|
|
353
|
-
# drop `--changed` or use `npx vitest run --reporter=verbose`. The peaks-solo-test
|
|
354
|
-
# skill is the user-facing wrapper for the full suite; the slice check's
|
|
355
|
-
# `--run-tests` flag is the CLI opt-in.
|
|
356
|
-
```
|
|
298
|
+
The full per-gate contract — including the CLI enforcement table (per `--type` required files), the `ls` / `grep` shell snippets for Gate A, A2, A3, B, B2, B3, B4, B5, B6, B7, B8, B9, and the expected outcomes — lives in [`references/rd-transition-gates.md`](references/rd-transition-gates.md). Read that file before declaring a phase complete. The summary below is the index; the reference file is the contract.
|
|
357
299
|
|
|
358
|
-
**
|
|
359
|
-
```bash
|
|
360
|
-
ls .peaks/<changeId>/rd/code-review.md 2>&1
|
|
361
|
-
# Expected: .peaks/<changeId>/rd/code-review.md
|
|
362
|
-
# "No such file" → BLOCKED. Run code review (use code-reviewer agent or equivalent),
|
|
363
|
-
# record findings, fix CRITICAL/HIGH issues, then re-check.
|
|
364
|
-
```
|
|
300
|
+
> **CLI enforcement**: the gates below are ALSO enforced by `peaks request transition`. The CLI checks the same files before allowing the transition and fails with `code: PREREQUISITES_MISSING` if any are absent. Per-type required files: see the table at the top of `references/rd-transition-gates.md`. The escape hatch `--allow-incomplete --reason "<text>"` still exists for one-off exceptions.
|
|
365
301
|
|
|
366
|
-
**
|
|
367
|
-
```bash
|
|
368
|
-
ls .peaks/<changeId>/rd/security-review.md 2>&1
|
|
369
|
-
# Expected: .peaks/<changeId>/rd/security-review.md
|
|
370
|
-
# "No such file" → BLOCKED. Run security review (use security-reviewer agent or equivalent),
|
|
371
|
-
# fix CRITICAL/HIGH issues, record findings, then re-check.
|
|
372
|
-
```
|
|
302
|
+
**Index of gates** (full contract in `references/rd-transition-gates.md`):
|
|
373
303
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
peaks scan request-type-sanity
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
# (`peaks request init` with the corrected --type) or trim the scope.
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
**Peaks-Cli Gate B7 — Repair cycle cap (only relevant during RD↔QA repair loop):**
|
|
392
|
-
```bash
|
|
393
|
-
peaks request repair-status <rid> --project <repo> --session-id <session-id> --json
|
|
394
|
-
# Expected: atCap=false. exit 0.
|
|
395
|
-
# atCap=true → BLOCKED. Three repair cycles already attempted; emit a blocked TXT
|
|
396
|
-
# handoff via Solo rather than entering a fourth cycle.
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
**Peaks-Cli Gate B8 — Diff stays inside the declared red-line scope:**
|
|
400
|
-
```bash
|
|
401
|
-
peaks scan diff-vs-scope --rid <rid> --project <repo> --session-id <session-id> --json
|
|
402
|
-
# Expected: ok=true. exit 0.
|
|
403
|
-
# violations[] non-empty → BLOCKED. A changed file matches an explicit out-of-scope
|
|
404
|
-
# pattern. Revert it, or — only with PRD approval — expand the RD red-line scope.
|
|
405
|
-
# unclassified[] non-empty → BLOCKED. A changed file does not match any declared
|
|
406
|
-
# in-scope pattern. Either add it to the in-scope list (intentional widening, requires
|
|
407
|
-
# PRD approval) or revert the change.
|
|
408
|
-
# patternsDeclared=false → BLOCKED. The RD artifact's `## Red-line scope` section has
|
|
409
|
-
# no concrete path or glob patterns. Fill it in with paths like `src/services/login/**`
|
|
410
|
-
# before re-running. Auto-allowed paths (test files, .peaks/, __mocks__/) never need a pattern.
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**Peaks-Cli Gate B9 — RD-side perf-baseline output present (when slice has a user-perceivable perf surface):**
|
|
414
|
-
```bash
|
|
415
|
-
ls .peaks/<changeId>/rd/perf-baseline.md 2>&1
|
|
416
|
-
# Expected: .peaks/<changeId>/rd/perf-baseline.md
|
|
417
|
-
# "No such file" + slice is feature / refactor / bugfix-when-perf → BLOCKED.
|
|
418
|
-
# Run the perf-baseline sub-agent from "Parallel review fan-out" below (or
|
|
419
|
-
# `peaks perf baseline --apply` inline), then fill in the Results table
|
|
420
|
-
# with measurements (lighthouse / k6 / autocannon / project-local bench —
|
|
421
|
-
# the CLI does not run these; that is the RD's job), then re-verify.
|
|
422
|
-
# "No such file" + slice is docs / chore / pure-bugfix-no-perf → OK to proceed;
|
|
423
|
-
# this gate does not apply to those slice types.
|
|
424
|
-
# File exists but Results table is empty (only the header row, no data rows) →
|
|
425
|
-
# BLOCKED. The sub-agent scaffolds the file; the main RD loop must fill in
|
|
426
|
-
# the Path / route | Workload | Tool | Metric | Baseline | Threshold table
|
|
427
|
-
# with actual numbers before handoff.
|
|
428
|
-
# File contains the marker `N/A — no perf surface` in its Notes section →
|
|
429
|
-
# OK to proceed. This is the explicit opt-out the sub-agent writes when
|
|
430
|
-
# the slice has no user-perceivable perf surface (e.g. a feature that only
|
|
431
|
-
# adds an internal flag with no runtime cost, or a refactor that does not
|
|
432
|
-
# alter any hot path).
|
|
433
|
-
#
|
|
434
|
-
# The CLI enforcement table below the section header also gates this at the
|
|
435
|
-
# `peaks request transition rd:qa-handoff` call, so a missing or empty file
|
|
436
|
-
# is rejected by the CLI with `code: PREREQUISITES_MISSING`.
|
|
437
|
-
```
|
|
304
|
+
| Gate | When | File / command | Why |
|
|
305
|
+
|---|---|---|---|
|
|
306
|
+
| A | After project-scan read, before any implementation | `ls .peaks/<changeId>/rd/project-scan.md` | Confirms the project-scan exists before code edits |
|
|
307
|
+
| A2 | Before tech-doc write | `ls <every-path-in-tech-doc> \| grep -c "No such file"` | Catches wrong paths in the tech-doc |
|
|
308
|
+
| A3 | Before implementation | `ls CLAUDE.md .claude/rules/...` | Confirms project standards exist |
|
|
309
|
+
| B | Before QA handoff | `ls rd/requests/<rid>.md rd/tech-doc.md` | Both files must exist |
|
|
310
|
+
| B2 | Before QA handoff | `npx vitest run --changed` | Unit tests pass on the changed surface |
|
|
311
|
+
| B3 | Before QA handoff | `ls rd/code-review.md` | Code review evidence exists |
|
|
312
|
+
| B4 | Before QA handoff | `ls rd/security-review.md` | Security review evidence exists |
|
|
313
|
+
| B5 | Before QA handoff | `peaks request lint` | RD artifact has no unfilled placeholders |
|
|
314
|
+
| B6 | Before QA handoff | `peaks scan request-type-sanity` | Declared `--type` matches the diff |
|
|
315
|
+
| B7 | Before QA handoff (repair only) | `peaks request repair-status` | Cycle count under the 3-cycle cap |
|
|
316
|
+
| B8 | Before QA handoff | `peaks scan diff-vs-scope` | Diff stays in red-line scope |
|
|
317
|
+
| B9 | Before QA handoff (perf surface) | `ls rd/perf-baseline.md` | Perf baseline filled (or `N/A — no perf surface`) |
|
|
438
318
|
|
|
439
319
|
## Project standards preflight
|
|
440
320
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Peaks-Cli RD transition verification gates
|
|
2
|
+
|
|
3
|
+
> Extracted from `skills/peaks-rd/SKILL.md` on 2026-06-09 (slice 019 — slim skill files to references) to keep SKILL.md under the 800-line cap from `common/coding-style.md`. The content below is the verbatim "Transition verification gates" section that was previously inline; nothing was paraphrased, just relocated.
|
|
4
|
+
|
|
5
|
+
## Transition verification gates (MANDATORY — run the command, see the output)
|
|
6
|
+
|
|
7
|
+
You cannot declare a phase complete from memory. Each gate below is a `ls` or `grep` command you **MUST run** and whose output you **MUST see** before proceeding. If any file shows "No such file" or any command returns empty, the phase is incomplete.
|
|
8
|
+
|
|
9
|
+
> **CLI enforcement (NEW)**: the gates below are now ALSO enforced by `peaks request transition`. The CLI checks the same files before allowing the transition and fails with `code: PREREQUISITES_MISSING` if any are absent. The exact required files depend on the request type chosen at `peaks request init --type <feature|bugfix|refactor|docs|config|chore>` (default `feature`):
|
|
10
|
+
>
|
|
11
|
+
> | Type | rd:implemented requires | rd:qa-handoff also requires |
|
|
12
|
+
> |---|---|---|
|
|
13
|
+
> | feature / refactor | `rd/tech-doc.md` | `rd/code-review.md` + `rd/security-review.md` + `rd/perf-baseline.md` (filled Results table, or `N/A — no perf surface` in Notes) + **`qa/test-cases/<rid>.md`** (added in slice 004; pre-drafted by the 4th sub-agent in the parallel fan-out) |
|
|
14
|
+
> | bugfix | `rd/bug-analysis.md` (lighter than tech-doc; root cause + fix + regression test plan) | `rd/code-review.md` + `rd/security-review.md` + **`qa/test-cases/<rid>.md`**; `rd/perf-baseline.md` only when the bug is performance-shaped (matches the L449-452 "When this applies" criteria) |
|
|
15
|
+
> | config | (none) | `rd/security-review.md` only |
|
|
16
|
+
> | docs / chore | (none) | (none) |
|
|
17
|
+
>
|
|
18
|
+
> The escape hatch `--allow-incomplete --reason "<text>"` still exists for one-off exceptions; the bypass is recorded in the artifact transition note.
|
|
19
|
+
|
|
20
|
+
**Peaks-Cli Gate A — After project-scan read (before any implementation):**
|
|
21
|
+
```bash
|
|
22
|
+
ls .peaks/<changeId>/rd/project-scan.md
|
|
23
|
+
# Expected output: .peaks/<changeId>/rd/project-scan.md
|
|
24
|
+
# "No such file" → STOP, create the project-scan first. Do not write code.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Peaks-Cli Gate A2 — Before tech-doc write: project structure verified (PATH CORRECTNESS — CRITICAL):**
|
|
28
|
+
```bash
|
|
29
|
+
# Verify EVERY file path and directory in the tech-doc exists in the actual project.
|
|
30
|
+
# Do not assume paths. Do not guess directory structures. Open the files and verify.
|
|
31
|
+
# Example verification (adapt paths to the actual tech-doc):
|
|
32
|
+
ls <every-single-directory-path-in-tech-doc> 2>&1 | grep -c "No such file"
|
|
33
|
+
# Expected: 0 (zero "No such file" errors)
|
|
34
|
+
# Any "No such file" → WRONG PATH. Fix the tech-doc BEFORE writing another word.
|
|
35
|
+
# This gate exists because a tech-doc with wrong paths wastes QA time,
|
|
36
|
+
# breaks the implementation, and forces the user to correct the engineer.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Peaks-Cli Gate A3 — Before implementation: project standards files exist (CLAUDE.md + .claude/rules/):**
|
|
40
|
+
```bash
|
|
41
|
+
ls CLAUDE.md .claude/rules/common/coding-style.md .claude/rules/common/code-review.md .claude/rules/common/security.md 2>&1 | grep -c "No such file"
|
|
42
|
+
# Expected: 0 (all four files exist)
|
|
43
|
+
# Any missing → BLOCKED. Run `peaks standards init --project .` to generate them FIRST.
|
|
44
|
+
# Do not write a single line of implementation code without standards files in place.
|
|
45
|
+
# Without CLAUDE.md and .claude/rules/, code review and security review triggers won't fire.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Peaks-Cli Gate B — Before QA handoff:**
|
|
49
|
+
```bash
|
|
50
|
+
ls .peaks/<changeId>/rd/requests/<rid>.md \
|
|
51
|
+
.peaks/<changeId>/rd/tech-doc.md
|
|
52
|
+
# Both must exist. Missing either → BLOCKED, do not hand off to QA
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Peaks-Cli Gate B2 — Before QA handoff: unit tests exist and pass for the changed surface:**
|
|
56
|
+
```bash
|
|
57
|
+
# Run the project's test command against changed files. Record the output.
|
|
58
|
+
# Example (adapt to project test runner):
|
|
59
|
+
npx vitest run --changed --reporter=verbose 2>&1 | tail -20
|
|
60
|
+
# Expected: exit code 0, all changed-surface tests passing, coverage for new/changed code recorded
|
|
61
|
+
# Any failing test or zero tests for new code → BLOCKED. Write tests, then re-run.
|
|
62
|
+
#
|
|
63
|
+
# To run the FULL suite (slower; not the default for `peaks slice check`),
|
|
64
|
+
# drop `--changed` or use `npx vitest run --reporter=verbose`. The peaks-solo-test
|
|
65
|
+
# skill is the user-facing wrapper for the full suite; the slice check's
|
|
66
|
+
# `--run-tests` flag is the CLI opt-in.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Peaks-Cli Gate B3 — Before QA handoff: code review evidence exists:**
|
|
70
|
+
```bash
|
|
71
|
+
ls .peaks/<changeId>/rd/code-review.md 2>&1
|
|
72
|
+
# Expected: .peaks/<changeId>/rd/code-review.md
|
|
73
|
+
# "No such file" → BLOCKED. Run code review (use code-reviewer agent or equivalent),
|
|
74
|
+
# record findings, fix CRITICAL/HIGH issues, then re-check.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Peaks-Cli Gate B4 — Before QA handoff: security review evidence exists:**
|
|
78
|
+
```bash
|
|
79
|
+
ls .peaks/<changeId>/rd/security-review.md 2>&1
|
|
80
|
+
# Expected: .peaks/<changeId>/rd/security-review.md
|
|
81
|
+
# "No such file" → BLOCKED. Run security review (use security-reviewer agent or equivalent),
|
|
82
|
+
# fix CRITICAL/HIGH issues, record findings, then re-check.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Peaks-Cli Gate B5 — RD artifact body has no unfilled placeholders:**
|
|
86
|
+
```bash
|
|
87
|
+
peaks request lint <rid> --role rd --project <repo> --session-id <session-id> --json
|
|
88
|
+
# Expected: ok=true. exit 0.
|
|
89
|
+
# ok=false → BLOCKED. The lint output lists every <placeholder>, "- ..." stub,
|
|
90
|
+
# and TBD/TODO marker with line numbers. Fill them in before attempting handoff.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Peaks-Cli Gate B6 — Declared --type matches the actual diff:**
|
|
94
|
+
```bash
|
|
95
|
+
peaks scan request-type-sanity --project <repo> --type <type> --json
|
|
96
|
+
# Expected: consistent=true. exit 0.
|
|
97
|
+
# consistent=false → BLOCKED. Either the implementation scope-creeped beyond what
|
|
98
|
+
# the declared type covers, or the type was mis-classified at PRD time. Re-classify
|
|
99
|
+
# (`peaks request init` with the corrected --type) or trim the scope.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Peaks-Cli Gate B7 — Repair cycle cap (only relevant during RD↔QA repair loop):**
|
|
103
|
+
```bash
|
|
104
|
+
peaks request repair-status <rid> --project <repo> --session-id <session-id> --json
|
|
105
|
+
# Expected: atCap=false. exit 0.
|
|
106
|
+
# atCap=true → BLOCKED. Three repair cycles already attempted; emit a blocked TXT
|
|
107
|
+
# handoff via Solo rather than entering a fourth cycle.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Peaks-Cli Gate B8 — Diff stays inside the declared red-line scope:**
|
|
111
|
+
```bash
|
|
112
|
+
peaks scan diff-vs-scope --rid <rid> --project <repo> --session-id <session-id> --json
|
|
113
|
+
# Expected: ok=true. exit 0.
|
|
114
|
+
# violations[] non-empty → BLOCKED. A changed file matches an explicit out-of-scope
|
|
115
|
+
# pattern. Revert it, or — only with PRD approval — expand the RD red-line scope.
|
|
116
|
+
# unclassified[] non-empty → BLOCKED. A changed file does not match any declared
|
|
117
|
+
# in-scope pattern. Either add it to the in-scope list (intentional widening, requires
|
|
118
|
+
# PRD approval) or revert the change.
|
|
119
|
+
# patternsDeclared=false → BLOCKED. The RD artifact's `## Red-line scope` section has
|
|
120
|
+
# no concrete path or glob patterns. Fill it in with paths like `src/services/login/**`
|
|
121
|
+
# before re-running. Auto-allowed paths (test files, .peaks/, __mocks__/) never need a pattern.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Peaks-Cli Gate B9 — RD-side perf-baseline output present (when slice has a user-perceivable perf surface):**
|
|
125
|
+
```bash
|
|
126
|
+
ls .peaks/<changeId>/rd/perf-baseline.md 2>&1
|
|
127
|
+
# Expected: .peaks/<changeId>/rd/perf-baseline.md
|
|
128
|
+
# "No such file" + slice is feature / refactor / bugfix-when-perf → BLOCKED.
|
|
129
|
+
# Run the perf-baseline sub-agent from "Parallel review fan-out" below (or
|
|
130
|
+
# `peaks perf baseline --apply` inline), then fill in the Results table
|
|
131
|
+
# with measurements (lighthouse / k6 / autocannon / project-local bench —
|
|
132
|
+
# the CLI does not run these; that is the RD's job), then re-verify.
|
|
133
|
+
# "No such file" + slice is docs / chore / pure-bugfix-no-perf → OK to proceed;
|
|
134
|
+
# this gate does not apply to those slice types.
|
|
135
|
+
# File exists but Results table is empty (only the header row, no data rows) →
|
|
136
|
+
# BLOCKED. The sub-agent scaffolds the file; the main RD loop must fill in
|
|
137
|
+
# the Path / route | Workload | Tool | Metric | Baseline | Threshold table
|
|
138
|
+
# with actual numbers before handoff.
|
|
139
|
+
# File contains the marker `N/A — no perf surface` in its Notes section →
|
|
140
|
+
# OK to proceed. This is the explicit opt-out the sub-agent writes when
|
|
141
|
+
# the slice has no user-perceivable perf surface (e.g. a feature that only
|
|
142
|
+
# adds an internal flag with no runtime cost, or a refactor that does not
|
|
143
|
+
# alter any hot path).
|
|
144
|
+
#
|
|
145
|
+
# The CLI enforcement table below the section header also gates this at the
|
|
146
|
+
# `peaks request transition rd:qa-handoff` call, so a missing or empty file
|
|
147
|
+
# is rejected by the CLI with `code: PREREQUISITES_MISSING`.
|
|
148
|
+
```
|