peaks-cli 1.2.4 → 1.2.5

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.
@@ -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
- return { sessionId: options.sessionId, sessionRoot, created, alreadyExisted };
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.4";
1
+ export declare const CLI_VERSION = "1.2.5";
@@ -1 +1 @@
1
- export const CLI_VERSION = "1.2.4";
1
+ export const CLI_VERSION = "1.2.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-cli",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Peaks CLI and short skill family for Claude Code automation.",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -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:
@@ -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
- ## Skill presence (MANDATORY first action)
10
+ ## Hard contracts for browser validation (BLOCKING read before any browser_take_screenshot / login flow)
11
11
 
12
- Before any analysis or tool call, immediately run:
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
@@ -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
- ## Skill presence (MANDATORY first action)
10
+ ## Hard contracts for browser self-test (BLOCKING read before any browser_take_screenshot / login flow)
11
11
 
12
- Before any analysis or tool call, immediately run:
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