peaks-cli 1.2.4 → 1.2.6
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/bin/peaks.js +0 -0
- package/dist/src/cli/commands/openspec-commands.js +31 -0
- package/dist/src/cli/commands/workspace-commands.js +50 -4
- package/dist/src/services/config/config-safety.d.ts +26 -0
- package/dist/src/services/config/config-safety.js +76 -0
- package/dist/src/services/config/config-service.d.ts +1 -1
- package/dist/src/services/config/config-service.js +2 -2
- package/dist/src/services/memory/project-memory-service.d.ts +18 -0
- package/dist/src/services/memory/project-memory-service.js +131 -13
- package/dist/src/services/openspec/openspec-init-service.d.ts +23 -0
- package/dist/src/services/openspec/openspec-init-service.js +122 -0
- package/dist/src/services/session/index.d.ts +1 -1
- package/dist/src/services/session/index.js +1 -1
- package/dist/src/services/session/session-manager.d.ts +11 -0
- package/dist/src/services/session/session-manager.js +19 -0
- package/dist/src/services/skills/skill-presence-service.js +11 -0
- package/dist/src/services/workspace/workspace-service.d.ts +15 -0
- package/dist/src/services/workspace/workspace-service.js +60 -1
- 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-prd/SKILL.md +36 -0
- package/skills/peaks-qa/SKILL.md +91 -2
- package/skills/peaks-rd/SKILL.md +69 -2
- package/skills/peaks-solo/SKILL.md +249 -41
- package/skills/peaks-solo/references/a2a-artifact-mapping.md +115 -0
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +186 -0
- package/skills/peaks-txt/SKILL.md +16 -0
- package/skills/peaks-ui/SKILL.md +61 -2
|
@@ -61,6 +61,25 @@ function writeSessionFile(projectRoot, info) {
|
|
|
61
61
|
}
|
|
62
62
|
writeFileSync(sessionFile, JSON.stringify(info, null, 2), 'utf8');
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Bind the project's current session to the given session id by writing
|
|
66
|
+
* `.peaks/.session.json`. The single-session binding is the source of truth
|
|
67
|
+
* for `ensureSession()` and any other path that needs to discover the
|
|
68
|
+
* active session without an explicit --session-id flag.
|
|
69
|
+
*
|
|
70
|
+
* This does NOT touch the per-session `session.json` inside `.peaks/<id>/`;
|
|
71
|
+
* that file is owned by `setSessionMeta` and records session-scoped
|
|
72
|
+
* metadata (title, skill, mode, gate, etc.).
|
|
73
|
+
*/
|
|
74
|
+
export function setCurrentSessionBinding(projectRoot, sessionId) {
|
|
75
|
+
const info = {
|
|
76
|
+
sessionId,
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
projectRoot
|
|
79
|
+
};
|
|
80
|
+
writeSessionFile(projectRoot, info);
|
|
81
|
+
return info;
|
|
82
|
+
}
|
|
64
83
|
function getMetaFilePath(projectRoot, sessionId) {
|
|
65
84
|
return join(projectRoot, '.peaks', sessionId, META_FILE);
|
|
66
85
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import { findProjectRoot } from '../config/config-safety.js';
|
|
4
|
+
import { ensureMemoryBootstrap } from '../memory/project-memory-service.js';
|
|
4
5
|
export const VALID_SKILL_PRESENCE_MODES = [
|
|
5
6
|
'full-auto',
|
|
6
7
|
'assisted',
|
|
@@ -67,6 +68,16 @@ export function setSkillPresence(skill, mode, gate, projectRootOverride) {
|
|
|
67
68
|
mkdirSync(presenceDir, { recursive: true });
|
|
68
69
|
}
|
|
69
70
|
writeFileSync(presencePath, JSON.stringify(presence, null, 2), 'utf8');
|
|
71
|
+
// Skill-activation side effect: ensure `.peaks/memory/` and a full-shape
|
|
72
|
+
// empty `index.json` exist for the project. This is the user-facing fix
|
|
73
|
+
// for "stock projects never get a memory directory or index". Every peaks
|
|
74
|
+
// skill starts with `peaks skill presence:set peaks-<role>`, so doing the
|
|
75
|
+
// bootstrap here means the very first skill invocation in a fresh project
|
|
76
|
+
// (or in a stock project that pre-dates the memory layer) brings the
|
|
77
|
+
// memory store into existence. The helper is fail-open, so a failure here
|
|
78
|
+
// does not block presence from being written.
|
|
79
|
+
const projectRoot = resolveProjectRoot(projectRootOverride);
|
|
80
|
+
ensureMemoryBootstrap(projectRoot);
|
|
70
81
|
return presence;
|
|
71
82
|
}
|
|
72
83
|
export function getSkillPresence(projectRootOverride) {
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
export type WorkspaceInitOptions = {
|
|
2
2
|
projectRoot: string;
|
|
3
3
|
sessionId: string;
|
|
4
|
+
/**
|
|
5
|
+
* When true, the conflict check is skipped and the new session id is
|
|
6
|
+
* written to .peaks/.session.json even if the project is already bound
|
|
7
|
+
* to a different (real) session. Use only with explicit user authorization
|
|
8
|
+
* — the CLI surfaces this as `--allow-session-rebind`.
|
|
9
|
+
*/
|
|
10
|
+
allowSessionRebind?: boolean;
|
|
4
11
|
};
|
|
5
12
|
export type WorkspaceInitReport = {
|
|
6
13
|
sessionId: string;
|
|
7
14
|
sessionRoot: string;
|
|
8
15
|
created: string[];
|
|
9
16
|
alreadyExisted: string[];
|
|
17
|
+
bound: boolean;
|
|
18
|
+
previousSessionId: string | null;
|
|
10
19
|
};
|
|
11
20
|
export declare class InvalidSessionIdError extends Error {
|
|
12
21
|
readonly code = "INVALID_SESSION_ID";
|
|
13
22
|
constructor(message: string);
|
|
14
23
|
}
|
|
24
|
+
export declare class ConflictingSessionError extends Error {
|
|
25
|
+
readonly existingSessionId: string;
|
|
26
|
+
readonly requestedSessionId: string;
|
|
27
|
+
readonly code = "CONFLICTING_SESSION";
|
|
28
|
+
constructor(message: string, existingSessionId: string, requestedSessionId: string);
|
|
29
|
+
}
|
|
15
30
|
export declare function validateSessionId(sessionId: string): void;
|
|
16
31
|
export declare function initWorkspace(options: WorkspaceInitOptions): Promise<WorkspaceInitReport>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { isDirectory } from '../../shared/fs.js';
|
|
4
|
+
import { getSessionId, setCurrentSessionBinding } from '../session/session-manager.js';
|
|
4
5
|
const SUBDIRECTORIES = [
|
|
5
6
|
'prd/source',
|
|
6
7
|
'prd/requests',
|
|
@@ -9,6 +10,7 @@ const SUBDIRECTORIES = [
|
|
|
9
10
|
'qa/test-cases',
|
|
10
11
|
'qa/test-reports',
|
|
11
12
|
'qa/requests',
|
|
13
|
+
'qa/screenshots',
|
|
12
14
|
'sc',
|
|
13
15
|
'txt',
|
|
14
16
|
'system'
|
|
@@ -24,6 +26,17 @@ export class InvalidSessionIdError extends Error {
|
|
|
24
26
|
this.name = 'InvalidSessionIdError';
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
export class ConflictingSessionError extends Error {
|
|
30
|
+
existingSessionId;
|
|
31
|
+
requestedSessionId;
|
|
32
|
+
code = 'CONFLICTING_SESSION';
|
|
33
|
+
constructor(message, existingSessionId, requestedSessionId) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.existingSessionId = existingSessionId;
|
|
36
|
+
this.requestedSessionId = requestedSessionId;
|
|
37
|
+
this.name = 'ConflictingSessionError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
export function validateSessionId(sessionId) {
|
|
28
41
|
// Auto-generated session IDs (YYYY-MM-DD-session-<hex>) bypass manual validation
|
|
29
42
|
if (AUTO_SESSION_PATTERN.test(sessionId)) {
|
|
@@ -68,5 +81,51 @@ export async function initWorkspace(options) {
|
|
|
68
81
|
created.push(sub);
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
|
-
|
|
84
|
+
// Bind this session as the project's current one.
|
|
85
|
+
//
|
|
86
|
+
// Single source of truth: `peaks workspace init` is the only CLI entry point
|
|
87
|
+
// that takes an explicit --session-id, so it owns the binding to .session.json.
|
|
88
|
+
// Without this write, downstream commands that fall through to
|
|
89
|
+
// `ensureSession()` would auto-generate a *different* id and create a second
|
|
90
|
+
// session directory — the bug that confuses the LLM in peaks-solo.
|
|
91
|
+
//
|
|
92
|
+
// Conflict rule: if .session.json already points at a different session
|
|
93
|
+
// whose directory is real (has session.json inside), the caller is starting
|
|
94
|
+
// a parallel session without closing the previous one. Refuse to bind —
|
|
95
|
+
// this is the "strict" mode the user picked. The user must finish or delete
|
|
96
|
+
// the existing session first.
|
|
97
|
+
const existingSessionId = getSessionId(options.projectRoot);
|
|
98
|
+
let previousSessionId = null;
|
|
99
|
+
let bound = false;
|
|
100
|
+
if (existingSessionId === null) {
|
|
101
|
+
// No prior binding — adopt the requested id.
|
|
102
|
+
setCurrentSessionBinding(options.projectRoot, options.sessionId);
|
|
103
|
+
bound = true;
|
|
104
|
+
}
|
|
105
|
+
else if (existingSessionId === options.sessionId) {
|
|
106
|
+
// Already bound to the same id — idempotent.
|
|
107
|
+
bound = true;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Different id already bound. The existing session is "real" if its
|
|
111
|
+
// directory is non-empty — that holds the user's data (rd/, qa/, ui/,
|
|
112
|
+
// etc.) regardless of whether the per-session metadata file is present.
|
|
113
|
+
// Refuse to rebind without explicit authorization.
|
|
114
|
+
previousSessionId = existingSessionId;
|
|
115
|
+
const existingSessionDir = join(options.projectRoot, '.peaks', existingSessionId);
|
|
116
|
+
if (await isDirectory(existingSessionDir) && !options.allowSessionRebind) {
|
|
117
|
+
const { readdirSync } = await import('node:fs');
|
|
118
|
+
const entries = readdirSync(existingSessionDir);
|
|
119
|
+
if (entries.length > 0) {
|
|
120
|
+
throw new ConflictingSessionError(`Project is already bound to session "${existingSessionId}". ` +
|
|
121
|
+
`Cannot start session "${options.sessionId}" without closing the previous one. ` +
|
|
122
|
+
`Either finish/abandon the prior session first, or pass --allow-session-rebind to override.`, existingSessionId, options.sessionId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Either: existing session dir is empty (true leftover, no user data),
|
|
126
|
+
// or the caller explicitly authorised a rebind. Overwrite.
|
|
127
|
+
setCurrentSessionBinding(options.projectRoot, options.sessionId);
|
|
128
|
+
bound = true;
|
|
129
|
+
}
|
|
130
|
+
return { sessionId: options.sessionId, sessionRoot, created, alreadyExisted, bound, previousSessionId };
|
|
72
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.2.
|
|
1
|
+
export declare const CLI_VERSION = "1.2.6";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.2.
|
|
1
|
+
export const CLI_VERSION = "1.2.6";
|
package/package.json
CHANGED
|
@@ -7,6 +7,42 @@ description: Product and requirement skill for Peaks. Use when a workflow needs
|
|
|
7
7
|
|
|
8
8
|
Peaks-Cli PRD turns user intent into verifiable product artifacts.
|
|
9
9
|
|
|
10
|
+
## Hard contracts for PRD source-document screenshots (BLOCKING)
|
|
11
|
+
|
|
12
|
+
When the PRD source is an authenticated web document (Feishu / Lark / Notion / Confluence / GitHub / any site that demands a login before the document body is reachable), PRD uses the Playwright MCP headed browser to render it. The two contracts are the same as in `peaks-qa` and `peaks-rd`; the role differs.
|
|
13
|
+
|
|
14
|
+
### Contract 1 — Source-document screenshots must land under .peaks/<sid>/prd/source/
|
|
15
|
+
|
|
16
|
+
PRD's `mcp__playwright__browser_take_screenshot` calls MUST pass `filename` inside `.peaks/<session-id>/prd/source/`, not in the project root and not in `.peaks/<sid>/qa/screenshots/` (PRD's evidence is upstream of QA's). Example:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mcp__playwright__browser_take_screenshot \
|
|
20
|
+
filename=".peaks/<sid>/prd/source/<doc-name>-page-<n>.png" \
|
|
21
|
+
fullPage=true
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After the navigation / snapshot batch, run `find . -maxdepth 1 -name '*.png'` and verify the project root is clean. Sanitise before retention: no login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots containing PII or SSO/MFA material.
|
|
25
|
+
|
|
26
|
+
### Contract 2 — Login / CAPTCHA / SSO / MFA wall is a hard block, not a skip
|
|
27
|
+
|
|
28
|
+
If `browser_navigate` redirects to a login / captcha / SSO / MFA, PRD does NOT silently fall back to unauthenticated `fetch` or `WebFetch`. The visible browser is already open; the skill must surface the wall with `AskUserQuestion`:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
AskUserQuestion({
|
|
32
|
+
question: "PRD source <URL> hit a login wall. How should PRD proceed?",
|
|
33
|
+
options: [
|
|
34
|
+
{ label: "I am logged in / I'll log in now",
|
|
35
|
+
description: "Pause PRD. The user completes login in the visible browser, then types 'logged in' or equivalent. PRD resumes browser_navigate + browser_snapshot from the post-login page." },
|
|
36
|
+
{ label: "Skip browser capture, paste the document",
|
|
37
|
+
description: "The user pastes the document content as Markdown / plain text into the chat or drops a .md / .pdf export into .peaks/<sid>/prd/source/. PRD ingests the paste / file. Sanitise cookies / PII / SSO before retention." },
|
|
38
|
+
{ label: "Mark PRD as blocked",
|
|
39
|
+
description: "Set the PRD state to blocked with reason doc-inaccessible. Do not fabricate facts from a partial read." }
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Do not infer login from DOM state. The full hard-block contract is defined in `peaks-qa`; PRD inherits the same rules.
|
|
45
|
+
|
|
10
46
|
## Skill presence (MANDATORY first action)
|
|
11
47
|
|
|
12
48
|
Before any analysis or tool call, immediately run:
|
package/skills/peaks-qa/SKILL.md
CHANGED
|
@@ -7,9 +7,98 @@ description: QA and verification skill for Peaks. Use when a workflow needs unit
|
|
|
7
7
|
|
|
8
8
|
Peaks-Cli QA proves that planned changes are protected and accepted.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Hard contracts for browser validation (BLOCKING — read before any browser_take_screenshot / login flow)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
These two contracts are non-negotiable. The previous prose-only phrasing let the LLM skip the browser gate entirely when an auth wall appeared, and let screenshots land in the project root because the LLM forgot to pass `filename`. Both fail modes are blocking violations; the rules below are what a reviewer should hold the skill to.
|
|
13
|
+
|
|
14
|
+
### Contract 1 — Screenshot path is mandatory and must land under .peaks/<sid>/qa/screenshots/
|
|
15
|
+
|
|
16
|
+
Every `mcp__playwright__browser_take_screenshot` call **MUST** pass `filename` whose absolute path is **inside** `.peaks/<session-id>/qa/screenshots/`. Concrete form:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mcp__playwright__browser_take_screenshot \
|
|
20
|
+
filename=".peaks/<sid>/qa/screenshots/<state-or-step>.png" \
|
|
21
|
+
fullPage=true
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The default behaviour of Playwright MCP when `filename` is omitted or points outside that directory is to write a screenshot to the current working directory, which leaves `.png` files scattered at the project root. **This is a workflow violation.** If a screenshot does land outside `.peaks/<sid>/qa/screenshots/` for any reason (e.g. an upstream tool wrote there), QA MUST move it into that directory before declaring the test report complete; do not commit project-root `.png` files. Sanitise before retention: no login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material.
|
|
25
|
+
|
|
26
|
+
This rule is enforced by a Peaks-Cli preflight check inside this skill:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# After every browser_take_screenshot batch and before declaring the test report complete:
|
|
30
|
+
ls .peaks/<sid>/qa/screenshots/*.png 2>&1
|
|
31
|
+
# Expected: at least one .png file under the screenshots directory.
|
|
32
|
+
# "No such file" → BLOCKED. Either the screenshot was never taken, or
|
|
33
|
+
# it landed in the project root (move it before continuing).
|
|
34
|
+
find . -maxdepth 1 -name '*.png' 2>&1
|
|
35
|
+
# Expected: empty. Any .png at the project root is a leak — move it
|
|
36
|
+
# to .peaks/<sid>/qa/screenshots/ before completing this skill.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Contract 2 — Login / CAPTCHA / SSO / MFA wall is a hard block, not a skip
|
|
40
|
+
|
|
41
|
+
When the headed browser hits a login wall (Feishu / Lark SSO, GitHub OAuth, custom captcha, MFA push, anything that needs the human), QA **MUST NOT** silently downgrade to static screenshots, manual steps, or any other tool. The skill must surface the wall to the user with `AskUserQuestion` and pick one of three paths:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
AskUserQuestion({
|
|
45
|
+
question: "Headed browser hit a login wall at <URL>. How should QA proceed?",
|
|
46
|
+
options: [
|
|
47
|
+
{ label: "I am logged in / I'll log in now",
|
|
48
|
+
description: "Pause QA. The visible browser is already open; the user completes login in-place, then types 'logged in' or equivalent. QA then resumes browser_navigate + browser_snapshot from the post-login page." },
|
|
49
|
+
{ label: "Skip browser validation for this slice",
|
|
50
|
+
description: "Mark the affected acceptance items as unverified in the test report. Do NOT issue a pass verdict. The slice stays in qa-running with the browser gate marked blocked, reason=login-required. peaks-solo's repair loop will surface this on the next cycle." },
|
|
51
|
+
{ label: "Cancel the workflow",
|
|
52
|
+
description: "Stop QA immediately. Emit a blocked TXT handoff so peaks-solo can surface the auth wall to the user. Do not mark any acceptance items as accepted." }
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Do **not** infer login completion from DOM state (presence of an avatar, a user-name span, etc.) — only the user's explicit confirmation counts. Do **not** route through Chrome DevTools MCP as a substitute for the headed browser; it does not launch a browser and cannot simulate user interaction.
|
|
58
|
+
|
|
59
|
+
This is the hard-block replacement for the previous "wait for the user" prose. Without an explicit decision from the user, QA does not advance past the wall.
|
|
60
|
+
|
|
61
|
+
## Sub-agent dispatch (when launched by peaks-solo swarm)
|
|
62
|
+
|
|
63
|
+
When this skill is launched as a sub-agent via `Task(subagent_type="general-purpose", ...)` from `peaks-solo`, the following sections of THIS skill are **suspended** for the sub-agent run:
|
|
64
|
+
|
|
65
|
+
- **Skill presence (MANDATORY first action)** — do NOT call `peaks skill presence:set peaks-qa`. The sub-agent must not overwrite `.peaks/.active-skill.json`; the main Solo loop owns that file. If you need to mark your own state, write a marker file at `.peaks/<session-id>/system/sub-agent-qa.json` and only that.
|
|
66
|
+
- **Workspace initialization** — Solo has already run `peaks workspace init` before fan-out. Do not re-run it.
|
|
67
|
+
- **Mode selection** — Solo has already chosen the mode.
|
|
68
|
+
- **Statusline install** — already done by Solo at session startup.
|
|
69
|
+
|
|
70
|
+
What the sub-agent **MUST** still do:
|
|
71
|
+
|
|
72
|
+
0. **Do NOT call `peaks request init`** — Solo has already initialised the request artefact slot in the main loop before fan-out. The sub-agent reads it via `peaks request show <rid> --role qa --project <repo> --json` if it needs to.
|
|
73
|
+
2. `peaks request show <rid> --role prd --project <repo> --json` (and `--role rd`, `--role ui` if UI is in the swarm plan).
|
|
74
|
+
3. Standards preflight (dry-run only).
|
|
75
|
+
4. Write `.peaks/<session-id>/qa/test-cases/<rid>.md` with test cases that link to PRD acceptance items.
|
|
76
|
+
5. Return only a compact JSON envelope:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"role": "qa-test-cases",
|
|
81
|
+
"rid": "<rid>",
|
|
82
|
+
"status": "ok" | "blocked" | "skipped",
|
|
83
|
+
"artefacts": [".peaks/<sid>/qa/test-cases/<rid>.md"],
|
|
84
|
+
"warnings": [],
|
|
85
|
+
"blockedReason": null
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Hard prohibitions** (sub-agent context):
|
|
90
|
+
|
|
91
|
+
- Do NOT call `Skill(skill="...")`.
|
|
92
|
+
- Do NOT call `peaks skill presence:set` — Solo owns the active-skill file.
|
|
93
|
+
- Do NOT run the actual test suite, do NOT execute security/perf tools, do NOT open a browser — those are the **QA validation** phase, not the Swarm planning phase. The Swarm sub-agent is "QA(test-cases)" (planning), which only produces the test-case artefact. The actual validation runs after RD implementation in a separate sub-agent or inline run.
|
|
94
|
+
- Do NOT commit, push, install hooks, or apply settings.json mutations.
|
|
95
|
+
- Do NOT ask the user interactive questions. If you need clarification, return `{"status":"blocked","blockedReason":"<text>"}`.
|
|
96
|
+
|
|
97
|
+
If `--type` is `docs` or `chore`, return `{"status":"skipped","reason":"type=<type>"}` and exit — there is no acceptance surface to plan tests for.
|
|
98
|
+
|
|
99
|
+
## Skill presence (MANDATORY first action — main-loop context only)
|
|
100
|
+
|
|
101
|
+
When this skill is running in the main Claude session (not as a sub-agent), before any analysis or tool call, immediately run:
|
|
13
102
|
|
|
14
103
|
```bash
|
|
15
104
|
peaks skill presence:set peaks-qa --project <repo> --mode <mode> --gate startup
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -7,9 +7,76 @@ description: Research and development skill for Peaks. Use for engineering analy
|
|
|
7
7
|
|
|
8
8
|
Peaks-Cli RD owns engineering analysis, implementation planning, and refactor execution contracts.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Hard contracts for browser self-test (BLOCKING — read before any browser_take_screenshot / login flow)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
For frontend or UI-affecting slices, RD's self-test uses the Playwright MCP headed browser to verify the implementation behaves correctly before handing off to QA. The two contracts below are identical in spirit to `peaks-qa`'s contracts — RD and QA share the same headed-browser path and the same evidence conventions; only the role differs.
|
|
13
|
+
|
|
14
|
+
### Contract 1 — Self-test screenshots must land under .peaks/<sid>/qa/screenshots/
|
|
15
|
+
|
|
16
|
+
Even though RD runs the self-test, **the screenshot evidence is QA's** by convention (the test report under `.peaks/<sid>/qa/test-reports/` cites these paths). Therefore RD's `mcp__playwright__browser_take_screenshot` calls MUST pass `filename` whose absolute path is inside `.peaks/<session-id>/qa/screenshots/`, exactly the same contract QA enforces. Do not let Playwright fall back to the project root.
|
|
17
|
+
|
|
18
|
+
### Contract 2 — Login / CAPTCHA / SSO / MFA wall is a hard block, not a skip
|
|
19
|
+
|
|
20
|
+
When the headed browser hits an auth wall, RD does **not** skip the browser gate. The skill must surface the wall with `AskUserQuestion` and pick one of three paths:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
AskUserQuestion({
|
|
24
|
+
question: "Headed browser hit a login wall at <URL>. How should RD self-test proceed?",
|
|
25
|
+
options: [
|
|
26
|
+
{ label: "I am logged in / I'll log in now",
|
|
27
|
+
description: "Pause RD. The visible browser is already open; the user completes login in-place, then types 'logged in' or equivalent. RD resumes browser_navigate + browser_snapshot from the post-login page." },
|
|
28
|
+
{ label: "Skip browser self-test, hand off to QA",
|
|
29
|
+
description: "Mark the slice's browser self-test as deferred. Do NOT mark the slice as RD-done; transition to qa-handoff with browser-gate=blocked reason=login-required, and let QA's gate machinery surface the wall to the user again." },
|
|
30
|
+
{ label: "Cancel the workflow",
|
|
31
|
+
description: "Stop RD. Emit a blocked TXT handoff so peaks-solo can surface the auth wall to the user. Do not modify code paths that the browser gate would have covered." }
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The full hard-block contract is defined in `peaks-qa` (see "Hard contracts for browser validation" there); RD inherits the same rules. Without an explicit decision from the user, RD does not advance past the wall.
|
|
37
|
+
|
|
38
|
+
## Sub-agent dispatch (when launched by peaks-solo swarm)
|
|
39
|
+
|
|
40
|
+
When this skill is launched as a sub-agent via `Task(subagent_type="general-purpose", ...)` from `peaks-solo`, the following sections of THIS skill are **suspended** for the sub-agent run:
|
|
41
|
+
|
|
42
|
+
- **Skill presence (MANDATORY first action)** — do NOT call `peaks skill presence:set peaks-rd`. The sub-agent must not overwrite `.peaks/.active-skill.json`; the main Solo loop owns that file. If you need to mark your own state, write a marker file at `.peaks/<session-id>/system/sub-agent-rd.json` and only that.
|
|
43
|
+
- **Workspace initialization** — Solo has already run `peaks workspace init` before fan-out. Do not re-run it.
|
|
44
|
+
- **Mode selection** — Solo has already chosen the mode. Read it from the prompt arguments (or from `.peaks/.active-skill.json` if you can, but do not write it).
|
|
45
|
+
- **Statusline install** — already done by Solo at session startup; do not re-run.
|
|
46
|
+
|
|
47
|
+
What the sub-agent **MUST** still do, from this skill's contract:
|
|
48
|
+
|
|
49
|
+
0. **Do NOT call `peaks request init`** — Solo has already initialised the request artefact slot in the main loop before fan-out (the runbook has the exact `peaks request init --role rd --id <rid> --project <repo> --apply --type <type> --json` call). The sub-agent reads the slot via `peaks request show <rid> --role rd --project <repo> --json` if it needs to.
|
|
50
|
+
2. `peaks request show <rid> --role prd --project <repo> --json` (and `--role ui` if UI is in the swarm plan).
|
|
51
|
+
3. Standards preflight (dry-run only; Solo owns the apply step).
|
|
52
|
+
4. Project-scan read; create `rd/project-scan.md` only if Solo flagged it missing in the dispatch prompt.
|
|
53
|
+
5. Write the planning artefact: `rd/tech-doc.md` (feature/refactor) or `rd/bug-analysis.md` (bugfix). If `--type` is `config|docs|chore`, **no planning artefact is required** — return immediately with `{"role":"rd-planning","status":"skipped","reason":"type=<type>"}`.
|
|
54
|
+
6. Return only a compact JSON envelope — Solo will run the convergence gate (`ls` checks):
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"role": "rd-planning",
|
|
59
|
+
"rid": "<rid>",
|
|
60
|
+
"status": "ok" | "blocked" | "skipped",
|
|
61
|
+
"artefacts": [".peaks/<sid>/rd/tech-doc.md"],
|
|
62
|
+
"warnings": [],
|
|
63
|
+
"blockedReason": null
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Hard prohibitions** (sub-agent context, in addition to general red lines):
|
|
68
|
+
|
|
69
|
+
- Do NOT call `Skill(skill="...")` from inside the sub-agent — that defeats the fan-out.
|
|
70
|
+
- Do NOT call `peaks skill presence:set` — Solo owns the active-skill file.
|
|
71
|
+
- Do NOT commit, push, install hooks, or apply settings.json mutations.
|
|
72
|
+
- Do NOT ask the user interactive questions. If you need clarification, return `{"status":"blocked","blockedReason":"<text>"}` and let Solo handle the user message.
|
|
73
|
+
- Do NOT modify code (the Swarm phase is planning only; code edits happen in the RD implementation phase, which is a separate sub-agent or inline run after Gate B).
|
|
74
|
+
|
|
75
|
+
After returning, Solo re-checks Gate B (`ls .peaks/<sid>/rd/tech-doc.md` etc.) and proceeds to RD implementation, which is a different sub-agent or inline run.
|
|
76
|
+
|
|
77
|
+
## Skill presence (MANDATORY first action — main-loop context only)
|
|
78
|
+
|
|
79
|
+
When this skill is running in the main Claude session (not as a sub-agent — i.e. user invoked `peaks-rd` directly, or `peaks-solo` is executing the role inline in assisted/strict mode), before any analysis or tool call, immediately run:
|
|
13
80
|
|
|
14
81
|
```bash
|
|
15
82
|
peaks skill presence:set peaks-rd --project <repo> --mode <mode> --gate startup
|