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 +0 -0
- package/dist/src/cli/commands/core-artifact-commands.js +21 -10
- package/dist/src/cli/commands/project-commands.js +59 -0
- package/dist/src/services/memory/project-context-service.d.ts +11 -0
- package/dist/src/services/memory/project-context-service.js +147 -0
- package/dist/src/services/memory/project-memory-service.d.ts +17 -1
- package/dist/src/services/memory/project-memory-service.js +72 -4
- package/dist/src/services/skills/skill-presence-service.d.ts +5 -5
- package/dist/src/services/skills/skill-presence-service.js +20 -18
- 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 +10 -4
- package/skills/peaks-qa/SKILL.md +10 -4
- package/skills/peaks-rd/SKILL.md +10 -4
- package/skills/peaks-sc/SKILL.md +11 -5
- package/skills/peaks-solo/SKILL.md +23 -5
- package/skills/peaks-txt/SKILL.md +12 -6
- package/skills/peaks-ui/SKILL.md +10 -4
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')
|
|
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')
|
|
96
|
-
|
|
97
|
-
|
|
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 .
|
|
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 .
|
|
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 .
|
|
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 .
|
|
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
|
|
46
|
-
if (existsSync(
|
|
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(
|
|
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
|
|
1
|
+
export declare const CLI_VERSION = "1.1.0";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.0
|
|
1
|
+
export const CLI_VERSION = "1.1.0";
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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:
|
package/skills/peaks-qa/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
package/skills/peaks-sc/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
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 `.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 `.
|
|
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 `.
|
|
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
|
|
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.
|
package/skills/peaks-ui/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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`.
|