peaks-cli 1.0.27 → 1.1.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/bin/peaks.js CHANGED
File without changes
@@ -10,6 +10,7 @@ import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service
10
10
  import { setSkillPresence, clearSkillPresence, getSkillPresence, isSkillPresenceMode, touchSkillHeartbeat } from '../../services/skills/skill-presence-service.js';
11
11
  import { ensureSession, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas } from '../../services/session/session-manager.js';
12
12
  import { findProjectRoot } from '../../services/config/config-safety.js';
13
+ import { generateProjectContext } from '../../services/memory/project-context-service.js';
13
14
  import { fail, ok } from '../../shared/result.js';
14
15
  import { addJsonOption, failUnsupportedNonDryRun, getErrorMessage, isArtifactProvider, isArtifactSetupStep, printResult } from '../cli-helpers.js';
15
16
  export function registerCoreAndArtifactCommands(program, io) {
@@ -73,15 +74,16 @@ export function registerCoreAndArtifactCommands(program, io) {
73
74
  .command('presence:set <name>')
74
75
  .description('Set the currently active Peaks skill for session-wide visibility')
75
76
  .option('--mode <mode>', 'execution mode')
76
- .option('--gate <gate>', 'current gate')).action(async (name, options) => {
77
+ .option('--gate <gate>', 'current gate')
78
+ .option('--project <path>', 'project root path (auto-detected from cwd when omitted)')).action(async (name, options) => {
79
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
77
80
  if (options.mode !== undefined && !isSkillPresenceMode(options.mode)) {
78
81
  printResult(io, fail('skill.presence:set', 'INVALID_MODE', `Invalid mode: ${options.mode} (expected one of: full-auto, assisted, swarm, strict)`, { name, mode: options.mode }, ['Use a valid mode: full-auto, assisted, swarm, or strict']), options.json);
79
82
  process.exitCode = 1;
80
83
  return;
81
84
  }
82
- const presence = setSkillPresence(name, options.mode, options.gate);
85
+ const presence = setSkillPresence(name, options.mode, options.gate, options.project);
83
86
  // Also update session metadata so session dirs self-document
84
- const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
85
87
  const sessionId = await ensureSession(projectRoot);
86
88
  setSessionMeta(projectRoot, sessionId, {
87
89
  skill: name,
@@ -92,9 +94,18 @@ export function registerCoreAndArtifactCommands(program, io) {
92
94
  });
93
95
  addJsonOption(skill
94
96
  .command('presence:clear')
95
- .description('Clear the active Peaks skill presence indicator')).action((options) => {
96
- const removed = clearSkillPresence();
97
- printResult(io, ok('skill.presence:clear', { active: false, removed }), options.json);
97
+ .description('Clear the active Peaks skill presence indicator and update project context')
98
+ .option('--project <path>', 'project root path (auto-detected from cwd when omitted)')).action((options) => {
99
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
100
+ const removed = clearSkillPresence(options.project);
101
+ // Auto-update project context so future sessions have up-to-date history
102
+ try {
103
+ generateProjectContext(projectRoot);
104
+ }
105
+ catch {
106
+ // non-fatal: context update failure should not block presence clear
107
+ }
108
+ printResult(io, ok('skill.presence:clear', { active: false, removed, projectContextUpdated: true }), options.json);
98
109
  });
99
110
  addJsonOption(skill
100
111
  .command('heartbeat')
@@ -216,11 +227,11 @@ export function registerCoreAndArtifactCommands(program, io) {
216
227
  const memory = program.command('memory').description('Manage project-local Peaks memory');
217
228
  addJsonOption(memory
218
229
  .command('extract')
219
- .description('Extract stable project memory from skill artifacts into project .claude/memory')
230
+ .description('Extract stable project memory from skill artifacts into project .peaks/memory')
220
231
  .requiredOption('--project <path>', 'target project root')
221
232
  .requiredOption('--artifact <path...>', 'skill artifact paths inside the project')
222
233
  .option('--dry-run', 'preview writes without changing files', true)
223
- .option('--apply', 'write extracted memories into project .claude/memory')).action((options) => {
234
+ .option('--apply', 'write extracted memories into project .peaks/memory')).action((options) => {
224
235
  try {
225
236
  const result = executeProjectMemoryExtract({ projectRoot: options.project, artifactPaths: options.artifact, apply: options.apply === true });
226
237
  printResult(io, ok('memory.extract', summarizeProjectMemoryExtractResult(result)), options.json);
@@ -232,11 +243,11 @@ export function registerCoreAndArtifactCommands(program, io) {
232
243
  });
233
244
  addJsonOption(memory
234
245
  .command('sync')
235
- .description('Back up project .claude/memory into the artifact workspace')
246
+ .description('Back up project .peaks/memory into the artifact workspace')
236
247
  .requiredOption('--project <path>', 'target project root')
237
248
  .requiredOption('--workspace <path>', 'artifact workspace path')
238
249
  .option('--dry-run', 'preview copies without changing files', true)
239
- .option('--apply', 'copy project .claude/memory into artifact workspace backup')).action((options) => {
250
+ .option('--apply', 'copy project .peaks/memory into artifact workspace backup')).action((options) => {
240
251
  try {
241
252
  const result = executeProjectMemoryBackup({ projectRoot: options.project, artifactWorkspacePath: options.workspace, apply: options.apply === true });
242
253
  printResult(io, ok('memory.sync', summarizeProjectMemoryBackupResult(result)), options.json);
@@ -1,4 +1,6 @@
1
1
  import { loadProjectDashboard } from '../../services/dashboard/project-dashboard-service.js';
2
+ import { generateProjectContext, readProjectContext } from '../../services/memory/project-context-service.js';
3
+ import { readProjectMemories } from '../../services/memory/project-memory-service.js';
2
4
  import { fail, ok } from '../../shared/result.js';
3
5
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
4
6
  export function registerProjectCommands(program, io) {
@@ -39,4 +41,61 @@ export function registerProjectCommands(program, io) {
39
41
  process.exitCode = 1;
40
42
  }
41
43
  });
44
+ addJsonOption(project
45
+ .command('context')
46
+ .description('Generate or read persistent project context for cross-session Peaks understanding')
47
+ .requiredOption('--project <path>', 'target project root')
48
+ .option('--read', 'read existing PROJECT.md without regenerating')).action((options) => {
49
+ try {
50
+ if (options.read) {
51
+ const content = readProjectContext(options.project);
52
+ if (content === null) {
53
+ printResult(io, ok('project.context', { exists: false, path: `${options.project}/.peaks/PROJECT.md` }), options.json);
54
+ return;
55
+ }
56
+ printResult(io, ok('project.context', { exists: true, path: `${options.project}/.peaks/PROJECT.md`, content }), options.json);
57
+ return;
58
+ }
59
+ const result = generateProjectContext(options.project);
60
+ printResult(io, ok('project.context', {
61
+ path: result.path,
62
+ sessionCount: result.sessionCount,
63
+ content: result.content
64
+ }), options.json);
65
+ }
66
+ catch (error) {
67
+ printResult(io, fail('project.context', 'PROJECT_CONTEXT_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path and .peaks directory']), options.json);
68
+ process.exitCode = 1;
69
+ }
70
+ });
71
+ // --- Structured project memory (durable, LLM-authored, stored under .peaks/memory) ---
72
+ addJsonOption(project
73
+ .command('memories')
74
+ .description('Read durable project memories (decisions, conventions, modules, rules) from .peaks/memory for LLM consumption')
75
+ .requiredOption('--project <path>', 'target project root')
76
+ .option('--kind <kind>', 'filter by kind: project, rule, decision, reference, feedback, convention, module')).action((options) => {
77
+ try {
78
+ const result = readProjectMemories(options.project);
79
+ if (options.kind) {
80
+ const memories = result.memories.filter((memory) => memory.kind === options.kind);
81
+ printResult(io, ok('project.memories', {
82
+ memoryDir: result.memoryDir,
83
+ kind: options.kind,
84
+ total: memories.length,
85
+ memories
86
+ }), options.json);
87
+ return;
88
+ }
89
+ printResult(io, ok('project.memories', {
90
+ memoryDir: result.memoryDir,
91
+ total: result.total,
92
+ byKind: result.byKind,
93
+ memories: result.memories
94
+ }), options.json);
95
+ }
96
+ catch (error) {
97
+ printResult(io, fail('project.memories', 'PROJECT_MEMORIES_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path and .peaks/memory directory']), options.json);
98
+ process.exitCode = 1;
99
+ }
100
+ });
42
101
  }
@@ -0,0 +1,11 @@
1
+ export type ProjectContextSection = {
2
+ heading: string;
3
+ body: string;
4
+ };
5
+ export declare function generateProjectContext(projectRoot: string): {
6
+ path: string;
7
+ content: string;
8
+ sessionCount: number;
9
+ };
10
+ export declare function readProjectContext(projectRoot: string): string | null;
11
+ export declare function getProjectContextPath(projectRoot: string): string;
@@ -0,0 +1,147 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { listSessionMetas } from '../session/session-manager.js';
4
+ const PROJECT_CONTEXT_FILE = '.peaks/PROJECT.md';
5
+ const CONTEXT_HEADER = `# Peaks Project Context
6
+
7
+ > Auto-generated project memory. Peaks reads this at the start of each session to understand
8
+ > the project's history, tech stack, conventions, and past decisions.
9
+ > Last updated: `;
10
+ const MANAGED_BLOCK_START = '<!-- peaks-managed:session-history-start -->';
11
+ const MANAGED_BLOCK_END = '<!-- peaks-managed:session-history-end -->';
12
+ function projectName(projectRoot) {
13
+ const pkgPath = join(projectRoot, 'package.json');
14
+ if (!existsSync(pkgPath))
15
+ return projectRoot.split(/[\\/]/).pop() ?? 'unknown';
16
+ try {
17
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
18
+ return pkg.name ?? projectRoot.split(/[\\/]/).pop() ?? 'unknown';
19
+ }
20
+ catch {
21
+ return projectRoot.split(/[\\/]/).pop() ?? 'unknown';
22
+ }
23
+ }
24
+ function listMdFiles(dir, maxDepth = 3) {
25
+ const results = [];
26
+ if (!existsSync(dir) || maxDepth <= 0)
27
+ return results;
28
+ let entries;
29
+ try {
30
+ entries = readdirSync(dir, { withFileTypes: true });
31
+ }
32
+ catch {
33
+ return results;
34
+ }
35
+ for (const entry of entries) {
36
+ if (entry.name.startsWith('.'))
37
+ continue;
38
+ const full = join(dir, entry.name);
39
+ if (entry.isDirectory()) {
40
+ results.push(...listMdFiles(full, maxDepth - 1));
41
+ }
42
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
43
+ results.push(full);
44
+ }
45
+ }
46
+ return results;
47
+ }
48
+ function extractOneLineSummary(sessionRoot) {
49
+ const artifacts = listMdFiles(sessionRoot, 4);
50
+ for (const artifact of artifacts.slice(0, 5)) {
51
+ try {
52
+ const content = readFileSync(artifact, 'utf8');
53
+ // Grab the first non-heading, non-empty line after the front section
54
+ const lines = content.split(/\r?\n/);
55
+ for (const line of lines) {
56
+ const trimmed = line.trim();
57
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-') || trimmed.startsWith('`'))
58
+ continue;
59
+ if (trimmed.length > 10 && trimmed.length < 200)
60
+ return trimmed;
61
+ break;
62
+ }
63
+ }
64
+ catch {
65
+ // skip unreadable
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ function buildSessionHistory(projectRoot) {
71
+ const metas = listSessionMetas(projectRoot);
72
+ if (metas.length === 0) {
73
+ return `${MANAGED_BLOCK_START}\n\n_No sessions recorded yet._\n\n${MANAGED_BLOCK_END}`;
74
+ }
75
+ const maxSessions = 15;
76
+ // Sort by createdAt descending (most recent first)
77
+ const sorted = [...metas].sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || ''));
78
+ const recent = sorted.slice(0, maxSessions);
79
+ let body = `${MANAGED_BLOCK_START}\n\n## Timeline (${metas.length} sessions`;
80
+ if (metas.length > maxSessions)
81
+ body += `, showing last ${maxSessions}`;
82
+ body += ')\n\n';
83
+ // Human-readable timeline: date | name | brief
84
+ body += `| Date | Directory | Title | What |\n`;
85
+ body += `|------|-----------|-------|------|\n`;
86
+ for (const meta of recent) {
87
+ const date = meta.createdAt ? meta.createdAt.slice(0, 10) : '?';
88
+ const dir = meta.sessionId;
89
+ const title = (meta.title ?? 'Untitled').slice(0, 40);
90
+ const skill = meta.skill ?? '-';
91
+ // Extract one-line summary from artifacts for the "What" column
92
+ const sessionRoot = join(projectRoot, '.peaks', meta.sessionId);
93
+ const summary = extractOneLineSummary(sessionRoot);
94
+ const brief = summary ? summary.slice(0, 70) : skill;
95
+ body += `| ${date} | \`${dir}\` | ${title} | ${brief} |\n`;
96
+ }
97
+ body += `\n${MANAGED_BLOCK_END}`;
98
+ return body;
99
+ }
100
+ export function generateProjectContext(projectRoot) {
101
+ const peaksDir = join(projectRoot, '.peaks');
102
+ if (!existsSync(peaksDir)) {
103
+ mkdirSync(peaksDir, { recursive: true });
104
+ }
105
+ const contextPath = join(projectRoot, PROJECT_CONTEXT_FILE);
106
+ const name = projectName(projectRoot);
107
+ const now = new Date().toISOString();
108
+ const sessionHistory = buildSessionHistory(projectRoot);
109
+ const header = `${CONTEXT_HEADER}${now}\n\n## Project: ${name}\n`;
110
+ let content;
111
+ if (existsSync(contextPath)) {
112
+ const existing = readFileSync(contextPath, 'utf8');
113
+ const startIdx = existing.indexOf(MANAGED_BLOCK_START);
114
+ const endIdx = existing.indexOf(MANAGED_BLOCK_END);
115
+ // Update the Last-updated timestamp in the header
116
+ const updatedExisting = existing.replace(/Last updated: .*/, `Last updated: ${now}`);
117
+ if (startIdx >= 0 && endIdx > startIdx) {
118
+ // Replace managed block, preserve user content outside it
119
+ const before = updatedExisting.slice(0, startIdx);
120
+ const after = updatedExisting.slice(endIdx + MANAGED_BLOCK_END.length);
121
+ content = before + sessionHistory + after;
122
+ }
123
+ else {
124
+ // No managed block found — append
125
+ content = updatedExisting.trimEnd() + '\n\n' + sessionHistory + '\n';
126
+ }
127
+ }
128
+ else {
129
+ content = header + '\n' + sessionHistory + '\n';
130
+ }
131
+ writeFileSync(contextPath, content, 'utf8');
132
+ return { path: contextPath, content, sessionCount: listSessionMetas(projectRoot).length };
133
+ }
134
+ export function readProjectContext(projectRoot) {
135
+ const contextPath = join(projectRoot, PROJECT_CONTEXT_FILE);
136
+ if (!existsSync(contextPath))
137
+ return null;
138
+ try {
139
+ return readFileSync(contextPath, 'utf8');
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ }
145
+ export function getProjectContextPath(projectRoot) {
146
+ return join(projectRoot, PROJECT_CONTEXT_FILE);
147
+ }
@@ -1,4 +1,4 @@
1
- export type ProjectMemoryKind = 'project' | 'rule' | 'decision' | 'reference' | 'feedback';
1
+ export type ProjectMemoryKind = 'project' | 'rule' | 'decision' | 'reference' | 'feedback' | 'convention' | 'module';
2
2
  export type ExtractedProjectMemory = {
3
3
  title: string;
4
4
  kind: ProjectMemoryKind;
@@ -59,6 +59,21 @@ export type ProjectMemoryBackupPlan = {
59
59
  export type ProjectMemoryBackupResult = ProjectMemoryBackupPlan & {
60
60
  copiedFiles: string[];
61
61
  };
62
+ export type StoredProjectMemory = {
63
+ name: string;
64
+ title: string;
65
+ kind: ProjectMemoryKind;
66
+ sourceArtifact: string | null;
67
+ body: string;
68
+ filePath: string;
69
+ };
70
+ export type ProjectMemoryReadResult = {
71
+ projectRoot: string;
72
+ memoryDir: string;
73
+ total: number;
74
+ byKind: Record<ProjectMemoryKind, StoredProjectMemory[]>;
75
+ memories: StoredProjectMemory[];
76
+ };
62
77
  type ExtractPlanOptions = {
63
78
  projectRoot: string;
64
79
  artifactPaths: string[];
@@ -76,4 +91,5 @@ export declare function createProjectMemoryBackupPlan(options: BackupPlanOptions
76
91
  export declare function executeProjectMemoryBackup(options: BackupPlanOptions): ProjectMemoryBackupResult;
77
92
  export declare function summarizeProjectMemoryExtractResult(result: ProjectMemoryExtractResult): ProjectMemoryExtractSummary;
78
93
  export declare function summarizeProjectMemoryBackupResult(result: ProjectMemoryBackupResult): ProjectMemoryBackupSummary;
94
+ export declare function readProjectMemories(projectRoot: string): ProjectMemoryReadResult;
79
95
  export {};
@@ -4,7 +4,7 @@ import { isInsidePath, isWindowsAbsolutePath, normalizePath, resolveInputPath, s
4
4
  import { containsSensitiveConfigValue, isSensitiveConfigPath } from '../config/config-service.js';
5
5
  const START_MARKER = '<!-- peaks-memory:start -->';
6
6
  const END_MARKER = '<!-- peaks-memory:end -->';
7
- const VALID_MEMORY_KINDS = new Set(['project', 'rule', 'decision', 'reference', 'feedback']);
7
+ const VALID_MEMORY_KINDS = new Set(['project', 'rule', 'decision', 'reference', 'feedback', 'convention', 'module']);
8
8
  function normalizeRoot(path) {
9
9
  return resolveInputPath(path);
10
10
  }
@@ -42,11 +42,11 @@ function assertInsideProject(path, projectRoot) {
42
42
  function assertSafeProjectMemoryDir(projectRoot) {
43
43
  const resolvedRoot = normalizeRoot(projectRoot);
44
44
  const realRoot = normalizeRealRoot(projectRoot);
45
- const claudeDir = join(resolvedRoot, '.claude');
46
- if (existsSync(claudeDir) && lstatSync(claudeDir).isSymbolicLink()) {
45
+ const peaksDir = join(resolvedRoot, '.peaks');
46
+ if (existsSync(peaksDir) && lstatSync(peaksDir).isSymbolicLink()) {
47
47
  throw new Error('Project memory directory must stay inside the project root');
48
48
  }
49
- const memoryDir = join(claudeDir, 'memory');
49
+ const memoryDir = join(peaksDir, 'memory');
50
50
  if (existsSync(memoryDir)) {
51
51
  if (lstatSync(memoryDir).isSymbolicLink()) {
52
52
  throw new Error('Project memory directory must stay inside the project root');
@@ -136,6 +136,41 @@ function renderMemoryFile(memory) {
136
136
  ''
137
137
  ].join('\n');
138
138
  }
139
+ function parseStoredMemoryFile(content, filePath) {
140
+ const normalized = content.replace(/\r\n/g, '\n');
141
+ if (!normalized.startsWith('---\n'))
142
+ return null;
143
+ const endIndex = normalized.indexOf('\n---\n', 4);
144
+ if (endIndex < 0)
145
+ return null;
146
+ const frontmatter = normalized.slice(4, endIndex);
147
+ const body = normalized.slice(endIndex + '\n---\n'.length).trim();
148
+ let name;
149
+ let description;
150
+ let kind;
151
+ let sourceArtifact;
152
+ for (const rawLine of frontmatter.split('\n')) {
153
+ const line = rawLine.trim();
154
+ if (line.startsWith('name:'))
155
+ name = line.slice('name:'.length).trim();
156
+ else if (line.startsWith('description:'))
157
+ description = line.slice('description:'.length).trim();
158
+ else if (line.startsWith('type:'))
159
+ kind = line.slice('type:'.length).trim();
160
+ else if (line.startsWith('sourceArtifact:'))
161
+ sourceArtifact = line.slice('sourceArtifact:'.length).trim();
162
+ }
163
+ if (!name || !kind || !VALID_MEMORY_KINDS.has(kind) || body.length === 0)
164
+ return null;
165
+ return {
166
+ name,
167
+ title: description ?? name,
168
+ kind: kind,
169
+ sourceArtifact: sourceArtifact && sourceArtifact !== 'undefined' ? sourceArtifact : null,
170
+ body,
171
+ filePath
172
+ };
173
+ }
139
174
  function summarizeExtractResult(result) {
140
175
  return {
141
176
  apply: result.apply,
@@ -304,3 +339,36 @@ export function summarizeProjectMemoryExtractResult(result) {
304
339
  export function summarizeProjectMemoryBackupResult(result) {
305
340
  return summarizeBackupResult(result);
306
341
  }
342
+ function emptyByKind() {
343
+ return {
344
+ project: [],
345
+ rule: [],
346
+ decision: [],
347
+ reference: [],
348
+ feedback: [],
349
+ convention: [],
350
+ module: []
351
+ };
352
+ }
353
+ export function readProjectMemories(projectRoot) {
354
+ const normalizedRoot = normalizeRoot(projectRoot);
355
+ const memoryDir = assertSafeProjectMemoryDir(normalizedRoot);
356
+ const memories = [];
357
+ for (const filePath of listMarkdownFiles(memoryDir)) {
358
+ const parsed = parseStoredMemoryFile(readFileSync(filePath, 'utf8'), filePath);
359
+ if (parsed)
360
+ memories.push(parsed);
361
+ }
362
+ memories.sort((left, right) => left.name.localeCompare(right.name));
363
+ const byKind = emptyByKind();
364
+ for (const memory of memories) {
365
+ byKind[memory.kind].push(memory);
366
+ }
367
+ return {
368
+ projectRoot: normalizedRoot,
369
+ memoryDir,
370
+ total: memories.length,
371
+ byKind,
372
+ memories
373
+ };
374
+ }
@@ -9,8 +9,8 @@ export type SkillPresence = {
9
9
  setAt: string;
10
10
  lastHeartbeat?: string;
11
11
  };
12
- export declare function exportSkillPresence(): string;
13
- export declare function setSkillPresence(skill: string, mode?: string, gate?: string): SkillPresence;
14
- export declare function getSkillPresence(): SkillPresence | null;
15
- export declare function touchSkillHeartbeat(): SkillPresence | null;
16
- export declare function clearSkillPresence(): boolean;
12
+ export declare function exportSkillPresence(projectRootOverride?: string): string;
13
+ export declare function setSkillPresence(skill: string, mode?: string, gate?: string, projectRootOverride?: string): SkillPresence;
14
+ export declare function getSkillPresence(projectRootOverride?: string): SkillPresence | null;
15
+ export declare function touchSkillHeartbeat(projectRootOverride?: string): SkillPresence | null;
16
+ export declare function clearSkillPresence(projectRootOverride?: string): boolean;
@@ -12,14 +12,16 @@ export function isSkillPresenceMode(value) {
12
12
  }
13
13
  const PRESENCE_FILE = '.peaks/.active-skill.json';
14
14
  const SESSION_FILE = '.peaks/.session.json';
15
- function resolveProjectRoot() {
15
+ function resolveProjectRoot(override) {
16
+ if (override)
17
+ return resolve(override);
16
18
  return findProjectRoot(process.cwd()) ?? process.cwd();
17
19
  }
18
- function resolvePresencePath() {
19
- return resolve(resolveProjectRoot(), PRESENCE_FILE);
20
+ function resolvePresencePath(projectRootOverride) {
21
+ return resolve(resolveProjectRoot(projectRootOverride), PRESENCE_FILE);
20
22
  }
21
- function getCurrentSessionId() {
22
- const sessionPath = resolve(resolveProjectRoot(), SESSION_FILE);
23
+ function getCurrentSessionId(projectRootOverride) {
24
+ const sessionPath = resolve(resolveProjectRoot(projectRootOverride), SESSION_FILE);
23
25
  if (!existsSync(sessionPath))
24
26
  return null;
25
27
  try {
@@ -32,12 +34,12 @@ function getCurrentSessionId() {
32
34
  return null;
33
35
  }
34
36
  }
35
- export function exportSkillPresence() {
36
- return resolvePresencePath();
37
+ export function exportSkillPresence(projectRootOverride) {
38
+ return resolvePresencePath(projectRootOverride);
37
39
  }
38
- export function setSkillPresence(skill, mode, gate) {
40
+ export function setSkillPresence(skill, mode, gate, projectRootOverride) {
39
41
  const validatedMode = mode && isSkillPresenceMode(mode) ? mode : undefined;
40
- const sessionId = getCurrentSessionId();
42
+ const sessionId = getCurrentSessionId(projectRootOverride);
41
43
  const now = new Date().toISOString();
42
44
  const presence = {
43
45
  skill,
@@ -47,7 +49,7 @@ export function setSkillPresence(skill, mode, gate) {
47
49
  setAt: now,
48
50
  lastHeartbeat: now
49
51
  };
50
- const presencePath = resolvePresencePath();
52
+ const presencePath = resolvePresencePath(projectRootOverride);
51
53
  const presenceDir = dirname(presencePath);
52
54
  if (!existsSync(presenceDir)) {
53
55
  mkdirSync(presenceDir, { recursive: true });
@@ -55,8 +57,8 @@ export function setSkillPresence(skill, mode, gate) {
55
57
  writeFileSync(presencePath, JSON.stringify(presence, null, 2), 'utf8');
56
58
  return presence;
57
59
  }
58
- export function getSkillPresence() {
59
- const presencePath = resolvePresencePath();
60
+ export function getSkillPresence(projectRootOverride) {
61
+ const presencePath = resolvePresencePath(projectRootOverride);
60
62
  if (!existsSync(presencePath)) {
61
63
  return null;
62
64
  }
@@ -67,7 +69,7 @@ export function getSkillPresence() {
67
69
  return null;
68
70
  }
69
71
  if (typeof parsed.sessionId === 'string' && parsed.sessionId.length > 0) {
70
- const currentSessionId = getCurrentSessionId();
72
+ const currentSessionId = getCurrentSessionId(projectRootOverride);
71
73
  if (currentSessionId && parsed.sessionId !== currentSessionId) {
72
74
  unlinkSync(presencePath);
73
75
  return null;
@@ -79,8 +81,8 @@ export function getSkillPresence() {
79
81
  return null;
80
82
  }
81
83
  }
82
- export function touchSkillHeartbeat() {
83
- const presencePath = resolvePresencePath();
84
+ export function touchSkillHeartbeat(projectRootOverride) {
85
+ const presencePath = resolvePresencePath(projectRootOverride);
84
86
  if (!existsSync(presencePath)) {
85
87
  return null;
86
88
  }
@@ -91,7 +93,7 @@ export function touchSkillHeartbeat() {
91
93
  return null;
92
94
  }
93
95
  if (typeof parsed.sessionId === 'string' && parsed.sessionId.length > 0) {
94
- const currentSessionId = getCurrentSessionId();
96
+ const currentSessionId = getCurrentSessionId(projectRootOverride);
95
97
  if (currentSessionId && parsed.sessionId !== currentSessionId) {
96
98
  unlinkSync(presencePath);
97
99
  return null;
@@ -105,8 +107,8 @@ export function touchSkillHeartbeat() {
105
107
  return null;
106
108
  }
107
109
  }
108
- export function clearSkillPresence() {
109
- const presencePath = resolvePresencePath();
110
+ export function clearSkillPresence(projectRootOverride) {
111
+ const presencePath = resolvePresencePath(projectRootOverride);
110
112
  if (!existsSync(presencePath)) {
111
113
  return false;
112
114
  }
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "1.0.27";
1
+ export declare const CLI_VERSION = "1.1.0";
@@ -1 +1 @@
1
- export const CLI_VERSION = "1.0.27";
1
+ export const CLI_VERSION = "1.1.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-cli",
3
- "version": "1.0.27",
3
+ "version": "1.1.0",
4
4
  "description": "Peaks CLI and short skill family for Claude Code automation.",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -12,10 +12,16 @@ Peaks-Cli PRD turns user intent into verifiable product artifacts.
12
12
  Before any analysis or tool call, immediately run:
13
13
 
14
14
  ```bash
15
- peaks skill presence:set peaks-prd --mode <mode> --gate startup
15
+ peaks skill presence:set peaks-prd --project <repo> --mode <mode> --gate startup
16
16
  ```
17
+ Read persistent project memory via CLI (durable, LLM-authored memories):
17
18
 
18
- Then display: `Peaks-Cli Skill: peaks-prd | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-prd --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear`.
19
+ ```bash
20
+ peaks project memories --project <repo> --json
21
+ ```
22
+
23
+ This returns durable memories from `.peaks/memory` — decisions, conventions, modules, and rules captured in past sessions. Filter with `--kind <decision|convention|module|rule|reference|project>`. (`.peaks/PROJECT.md` is a human-readable session timeline only.)
24
+ Then display: `Peaks-Cli Skill: peaks-prd | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-prd --project <repo> --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear --project <repo>`.
19
25
 
20
26
  ## Responsibilities
21
27
 
@@ -54,7 +60,7 @@ For a feature / bug / clarification request with no authenticated source documen
54
60
  ```bash
55
61
  # 0. confirm PRD's own runbook integrity before driving any phase
56
62
  peaks skill runbook peaks-prd --json
57
- peaks skill presence:set peaks-prd # show persistent skill presence every turn
63
+ peaks skill presence:set peaks-prd --project <repo> # show persistent skill presence every turn
58
64
 
59
65
  # 1. capture the request as the canonical PRD artifact (preview, then apply)
60
66
  peaks request init --role prd --id <request-id> --project <repo> --json
@@ -74,7 +80,7 @@ peaks codegraph status --project <repo> # local index status
74
80
 
75
81
  # 5. write goals / non-goals / acceptance into the artifact body, then hand off
76
82
  peaks request show <request-id> --role prd --project <repo> --json
77
- peaks skill presence:clear # handoff complete, remove presence indicator
83
+ peaks skill presence:clear --project <repo> # handoff complete, remove presence indicator
78
84
  ```
79
85
 
80
86
  For an authenticated product document request (Feishu/Lark/wiki), add before step 5:
@@ -12,10 +12,16 @@ Peaks-Cli QA proves that planned changes are protected and accepted.
12
12
  Before any analysis or tool call, immediately run:
13
13
 
14
14
  ```bash
15
- peaks skill presence:set peaks-qa --mode <mode> --gate startup
15
+ peaks skill presence:set peaks-qa --project <repo> --mode <mode> --gate startup
16
16
  ```
17
+ Read persistent project memory via CLI (durable, LLM-authored memories):
17
18
 
18
- Then display: `Peaks-Cli Skill: peaks-qa | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-qa --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear`.
19
+ ```bash
20
+ peaks project memories --project <repo> --json
21
+ ```
22
+
23
+ This returns durable memories from `.peaks/memory` — decisions, conventions, modules, and rules captured in past sessions. Filter with `--kind <decision|convention|module|rule|reference|project>`. (`.peaks/PROJECT.md` is a human-readable session timeline only.)
24
+ Then display: `Peaks-Cli Skill: peaks-qa | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-qa --project <repo> --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear --project <repo>`.
19
25
 
20
26
  ## Responsibilities
21
27
 
@@ -47,7 +53,7 @@ The default sequence the QA skill should execute. Do not skip the boundary check
47
53
  ```bash
48
54
  # 0. confirm QA's own runbook integrity before validating anything
49
55
  peaks skill runbook peaks-qa --json
50
- peaks skill presence:set peaks-qa # show persistent skill presence every turn
56
+ peaks skill presence:set peaks-qa --project <repo> # show persistent skill presence every turn
51
57
 
52
58
  # 1. capture the QA request artifact and read upstream scope
53
59
  peaks request init --role qa --id <request-id> --project <repo> --apply --json
@@ -134,7 +140,7 @@ peaks request lint <rid> --role qa --project <repo> --json
134
140
  # 9. on verdict=return-to-rd, route findings back through the request id; otherwise close.
135
141
  peaks request show <request-id> --role qa --project <repo> --json
136
142
  peaks openspec archive <change-id> --project <repo> --json # preview, then --apply on full pass
137
- peaks skill presence:clear # QA complete, remove presence indicator
143
+ peaks skill presence:clear --project <repo> # QA complete, remove presence indicator
138
144
  ```
139
145
 
140
146
  Verdict `pass` is blocked until every applicable validation gate has evidence in the artifact.
@@ -12,10 +12,16 @@ Peaks-Cli RD owns engineering analysis, implementation planning, and refactor ex
12
12
  Before any analysis or tool call, immediately run:
13
13
 
14
14
  ```bash
15
- peaks skill presence:set peaks-rd --mode <mode> --gate startup
15
+ peaks skill presence:set peaks-rd --project <repo> --mode <mode> --gate startup
16
16
  ```
17
+ Read persistent project memory via CLI (durable, LLM-authored memories):
17
18
 
18
- Then display: `Peaks-Cli Skill: peaks-rd | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-rd --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear`.
19
+ ```bash
20
+ peaks project memories --project <repo> --json
21
+ ```
22
+
23
+ This returns durable memories from `.peaks/memory` — decisions, conventions, modules, and rules captured in past sessions. Filter with `--kind <decision|convention|module|rule|reference|project>`. (`.peaks/PROJECT.md` is a human-readable session timeline only.)
24
+ Then display: `Peaks-Cli Skill: peaks-rd | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-rd --project <repo> --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear --project <repo>`.
19
25
 
20
26
  ## Responsibilities
21
27
 
@@ -41,7 +47,7 @@ The default sequence the RD skill should execute for a code-touching request. Sk
41
47
  ```bash
42
48
  # 0. confirm RD's own runbook integrity before any code edit
43
49
  peaks skill runbook peaks-rd --json
44
- peaks skill presence:set peaks-rd # show persistent skill presence every turn
50
+ peaks skill presence:set peaks-rd --project <repo> # show persistent skill presence every turn
45
51
 
46
52
  # 1. capture the RD request artifact and read upstream PRD / UI scope
47
53
  peaks request init --role rd --id <request-id> --project <repo> --apply --json
@@ -138,7 +144,7 @@ peaks openspec validate <change-id> --project <repo> --json # exit gate (re-r
138
144
  # 8. hand off to QA via the cross-linked request id
139
145
  peaks request init --role qa --id <request-id> --project <repo> --apply --json
140
146
  peaks request show <request-id> --role rd --project <repo> --json
141
- peaks skill presence:clear # handoff complete, remove presence indicator
147
+ peaks skill presence:clear --project <repo> # handoff complete, remove presence indicator
142
148
  ```
143
149
 
144
150
  For refactor work, the coverage ≥ 95% gate in `Refactor hard gates` still applies and must be recorded in the artifact before slicing begins.
@@ -12,10 +12,16 @@ Peaks-Cli SC records how product, RD, QA, code, and artifacts move together.
12
12
  Before any analysis or tool call, immediately run:
13
13
 
14
14
  ```bash
15
- peaks skill presence:set peaks-sc --mode <mode> --gate startup
15
+ peaks skill presence:set peaks-sc --project <repo> --mode <mode> --gate startup
16
16
  ```
17
+ Read persistent project memory via CLI (durable, LLM-authored memories):
17
18
 
18
- Then display: `Peaks-Cli Skill: peaks-sc | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-sc --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear`.
19
+ ```bash
20
+ peaks project memories --project <repo> --json
21
+ ```
22
+
23
+ This returns durable memories from `.peaks/memory` — decisions, conventions, modules, and rules captured in past sessions. Filter with `--kind <decision|convention|module|rule|reference|project>`. (`.peaks/PROJECT.md` is a human-readable session timeline only.)
24
+ Then display: `Peaks-Cli Skill: peaks-sc | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-sc --project <repo> --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear --project <repo>`.
19
25
 
20
26
  ## Responsibilities
21
27
 
@@ -50,7 +56,7 @@ Use gstack as a concrete source-control and release workflow reference for the `
50
56
 
51
57
  ## Project memory backup
52
58
 
53
- Project `.claude/memory` is the primary source for durable project memory. At approved checkpoints, use `peaks memory sync --project <path> --workspace <artifact-workspace> --apply` to back up the full project memory directory into the artifact repository workspace; do not treat the artifact backup as a second writable memory source.
59
+ Project `.peaks/memory` is the primary source for durable project memory. At approved checkpoints, use `peaks memory sync --project <path> --workspace <artifact-workspace> --apply` to back up the full project memory directory into the artifact repository workspace; do not treat the artifact backup as a second writable memory source.
54
60
 
55
61
  ## Commit boundary derivation
56
62
 
@@ -77,7 +83,7 @@ Use this sequence when SC owns the change-control pass for a refactor or release
77
83
  # in: none
78
84
  # out: runbook version, presence set
79
85
  peaks skill runbook peaks-sc --json
80
- peaks skill presence:set peaks-sc # show persistent skill presence every turn
86
+ peaks skill presence:set peaks-sc --project <repo> # show persistent skill presence every turn
81
87
 
82
88
  # 1. Derive commit boundaries (OpenSpec preferred, git diff fallback)
83
89
  # in: change-id, repo path
@@ -115,7 +121,7 @@ peaks sc boundary --slice-id <slice-id> --artifact <artifact-path> --code <code-
115
121
  # out: sync result or dry-run preview
116
122
  peaks memory sync --project <repo> --workspace <workspace> --apply --json
117
123
  peaks artifacts sync --workspace <workspace> --apply --json
118
- peaks skill presence:clear # SC complete, remove presence indicator
124
+ peaks skill presence:clear --project <repo> # SC complete, remove presence indicator
119
125
  ```
120
126
 
121
127
  The final two `--apply` calls require explicit authorization. Without it, default to `--dry-run` or omit the sync calls entirely and keep the boundary evidence local under `.peaks/<session-id>/`.
@@ -71,12 +71,30 @@ If the user already names a profile in their invocation (e.g. `/peaks-solo --ful
71
71
  Only after the mode is known (user selected or explicitly named), run:
72
72
 
73
73
  ```bash
74
- peaks skill presence:set peaks-solo --mode <mode-value> --gate startup
74
+ peaks skill presence:set peaks-solo --project <repo> --mode <mode-value> --gate startup
75
75
  ```
76
76
 
77
77
  Then display the compact status header: `Peaks-Cli Skill: peaks-solo | Peaks-Cli Gate: startup | Next: <one short action>`. Display this header on EVERY turn while the skill is active.
78
78
 
79
- Update with `peaks skill presence:set peaks-solo --mode <mode> --gate <gate>` when gates change. The presence file persists across the full workflow lifecycle — do NOT clear it at workflow end.
79
+ Update with `peaks skill presence:set peaks-solo --project <repo> --mode <mode> --gate <gate>` when gates change. The presence file persists across the full workflow lifecycle — do NOT clear it at workflow end.
80
+
81
+ ### Peaks-Cli Step 2.3: Load project memory (durable, LLM-authored memories)
82
+
83
+ Before planning any work, read the project's persistent memory — durable memories that survive across sessions:
84
+
85
+ ```bash
86
+ peaks project memories --project <repo> --json
87
+ ```
88
+
89
+ This returns durable memories from `.peaks/memory`, grouped by kind:
90
+ - **module** — code areas touched, with risk and rationale captured by past sessions
91
+ - **decision** — architectural choices, why they were made, what they affect
92
+ - **convention** — discovered project patterns (code style, naming, tooling)
93
+ - **rule** / **reference** / **project** — standing constraints, external pointers, and project context
94
+
95
+ Filter with `--kind <decision|convention|module|rule|reference|project>` when you only need one slice. Use this to understand what exists, what was decided, and what to avoid re-litigating. Memories are LLM-authored at approved checkpoints via `peaks memory extract`.
96
+
97
+ `.peaks/PROJECT.md` is a human-readable session timeline only — do NOT use it for LLM context.
80
98
 
81
99
  ### Peaks-Cli Step 2.5: Set session title
82
100
 
@@ -579,7 +597,7 @@ Solo is itself a skill running in the current session. To "invoke peaks-rd" or "
579
597
  **Presence restoration after role skill returns (MANDATORY):** Role skills (peaks-rd, peaks-qa, peaks-ui) call `peaks skill presence:set <role>` internally, which overwrites `.peaks/.active-skill.json`. After EVERY role skill returns — whether success, repair-needed, or failure — Solo MUST immediately restore the orchestrator presence by re-running the same presence command from Step 2:
580
598
 
581
599
  ```bash
582
- peaks skill presence:set peaks-solo --mode <mode> --gate <current-gate>
600
+ peaks skill presence:set peaks-solo --project <repo> --mode <mode> --gate <current-gate>
583
601
  ```
584
602
 
585
603
  This keeps the CLAUDE.md status header accurate (`Peaks-Cli Skill: peaks-solo`) instead of showing a stale role name. Use the current mode and gate values; the gate may have advanced since startup. Skipping this step causes the header to display the last role skill name permanently.
@@ -606,7 +624,7 @@ When `peaks-qa` returns `verdict=return-to-rd`, Solo does NOT manually rewrite R
606
624
  6. Repeat steps 1-5 until QA returns `verdict=pass`, or the cap below fires.
607
625
  **After each repair iteration** (after peaks-rd and peaks-qa both return), Solo MUST restore presence:
608
626
  ```bash
609
- peaks skill presence:set peaks-solo --mode <mode> --gate repair-cycle-<N>
627
+ peaks skill presence:set peaks-solo --project <repo> --mode <mode> --gate repair-cycle-<N>
610
628
  ```
611
629
 
612
630
  **Repair cycle cap**: After 3 repair cycles without a passing QA verdict, emit a blocked TXT handoff regardless of remaining issues. Do not loop indefinitely. If a specific issue cannot be resolved within 3 cycles, mark it as a known blocker in the TXT handoff and proceed to the SC phase.
@@ -781,7 +799,7 @@ Use Peaks-Cli TXT for the compact handoff capsule: mode, validated decisions, ar
781
799
 
782
800
  ### Workflow completion (no auto-exit)
783
801
 
784
- Do NOT call `peaks skill presence:clear` at workflow end. The presence file and header remain active so the user stays inside the workflow context. The user can continue with follow-up requirements naturally — no need to re-invoke `/peaks-solo`. The header continues to display the active skill and current gate.
802
+ Do NOT call `peaks skill presence:clear --project <repo>` at workflow end. The presence file and header remain active so the user stays inside the workflow context. The user can continue with follow-up requirements naturally — no need to re-invoke `/peaks-solo`. The header continues to display the active skill and current gate.
785
803
 
786
804
  ## Peaks-Cli External references and lifecycle
787
805
 
@@ -12,10 +12,16 @@ Peaks-Cli TXT compresses workflow context into portable, role-specific artifacts
12
12
  Before any analysis or tool call, immediately run:
13
13
 
14
14
  ```bash
15
- peaks skill presence:set peaks-txt --mode <mode> --gate startup
15
+ peaks skill presence:set peaks-txt --project <repo> --mode <mode> --gate startup
16
16
  ```
17
+ Read persistent project memory via CLI (durable, LLM-authored memories):
17
18
 
18
- Then display: `Peaks-Cli Skill: peaks-txt | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-txt --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear`.
19
+ ```bash
20
+ peaks project memories --project <repo> --json
21
+ ```
22
+
23
+ This returns durable memories from `.peaks/memory` — decisions, conventions, modules, and rules captured in past sessions. Filter with `--kind <decision|convention|module|rule|reference|project>`. (`.peaks/PROJECT.md` is a human-readable session timeline only.)
24
+ Then display: `Peaks-Cli Skill: peaks-txt | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-txt --project <repo> --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear --project <repo>`.
19
25
 
20
26
  ## Responsibilities
21
27
 
@@ -90,7 +96,7 @@ Each entry should include:
90
96
  - why it exists;
91
97
  - affected skills;
92
98
  - how future PRD/RD/UI/QA/SC/Solo workflows should apply it;
93
- - whether it is stable enough for `.claude/memory` extraction.
99
+ - whether it is stable enough for `.peaks/memory` extraction.
94
100
 
95
101
  ## Project memory guidance
96
102
 
@@ -105,7 +111,7 @@ Stable memory body.
105
111
  <!-- peaks-memory:end -->
106
112
  ```
107
113
 
108
- The primary write target is the target project's `.claude/memory`. Use `peaks memory extract --project <path> --artifact <artifact> --apply` only after the user or active profile allows durable project memory writes.
114
+ The primary write target is the target project's `.peaks/memory`. Use `peaks memory extract --project <path> --artifact <artifact> --apply` only after the user or active profile allows durable project memory writes.
109
115
 
110
116
  ## Matt Pocock skills integration
111
117
 
@@ -161,7 +167,7 @@ Use this sequence when TXT compresses an in-flight workflow into a portable, com
161
167
  ```bash
162
168
  # 0. Confirm TXT's own runbook integrity before compressing a handoff
163
169
  peaks skill runbook peaks-txt --json
164
- peaks skill presence:set peaks-txt # show persistent skill presence every turn
170
+ peaks skill presence:set peaks-txt --project <repo> # show persistent skill presence every turn
165
171
 
166
172
  # 1. Inventory per-role artifacts already produced for the request
167
173
  peaks request list --project <repo> --json
@@ -180,7 +186,7 @@ peaks capabilities --json
180
186
  # 5. Memory extraction — dry-run by default, apply only when authorized
181
187
  peaks memory extract --project <repo> --artifact <artifact-path> --dry-run --json
182
188
  peaks memory extract --project <repo> --artifact <artifact-path> --apply --json
183
- peaks skill presence:clear # handoff capsule complete, remove presence indicator
189
+ peaks skill presence:clear --project <repo> # handoff capsule complete, remove presence indicator
184
190
  ```
185
191
 
186
192
  The final `--apply` call requires explicit user or profile authorization. Without it, keep the capsule under `.peaks/<session-id>/txt/` and reference artifact paths from other roles instead of duplicating their content.
@@ -12,10 +12,16 @@ Peaks-Cli UI handles experience, interaction, visual direction, and UI-specific
12
12
  Before any analysis or tool call, immediately run:
13
13
 
14
14
  ```bash
15
- peaks skill presence:set peaks-ui --mode <mode> --gate startup
15
+ peaks skill presence:set peaks-ui --project <repo> --mode <mode> --gate startup
16
16
  ```
17
+ Read persistent project memory via CLI (durable, LLM-authored memories):
17
18
 
18
- Then display: `Peaks-Cli Skill: peaks-ui | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-ui --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear`.
19
+ ```bash
20
+ peaks project memories --project <repo> --json
21
+ ```
22
+
23
+ This returns durable memories from `.peaks/memory` — decisions, conventions, modules, and rules captured in past sessions. Filter with `--kind <decision|convention|module|rule|reference|project>`. (`.peaks/PROJECT.md` is a human-readable session timeline only.)
24
+ Then display: `Peaks-Cli Skill: peaks-ui | Peaks-Cli Gate: startup | Next: <one short action>`. Update with `peaks skill presence:set peaks-ui --project <repo> --mode <mode> --gate <gate>` when gates change. When the role's work ends, run `peaks skill presence:clear --project <repo>`.
19
25
 
20
26
  ## Responsibilities
21
27
 
@@ -48,7 +54,7 @@ The default sequence the UI skill should execute. Skip steps that do not apply;
48
54
  ```bash
49
55
  # 0. confirm UI's own runbook integrity before driving any phase
50
56
  peaks skill runbook peaks-ui --json
51
- peaks skill presence:set peaks-ui # show persistent skill presence every turn
57
+ peaks skill presence:set peaks-ui --project <repo> # show persistent skill presence every turn
52
58
 
53
59
  # 1. capture the UI request as a durable artifact tied to the same PRD request id
54
60
  peaks request init --role ui --id <request-id> --project <repo> --json
@@ -113,7 +119,7 @@ peaks mcp apply --capability playwright-mcp.browser-validation --yes --json
113
119
  # 7. hand off to RD / QA via the cross-linked request id
114
120
  peaks request list --project <repo> --json
115
121
  peaks request show <request-id> --role ui --project <repo> --json
116
- peaks skill presence:clear # handoff complete, remove presence indicator
122
+ peaks skill presence:clear --project <repo> # handoff complete, remove presence indicator
117
123
  ```
118
124
 
119
125
  Handoff is blocked until the UI artifact's `state` reaches `direction-locked` or `handed-off`.