peaks-cli 1.0.28 → 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 +4 -4
- package/dist/src/cli/commands/project-commands.js +23 -101
- package/dist/src/services/memory/project-context-service.d.ts +0 -38
- package/dist/src/services/memory/project-context-service.js +2 -304
- 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/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 +3 -3
- package/skills/peaks-qa/SKILL.md +3 -3
- package/skills/peaks-rd/SKILL.md +3 -3
- package/skills/peaks-sc/SKILL.md +4 -4
- package/skills/peaks-solo/SKILL.md +10 -9
- package/skills/peaks-txt/SKILL.md +5 -5
- package/skills/peaks-ui/SKILL.md +3 -3
package/bin/peaks.js
CHANGED
|
File without changes
|
|
@@ -227,11 +227,11 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
227
227
|
const memory = program.command('memory').description('Manage project-local Peaks memory');
|
|
228
228
|
addJsonOption(memory
|
|
229
229
|
.command('extract')
|
|
230
|
-
.description('Extract stable project memory from skill artifacts into project .
|
|
230
|
+
.description('Extract stable project memory from skill artifacts into project .peaks/memory')
|
|
231
231
|
.requiredOption('--project <path>', 'target project root')
|
|
232
232
|
.requiredOption('--artifact <path...>', 'skill artifact paths inside the project')
|
|
233
233
|
.option('--dry-run', 'preview writes without changing files', true)
|
|
234
|
-
.option('--apply', 'write extracted memories into project .
|
|
234
|
+
.option('--apply', 'write extracted memories into project .peaks/memory')).action((options) => {
|
|
235
235
|
try {
|
|
236
236
|
const result = executeProjectMemoryExtract({ projectRoot: options.project, artifactPaths: options.artifact, apply: options.apply === true });
|
|
237
237
|
printResult(io, ok('memory.extract', summarizeProjectMemoryExtractResult(result)), options.json);
|
|
@@ -243,11 +243,11 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
243
243
|
});
|
|
244
244
|
addJsonOption(memory
|
|
245
245
|
.command('sync')
|
|
246
|
-
.description('Back up project .
|
|
246
|
+
.description('Back up project .peaks/memory into the artifact workspace')
|
|
247
247
|
.requiredOption('--project <path>', 'target project root')
|
|
248
248
|
.requiredOption('--workspace <path>', 'artifact workspace path')
|
|
249
249
|
.option('--dry-run', 'preview copies without changing files', true)
|
|
250
|
-
.option('--apply', 'copy project .
|
|
250
|
+
.option('--apply', 'copy project .peaks/memory into artifact workspace backup')).action((options) => {
|
|
251
251
|
try {
|
|
252
252
|
const result = executeProjectMemoryBackup({ projectRoot: options.project, artifactWorkspacePath: options.workspace, apply: options.apply === true });
|
|
253
253
|
printResult(io, ok('memory.sync', summarizeProjectMemoryBackupResult(result)), options.json);
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { loadProjectDashboard } from '../../services/dashboard/project-dashboard-service.js';
|
|
2
|
-
import { generateProjectContext,
|
|
2
|
+
import { generateProjectContext, readProjectContext } from '../../services/memory/project-context-service.js';
|
|
3
|
+
import { readProjectMemories } from '../../services/memory/project-memory-service.js';
|
|
3
4
|
import { fail, ok } from '../../shared/result.js';
|
|
4
5
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
5
|
-
function defined(obj) {
|
|
6
|
-
const result = {};
|
|
7
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
8
|
-
if (v !== undefined)
|
|
9
|
-
result[k] = v;
|
|
10
|
-
}
|
|
11
|
-
return result;
|
|
12
|
-
}
|
|
13
6
|
export function registerProjectCommands(program, io) {
|
|
14
7
|
const project = program.command('project').description('Aggregate Peaks state for a target project (read-only)');
|
|
15
8
|
addJsonOption(project
|
|
@@ -75,104 +68,33 @@ export function registerProjectCommands(program, io) {
|
|
|
75
68
|
process.exitCode = 1;
|
|
76
69
|
}
|
|
77
70
|
});
|
|
78
|
-
// ---
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
.description('Read the full ontology JSON for LLM consumption')
|
|
83
|
-
.requiredOption('--project <path>', 'target project root')).action((options) => {
|
|
84
|
-
try {
|
|
85
|
-
const onto = loadOntology(options.project);
|
|
86
|
-
if (onto === null) {
|
|
87
|
-
// Auto-generate if missing
|
|
88
|
-
const result = generateProjectContext(options.project);
|
|
89
|
-
printResult(io, ok('project.ontology', result.ontology), options.json);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
printResult(io, ok('project.ontology', onto), options.json);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
printResult(io, fail('project.ontology', 'ONTOLOGY_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path']), options.json);
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
addJsonOption(ontology
|
|
100
|
-
.command('module')
|
|
101
|
-
.description('Record or query a project module')
|
|
102
|
-
.requiredOption('--project <path>', 'target project root')
|
|
103
|
-
.option('--id <id>', 'module id (kebab-case)')
|
|
104
|
-
.option('--path <path>', 'file path for the module')
|
|
105
|
-
.option('--risk <level>', 'risk level: low, medium, high')
|
|
106
|
-
.option('--summary <text>', 'brief module description')
|
|
107
|
-
.option('--session <id>', 'session id')
|
|
108
|
-
.option('--put', 'write/update the module entry')).action((options) => {
|
|
109
|
-
try {
|
|
110
|
-
if (options.put) {
|
|
111
|
-
if (!options.id || !options.path || !options.session) {
|
|
112
|
-
printResult(io, fail('project.ontology.module', 'MISSING_FIELDS', '--id, --path, --session required with --put', {}, ['Provide all required fields']), options.json);
|
|
113
|
-
process.exitCode = 1;
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const risk = (options.risk === 'low' || options.risk === 'medium' || options.risk === 'high') ? options.risk : undefined;
|
|
117
|
-
const result = upsertModule(options.project, defined({
|
|
118
|
-
id: options.id,
|
|
119
|
-
path: options.path,
|
|
120
|
-
session: options.session,
|
|
121
|
-
risk,
|
|
122
|
-
summary: options.summary
|
|
123
|
-
}));
|
|
124
|
-
printResult(io, ok('project.ontology.module', { modules: result.modules }), options.json);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const onto = loadOntology(options.project) ?? generateProjectContext(options.project).ontology;
|
|
128
|
-
if (options.id) {
|
|
129
|
-
const mod = onto.modules.find((m) => m.id === options.id);
|
|
130
|
-
printResult(io, ok('project.ontology.module', mod ?? { notFound: true, id: options.id }), options.json);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
printResult(io, ok('project.ontology.module', { modules: onto.modules }), options.json);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
printResult(io, fail('project.ontology.module', 'ONTOLOGY_MODULE_FAILED', getErrorMessage(error), {}, []), options.json);
|
|
137
|
-
process.exitCode = 1;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
addJsonOption(ontology
|
|
141
|
-
.command('decision')
|
|
142
|
-
.description('Record or query architectural decisions')
|
|
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')
|
|
143
75
|
.requiredOption('--project <path>', 'target project root')
|
|
144
|
-
.option('--
|
|
145
|
-
.option('--what <text>', 'what was decided')
|
|
146
|
-
.option('--why <text>', 'rationale behind the decision')
|
|
147
|
-
.option('--scope <modules>', 'comma-separated module ids')
|
|
148
|
-
.option('--session <id>', 'session id')
|
|
149
|
-
.option('--date <date>', 'decision date')
|
|
150
|
-
.option('--put', 'write/update the decision')).action((options) => {
|
|
76
|
+
.option('--kind <kind>', 'filter by kind: project, rule, decision, reference, feedback, convention, module')).action((options) => {
|
|
151
77
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
session: options.session, date: options.date
|
|
162
|
-
}));
|
|
163
|
-
printResult(io, ok('project.ontology.decision', { decisions: result.decisions }), options.json);
|
|
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);
|
|
164
87
|
return;
|
|
165
88
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
printResult(io, ok('project.ontology.decision', { decisions: onto.decisions }), options.json);
|
|
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);
|
|
173
95
|
}
|
|
174
96
|
catch (error) {
|
|
175
|
-
printResult(io, fail('project.
|
|
97
|
+
printResult(io, fail('project.memories', 'PROJECT_MEMORIES_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path and .peaks/memory directory']), options.json);
|
|
176
98
|
process.exitCode = 1;
|
|
177
99
|
}
|
|
178
100
|
});
|
|
@@ -2,48 +2,10 @@ export type ProjectContextSection = {
|
|
|
2
2
|
heading: string;
|
|
3
3
|
body: string;
|
|
4
4
|
};
|
|
5
|
-
export type Module = {
|
|
6
|
-
id: string;
|
|
7
|
-
path: string;
|
|
8
|
-
risk?: 'low' | 'medium' | 'high';
|
|
9
|
-
sessions: string[];
|
|
10
|
-
summary?: string;
|
|
11
|
-
};
|
|
12
|
-
export type Decision = {
|
|
13
|
-
id: string;
|
|
14
|
-
what: string;
|
|
15
|
-
why?: string;
|
|
16
|
-
scope: string[];
|
|
17
|
-
session: string;
|
|
18
|
-
date: string;
|
|
19
|
-
};
|
|
20
|
-
export type Convention = {
|
|
21
|
-
id: string;
|
|
22
|
-
rule: string;
|
|
23
|
-
category: 'code-style' | 'architecture' | 'naming' | 'tooling' | 'other';
|
|
24
|
-
source: string;
|
|
25
|
-
date: string;
|
|
26
|
-
};
|
|
27
|
-
export type Ontology = {
|
|
28
|
-
version: 1;
|
|
29
|
-
updated: string;
|
|
30
|
-
project: string;
|
|
31
|
-
modules: Module[];
|
|
32
|
-
decisions: Decision[];
|
|
33
|
-
conventions: Convention[];
|
|
34
|
-
};
|
|
35
|
-
export declare function loadOntology(projectRoot: string): Ontology | null;
|
|
36
|
-
export declare function saveOntology(projectRoot: string, onto: Ontology): void;
|
|
37
|
-
export declare function upsertModule(projectRoot: string, mod: Omit<Module, 'sessions'> & {
|
|
38
|
-
session: string;
|
|
39
|
-
}): Ontology;
|
|
40
|
-
export declare function upsertDecision(projectRoot: string, dec: Decision): Ontology;
|
|
41
|
-
export declare function upsertConvention(projectRoot: string, conv: Convention): Ontology;
|
|
42
5
|
export declare function generateProjectContext(projectRoot: string): {
|
|
43
6
|
path: string;
|
|
44
7
|
content: string;
|
|
45
8
|
sessionCount: number;
|
|
46
|
-
ontology: Ontology;
|
|
47
9
|
};
|
|
48
10
|
export declare function readProjectContext(projectRoot: string): string | null;
|
|
49
11
|
export declare function getProjectContextPath(projectRoot: string): string;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import { listSessionMetas } from '../session/session-manager.js';
|
|
4
4
|
const PROJECT_CONTEXT_FILE = '.peaks/PROJECT.md';
|
|
5
|
-
const ONTOLOGY_FILE = '.peaks/ontology.json';
|
|
6
5
|
const CONTEXT_HEADER = `# Peaks Project Context
|
|
7
6
|
|
|
8
7
|
> Auto-generated project memory. Peaks reads this at the start of each session to understand
|
|
@@ -46,26 +45,6 @@ function listMdFiles(dir, maxDepth = 3) {
|
|
|
46
45
|
}
|
|
47
46
|
return results;
|
|
48
47
|
}
|
|
49
|
-
function extractArtifactSummary(filePath, sessionRoot) {
|
|
50
|
-
try {
|
|
51
|
-
const content = readFileSync(filePath, 'utf8');
|
|
52
|
-
const lines = content.split(/\r?\n/);
|
|
53
|
-
const firstHeading = lines.find((l) => /^#\s/.test(l))?.replace(/^#\s+/, '').trim();
|
|
54
|
-
const stateLine = lines.find((l) => /^\-\s*state:\s*/.test(l))?.trim();
|
|
55
|
-
const relPath = relative(sessionRoot, filePath).split(/[\\/]/).join('/');
|
|
56
|
-
const parts = [];
|
|
57
|
-
if (firstHeading)
|
|
58
|
-
parts.push(firstHeading);
|
|
59
|
-
if (stateLine)
|
|
60
|
-
parts.push(stateLine.replace(/^-\s*state:\s*/, ''));
|
|
61
|
-
if (parts.length === 0)
|
|
62
|
-
return `- \`${relPath}\``;
|
|
63
|
-
return `- \`${relPath}\` — ${parts.join(' | ')}`;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
48
|
function extractOneLineSummary(sessionRoot) {
|
|
70
49
|
const artifacts = listMdFiles(sessionRoot, 4);
|
|
71
50
|
for (const artifact of artifacts.slice(0, 5)) {
|
|
@@ -88,27 +67,6 @@ function extractOneLineSummary(sessionRoot) {
|
|
|
88
67
|
}
|
|
89
68
|
return null;
|
|
90
69
|
}
|
|
91
|
-
function renderSessionBlock(meta, projectRoot) {
|
|
92
|
-
const title = meta.title ?? 'Untitled';
|
|
93
|
-
const date = meta.createdAt ? meta.createdAt.slice(0, 10) : '?';
|
|
94
|
-
const skill = meta.skill ?? '-';
|
|
95
|
-
const mode = meta.mode ?? '-';
|
|
96
|
-
let block = `### ${date} — ${title}\n`;
|
|
97
|
-
block += `- ${skill} (${mode})`;
|
|
98
|
-
const sessionRoot = join(projectRoot, '.peaks', meta.sessionId);
|
|
99
|
-
const summary = extractOneLineSummary(sessionRoot);
|
|
100
|
-
if (summary) {
|
|
101
|
-
block += ` — ${summary.slice(0, 120)}`;
|
|
102
|
-
}
|
|
103
|
-
block += '\n';
|
|
104
|
-
// Key artifact paths only
|
|
105
|
-
const artifacts = listMdFiles(sessionRoot, 3);
|
|
106
|
-
if (artifacts.length > 0) {
|
|
107
|
-
const paths = artifacts.slice(0, 8).map((f) => relative(sessionRoot, f).split(/[\\/]/).join('/'));
|
|
108
|
-
block += ` ${paths.join(' ')}\n`;
|
|
109
|
-
}
|
|
110
|
-
return block;
|
|
111
|
-
}
|
|
112
70
|
function buildSessionHistory(projectRoot) {
|
|
113
71
|
const metas = listSessionMetas(projectRoot);
|
|
114
72
|
if (metas.length === 0) {
|
|
@@ -139,263 +97,6 @@ function buildSessionHistory(projectRoot) {
|
|
|
139
97
|
body += `\n${MANAGED_BLOCK_END}`;
|
|
140
98
|
return body;
|
|
141
99
|
}
|
|
142
|
-
// --- Ontology engine ---
|
|
143
|
-
function emptyOntology(projectName) {
|
|
144
|
-
return {
|
|
145
|
-
version: 1,
|
|
146
|
-
updated: new Date().toISOString(),
|
|
147
|
-
project: projectName,
|
|
148
|
-
modules: [],
|
|
149
|
-
decisions: [],
|
|
150
|
-
conventions: []
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function ontoPath(projectRoot) {
|
|
154
|
-
return join(projectRoot, ONTOLOGY_FILE);
|
|
155
|
-
}
|
|
156
|
-
export function loadOntology(projectRoot) {
|
|
157
|
-
const path = ontoPath(projectRoot);
|
|
158
|
-
if (!existsSync(path))
|
|
159
|
-
return null;
|
|
160
|
-
try {
|
|
161
|
-
const raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
162
|
-
if (raw?.version === 1 && Array.isArray(raw.modules)) {
|
|
163
|
-
return raw;
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function slugify(text) {
|
|
172
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'unknown';
|
|
173
|
-
}
|
|
174
|
-
function scanModulesFromArtifacts(sessionRoot, sessionId) {
|
|
175
|
-
const artifacts = listMdFiles(sessionRoot, 4);
|
|
176
|
-
const modules = [];
|
|
177
|
-
const seen = new Set();
|
|
178
|
-
for (const artifact of artifacts.slice(0, 10)) {
|
|
179
|
-
try {
|
|
180
|
-
const content = readFileSync(artifact, 'utf8');
|
|
181
|
-
// Extract file paths — non-capturing groups for extensions
|
|
182
|
-
const patterns = [
|
|
183
|
-
/\b(src\/[^\s)`\]}"]+\.(?:tsx?|jsx?|css|less|scss|vue|svelte))\b/g,
|
|
184
|
-
/\b(packages\/[^\s)`\]}"]+\.(?:tsx?|jsx?))\b/g
|
|
185
|
-
];
|
|
186
|
-
for (const pattern of patterns) {
|
|
187
|
-
let m;
|
|
188
|
-
while ((m = pattern.exec(content)) !== null) {
|
|
189
|
-
const filePath = m[1] ?? '';
|
|
190
|
-
if (!filePath || filePath.length > 120 || filePath.length < 5)
|
|
191
|
-
continue;
|
|
192
|
-
const id = slugify(filePath.replace(/\.[^.]+$/, '').replace(/[\/\\]/g, '-'));
|
|
193
|
-
if (seen.has(id))
|
|
194
|
-
continue;
|
|
195
|
-
seen.add(id);
|
|
196
|
-
modules.push({ id, path: filePath });
|
|
197
|
-
if (modules.length >= 30)
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
if (modules.length >= 30)
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
// skip unreadable
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return modules;
|
|
209
|
-
}
|
|
210
|
-
function scanDecisionsFromArtifacts(sessionRoot, session) {
|
|
211
|
-
const artifacts = listMdFiles(sessionRoot, 4);
|
|
212
|
-
const decisions = [];
|
|
213
|
-
const date = session.createdAt ? session.createdAt.slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
214
|
-
for (const artifact of artifacts.slice(0, 10)) {
|
|
215
|
-
try {
|
|
216
|
-
const content = readFileSync(artifact, 'utf8');
|
|
217
|
-
// Look for decision markers: "- Decision: ..." or "Decision: ..." or "ADR: ..."
|
|
218
|
-
const decRegex = /^[\s-]*(?:decision|adr|决定|决策)\s*:\s*(.+?)$/gim;
|
|
219
|
-
let m;
|
|
220
|
-
while ((m = decRegex.exec(content)) !== null) {
|
|
221
|
-
const what = (m[1] ?? '').trim().slice(0, 200);
|
|
222
|
-
if (what.length < 5)
|
|
223
|
-
continue;
|
|
224
|
-
const id = slugify(what.slice(0, 40));
|
|
225
|
-
// Collect scope from surrounding context (modules mentioned within 3 lines before/after)
|
|
226
|
-
const scope = [];
|
|
227
|
-
const lineIdx = content.slice(0, m.index).split('\n').length;
|
|
228
|
-
const lines = content.split('\n');
|
|
229
|
-
for (let i = Math.max(0, lineIdx - 3); i < Math.min(lines.length, lineIdx + 3); i++) {
|
|
230
|
-
const line = lines[i] ?? '';
|
|
231
|
-
const pathMatch = /src\/[^\s)`\]}"]+\.(tsx?|jsx?)/.exec(line);
|
|
232
|
-
if (pathMatch?.[0])
|
|
233
|
-
scope.push(pathMatch[0]);
|
|
234
|
-
}
|
|
235
|
-
decisions.push({ id, what, scope: [...new Set(scope)].slice(0, 5), session: session.sessionId, date });
|
|
236
|
-
if (decisions.length >= 10)
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// skip unreadable
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return decisions;
|
|
245
|
-
}
|
|
246
|
-
function scanConventionsFromArtifacts(sessionRoot, session) {
|
|
247
|
-
const artifacts = listMdFiles(sessionRoot, 4);
|
|
248
|
-
const conventions = [];
|
|
249
|
-
const date = session.createdAt ? session.createdAt.slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
250
|
-
for (const artifact of artifacts.slice(0, 10)) {
|
|
251
|
-
try {
|
|
252
|
-
const content = readFileSync(artifact, 'utf8');
|
|
253
|
-
const convRegex = /^[\s-]*(?:convention|约定|规范)\s*:\s*(.+?)$/gim;
|
|
254
|
-
let m;
|
|
255
|
-
while ((m = convRegex.exec(content)) !== null) {
|
|
256
|
-
const rule = (m[1] ?? '').trim().slice(0, 200);
|
|
257
|
-
if (rule.length < 5)
|
|
258
|
-
continue;
|
|
259
|
-
const id = slugify(rule.slice(0, 40));
|
|
260
|
-
// Infer category from keywords
|
|
261
|
-
let category = 'other';
|
|
262
|
-
if (/class|function|interface|type|hook|component/i.test(rule))
|
|
263
|
-
category = 'code-style';
|
|
264
|
-
else if (/service|layer|package|module|shared|extract/i.test(rule))
|
|
265
|
-
category = 'architecture';
|
|
266
|
-
else if (/naming|命名|文件名|prefix|suffix/i.test(rule))
|
|
267
|
-
category = 'naming';
|
|
268
|
-
else if (/tooling|lint|format|build|test/i.test(rule))
|
|
269
|
-
category = 'tooling';
|
|
270
|
-
conventions.push({ id, rule, category, source: session.sessionId, date });
|
|
271
|
-
if (conventions.length >= 10)
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
// skip unreadable
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return conventions;
|
|
280
|
-
}
|
|
281
|
-
function buildOntology(projectRoot) {
|
|
282
|
-
const name = projectName(projectRoot);
|
|
283
|
-
const existing = loadOntology(projectRoot);
|
|
284
|
-
const onto = existing ?? emptyOntology(name);
|
|
285
|
-
onto.updated = new Date().toISOString();
|
|
286
|
-
onto.project = name;
|
|
287
|
-
const metas = listSessionMetas(projectRoot);
|
|
288
|
-
const knownSessions = new Set(metas.map((m) => m.sessionId));
|
|
289
|
-
// Prune: remove modules/decisions/conventions from sessions that no longer exist
|
|
290
|
-
onto.modules = onto.modules.filter((m) => m.sessions.some((s) => knownSessions.has(s)));
|
|
291
|
-
onto.decisions = onto.decisions.filter((d) => knownSessions.has(d.session));
|
|
292
|
-
onto.conventions = onto.conventions.filter((c) => knownSessions.has(c.source));
|
|
293
|
-
// Merge: scan each session for new modules and decisions
|
|
294
|
-
const moduleMap = new Map();
|
|
295
|
-
for (const m of onto.modules)
|
|
296
|
-
moduleMap.set(m.id, m);
|
|
297
|
-
const decisionMap = new Map();
|
|
298
|
-
for (const d of onto.decisions)
|
|
299
|
-
decisionMap.set(d.id, d);
|
|
300
|
-
for (const meta of metas) {
|
|
301
|
-
const sessionRoot = join(projectRoot, '.peaks', meta.sessionId);
|
|
302
|
-
// Modules
|
|
303
|
-
const foundModules = scanModulesFromArtifacts(sessionRoot, meta.sessionId);
|
|
304
|
-
for (const fm of foundModules) {
|
|
305
|
-
if (moduleMap.has(fm.id)) {
|
|
306
|
-
const existing = moduleMap.get(fm.id);
|
|
307
|
-
if (!existing.sessions.includes(meta.sessionId)) {
|
|
308
|
-
existing.sessions.push(meta.sessionId);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
moduleMap.set(fm.id, {
|
|
313
|
-
id: fm.id,
|
|
314
|
-
path: fm.path,
|
|
315
|
-
sessions: [meta.sessionId]
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// Decisions
|
|
320
|
-
const foundDecisions = scanDecisionsFromArtifacts(sessionRoot, meta);
|
|
321
|
-
for (const fd of foundDecisions) {
|
|
322
|
-
if (!decisionMap.has(fd.id)) {
|
|
323
|
-
decisionMap.set(fd.id, fd);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
// Conventions
|
|
327
|
-
const foundConventions = scanConventionsFromArtifacts(sessionRoot, meta);
|
|
328
|
-
const convMap = new Map();
|
|
329
|
-
for (const c of onto.conventions)
|
|
330
|
-
convMap.set(c.id, c);
|
|
331
|
-
for (const fc of foundConventions) {
|
|
332
|
-
if (!convMap.has(fc.id)) {
|
|
333
|
-
convMap.set(fc.id, fc);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
onto.conventions = [...convMap.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
337
|
-
}
|
|
338
|
-
// Dedup: remove shorter paths that are substring-matches of longer paths
|
|
339
|
-
const modules = [...moduleMap.values()];
|
|
340
|
-
const deduped = modules.filter((m) => {
|
|
341
|
-
return !modules.some((other) => other !== m && other.path.length > m.path.length && other.path.endsWith(m.path));
|
|
342
|
-
});
|
|
343
|
-
onto.modules = deduped.sort((a, b) => b.sessions.length - a.sessions.length);
|
|
344
|
-
onto.decisions = [...decisionMap.values()].sort((a, b) => b.date.localeCompare(a.date));
|
|
345
|
-
return onto;
|
|
346
|
-
}
|
|
347
|
-
export function saveOntology(projectRoot, onto) {
|
|
348
|
-
const peaksDir = join(projectRoot, '.peaks');
|
|
349
|
-
if (!existsSync(peaksDir))
|
|
350
|
-
mkdirSync(peaksDir, { recursive: true });
|
|
351
|
-
writeFileSync(ontoPath(projectRoot), JSON.stringify(onto, null, 2), 'utf8');
|
|
352
|
-
}
|
|
353
|
-
// Mutations for skills to call when they discover new facts
|
|
354
|
-
export function upsertModule(projectRoot, mod) {
|
|
355
|
-
const onto = buildOntology(projectRoot);
|
|
356
|
-
const existing = onto.modules.find((m) => m.id === mod.id);
|
|
357
|
-
if (existing) {
|
|
358
|
-
if (!existing.sessions.includes(mod.session))
|
|
359
|
-
existing.sessions.push(mod.session);
|
|
360
|
-
if (mod.risk)
|
|
361
|
-
existing.risk = mod.risk;
|
|
362
|
-
if (mod.summary)
|
|
363
|
-
existing.summary = mod.summary;
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
onto.modules.push({ ...mod, sessions: [mod.session] });
|
|
367
|
-
}
|
|
368
|
-
onto.updated = new Date().toISOString();
|
|
369
|
-
saveOntology(projectRoot, onto);
|
|
370
|
-
return onto;
|
|
371
|
-
}
|
|
372
|
-
export function upsertDecision(projectRoot, dec) {
|
|
373
|
-
const onto = buildOntology(projectRoot);
|
|
374
|
-
const idx = onto.decisions.findIndex((d) => d.id === dec.id);
|
|
375
|
-
if (idx >= 0) {
|
|
376
|
-
onto.decisions[idx] = dec;
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
onto.decisions.push(dec);
|
|
380
|
-
}
|
|
381
|
-
onto.updated = new Date().toISOString();
|
|
382
|
-
saveOntology(projectRoot, onto);
|
|
383
|
-
return onto;
|
|
384
|
-
}
|
|
385
|
-
export function upsertConvention(projectRoot, conv) {
|
|
386
|
-
const onto = buildOntology(projectRoot);
|
|
387
|
-
const idx = onto.conventions.findIndex((c) => c.id === conv.id);
|
|
388
|
-
if (idx >= 0) {
|
|
389
|
-
onto.conventions[idx] = conv;
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
onto.conventions.push(conv);
|
|
393
|
-
}
|
|
394
|
-
onto.updated = new Date().toISOString();
|
|
395
|
-
saveOntology(projectRoot, onto);
|
|
396
|
-
return onto;
|
|
397
|
-
}
|
|
398
|
-
// --- Context generator (unified: PROJECT.md + ontology.json) ---
|
|
399
100
|
export function generateProjectContext(projectRoot) {
|
|
400
101
|
const peaksDir = join(projectRoot, '.peaks');
|
|
401
102
|
if (!existsSync(peaksDir)) {
|
|
@@ -428,10 +129,7 @@ export function generateProjectContext(projectRoot) {
|
|
|
428
129
|
content = header + '\n' + sessionHistory + '\n';
|
|
429
130
|
}
|
|
430
131
|
writeFileSync(contextPath, content, 'utf8');
|
|
431
|
-
|
|
432
|
-
const ontology = buildOntology(projectRoot);
|
|
433
|
-
saveOntology(projectRoot, ontology);
|
|
434
|
-
return { path: contextPath, content, sessionCount: listSessionMetas(projectRoot).length, ontology };
|
|
132
|
+
return { path: contextPath, content, sessionCount: listSessionMetas(projectRoot).length };
|
|
435
133
|
}
|
|
436
134
|
export function readProjectContext(projectRoot) {
|
|
437
135
|
const contextPath = join(projectRoot, PROJECT_CONTEXT_FILE);
|
|
@@ -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
|
+
}
|
|
@@ -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
|
@@ -14,13 +14,13 @@ Before any analysis or tool call, immediately run:
|
|
|
14
14
|
```bash
|
|
15
15
|
peaks skill presence:set peaks-prd --project <repo> --mode <mode> --gate startup
|
|
16
16
|
```
|
|
17
|
-
Read persistent project memory via CLI (
|
|
17
|
+
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
peaks project
|
|
20
|
+
peaks project memories --project <repo> --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This returns `.peaks/
|
|
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
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>`.
|
|
25
25
|
|
|
26
26
|
## Responsibilities
|
package/skills/peaks-qa/SKILL.md
CHANGED
|
@@ -14,13 +14,13 @@ Before any analysis or tool call, immediately run:
|
|
|
14
14
|
```bash
|
|
15
15
|
peaks skill presence:set peaks-qa --project <repo> --mode <mode> --gate startup
|
|
16
16
|
```
|
|
17
|
-
Read persistent project memory via CLI (
|
|
17
|
+
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
peaks project
|
|
20
|
+
peaks project memories --project <repo> --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This returns `.peaks/
|
|
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
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>`.
|
|
25
25
|
|
|
26
26
|
## Responsibilities
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -14,13 +14,13 @@ Before any analysis or tool call, immediately run:
|
|
|
14
14
|
```bash
|
|
15
15
|
peaks skill presence:set peaks-rd --project <repo> --mode <mode> --gate startup
|
|
16
16
|
```
|
|
17
|
-
Read persistent project memory via CLI (
|
|
17
|
+
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
peaks project
|
|
20
|
+
peaks project memories --project <repo> --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This returns `.peaks/
|
|
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
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>`.
|
|
25
25
|
|
|
26
26
|
## Responsibilities
|
package/skills/peaks-sc/SKILL.md
CHANGED
|
@@ -14,13 +14,13 @@ Before any analysis or tool call, immediately run:
|
|
|
14
14
|
```bash
|
|
15
15
|
peaks skill presence:set peaks-sc --project <repo> --mode <mode> --gate startup
|
|
16
16
|
```
|
|
17
|
-
Read persistent project memory via CLI (
|
|
17
|
+
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
peaks project
|
|
20
|
+
peaks project memories --project <repo> --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This returns `.peaks/
|
|
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
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>`.
|
|
25
25
|
|
|
26
26
|
## Responsibilities
|
|
@@ -56,7 +56,7 @@ Use gstack as a concrete source-control and release workflow reference for the `
|
|
|
56
56
|
|
|
57
57
|
## Project memory backup
|
|
58
58
|
|
|
59
|
-
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.
|
|
60
60
|
|
|
61
61
|
## Commit boundary derivation
|
|
62
62
|
|
|
@@ -78,22 +78,23 @@ Then display the compact status header: `Peaks-Cli Skill: peaks-solo | Peaks-Cli
|
|
|
78
78
|
|
|
79
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
80
|
|
|
81
|
-
### Peaks-Cli Step 2.3: Load project memory (
|
|
81
|
+
### Peaks-Cli Step 2.3: Load project memory (durable, LLM-authored memories)
|
|
82
82
|
|
|
83
|
-
Before planning any work, read the project's persistent memory —
|
|
83
|
+
Before planning any work, read the project's persistent memory — durable memories that survive across sessions:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
peaks project
|
|
86
|
+
peaks project memories --project <repo> --json
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
This returns `.peaks/
|
|
90
|
-
- **
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
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
|
|
93
94
|
|
|
94
|
-
Use this to understand what exists, what was decided, and what to avoid re-litigating.
|
|
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`.
|
|
95
96
|
|
|
96
|
-
`.peaks/PROJECT.md` is a human-readable timeline only — do NOT use it for LLM context.
|
|
97
|
+
`.peaks/PROJECT.md` is a human-readable session timeline only — do NOT use it for LLM context.
|
|
97
98
|
|
|
98
99
|
### Peaks-Cli Step 2.5: Set session title
|
|
99
100
|
|
|
@@ -14,13 +14,13 @@ Before any analysis or tool call, immediately run:
|
|
|
14
14
|
```bash
|
|
15
15
|
peaks skill presence:set peaks-txt --project <repo> --mode <mode> --gate startup
|
|
16
16
|
```
|
|
17
|
-
Read persistent project memory via CLI (
|
|
17
|
+
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
peaks project
|
|
20
|
+
peaks project memories --project <repo> --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This returns `.peaks/
|
|
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
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>`.
|
|
25
25
|
|
|
26
26
|
## Responsibilities
|
|
@@ -96,7 +96,7 @@ Each entry should include:
|
|
|
96
96
|
- why it exists;
|
|
97
97
|
- affected skills;
|
|
98
98
|
- how future PRD/RD/UI/QA/SC/Solo workflows should apply it;
|
|
99
|
-
- whether it is stable enough for `.
|
|
99
|
+
- whether it is stable enough for `.peaks/memory` extraction.
|
|
100
100
|
|
|
101
101
|
## Project memory guidance
|
|
102
102
|
|
|
@@ -111,7 +111,7 @@ Stable memory body.
|
|
|
111
111
|
<!-- peaks-memory:end -->
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
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.
|
|
115
115
|
|
|
116
116
|
## Matt Pocock skills integration
|
|
117
117
|
|
package/skills/peaks-ui/SKILL.md
CHANGED
|
@@ -14,13 +14,13 @@ Before any analysis or tool call, immediately run:
|
|
|
14
14
|
```bash
|
|
15
15
|
peaks skill presence:set peaks-ui --project <repo> --mode <mode> --gate startup
|
|
16
16
|
```
|
|
17
|
-
Read persistent project memory via CLI (
|
|
17
|
+
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
peaks project
|
|
20
|
+
peaks project memories --project <repo> --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This returns `.peaks/
|
|
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
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>`.
|
|
25
25
|
|
|
26
26
|
## Responsibilities
|