peaks-cli 1.2.3 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/commands/openspec-commands.js +31 -0
- package/dist/src/cli/commands/project-commands.js +51 -1
- package/dist/src/cli/commands/sop-commands.js +2 -2
- package/dist/src/cli/commands/workspace-commands.js +38 -4
- package/dist/src/services/memory/project-memory-service.d.ts +50 -0
- package/dist/src/services/memory/project-memory-service.js +412 -35
- package/dist/src/services/openspec/openspec-init-service.d.ts +23 -0
- package/dist/src/services/openspec/openspec-init-service.js +122 -0
- package/dist/src/services/session/index.d.ts +1 -1
- package/dist/src/services/session/index.js +1 -1
- package/dist/src/services/session/session-manager.d.ts +11 -0
- package/dist/src/services/session/session-manager.js +19 -0
- package/dist/src/services/skills/skill-presence-service.js +11 -0
- package/dist/src/services/sop/sop-check-service.d.ts +16 -0
- package/dist/src/services/sop/sop-check-service.js +35 -2
- package/dist/src/services/sop/sop-service.d.ts +8 -0
- package/dist/src/services/sop/sop-service.js +13 -2
- package/dist/src/services/sop/sop-types.d.ts +7 -0
- package/dist/src/services/workspace/workspace-service.d.ts +15 -0
- package/dist/src/services/workspace/workspace-service.js +60 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-prd/SKILL.md +36 -0
- package/skills/peaks-qa/SKILL.md +92 -2
- package/skills/peaks-rd/SKILL.md +70 -2
- package/skills/peaks-solo/SKILL.md +253 -40
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +186 -0
- package/skills/peaks-sop/SKILL.md +17 -0
- package/skills/peaks-sop/references/sop-authoring.md +1 -1
- package/skills/peaks-txt/SKILL.md +16 -0
- package/skills/peaks-ui/SKILL.md +61 -2
|
@@ -4,6 +4,7 @@ import { projectOpenSpecToRdInput } from '../../services/openspec/openspec-bridg
|
|
|
4
4
|
import { renderOpenSpecChange } from '../../services/openspec/openspec-render-service.js';
|
|
5
5
|
import { validateOpenSpecChange } from '../../services/openspec/openspec-validate-service.js';
|
|
6
6
|
import { archiveOpenSpecChange } from '../../services/openspec/openspec-archive-service.js';
|
|
7
|
+
import { executeOpenSpecInit } from '../../services/openspec/openspec-init-service.js';
|
|
7
8
|
import { fail, ok } from '../../shared/result.js';
|
|
8
9
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
9
10
|
function resolveScanOptions(project) {
|
|
@@ -166,4 +167,34 @@ export function registerOpenSpecCommands(program, io) {
|
|
|
166
167
|
process.exitCode = 1;
|
|
167
168
|
}
|
|
168
169
|
});
|
|
170
|
+
addJsonOption(openspec
|
|
171
|
+
.command('init')
|
|
172
|
+
.description('Scaffold the openspec/ directory in the target project (changes/, archive/, README.md, CHANGES.md). Idempotent — refuses to overwrite an existing openspec/')
|
|
173
|
+
.requiredOption('--project <path>', 'target project root')
|
|
174
|
+
.option('--apply', 'write files to disk (default: dry-run preview)', false)).action(async (options) => {
|
|
175
|
+
try {
|
|
176
|
+
const result = await executeOpenSpecInit({
|
|
177
|
+
projectRoot: options.project,
|
|
178
|
+
apply: options.apply === true
|
|
179
|
+
});
|
|
180
|
+
const nextActions = [];
|
|
181
|
+
if (result.alreadyInitialized) {
|
|
182
|
+
nextActions.push('openspec/ already exists; no files were created or modified.');
|
|
183
|
+
if (result.existingFiles.length > 0) {
|
|
184
|
+
nextActions.push(`Existing files preserved: ${result.existingFiles.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (!result.apply) {
|
|
188
|
+
nextActions.push('Re-run with --apply to write the planned files to disk.');
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
nextActions.push('Run `peaks openspec render --request <path> --apply` to scaffold a first change proposal.');
|
|
192
|
+
}
|
|
193
|
+
printResult(io, ok('openspec.init', result, [], nextActions), options.json);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
printResult(io, fail('openspec.init', 'OPENSPEC_INIT_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Verify the project path exists and is writable']), options.json);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
169
200
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadProjectDashboard } from '../../services/dashboard/project-dashboard-service.js';
|
|
2
2
|
import { generateProjectContext, readProjectContext } from '../../services/memory/project-context-service.js';
|
|
3
|
-
import { readProjectMemories } from '../../services/memory/project-memory-service.js';
|
|
3
|
+
import { extractSessionMemories, readMemoryIndex, readProjectMemories } from '../../services/memory/project-memory-service.js';
|
|
4
4
|
import { fail, ok } from '../../shared/result.js';
|
|
5
5
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
6
6
|
export function registerProjectCommands(program, io) {
|
|
@@ -68,6 +68,56 @@ export function registerProjectCommands(program, io) {
|
|
|
68
68
|
process.exitCode = 1;
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
+
// --- Extract memories from a session's artifacts into .peaks/memory ---
|
|
72
|
+
addJsonOption(project
|
|
73
|
+
.command('memories:extract')
|
|
74
|
+
.description('Scan a session artifact directory and extract <!-- peaks-memory:start --> blocks into .peaks/memory/')
|
|
75
|
+
.requiredOption('--session-id <id>', 'session id (e.g. 2026-05-29-session-89ff35)')
|
|
76
|
+
.requiredOption('--project <path>', 'target project root')
|
|
77
|
+
.option('--dry-run', 'preview writes without changing files', true)
|
|
78
|
+
.option('--apply', 'write extracted memories into .peaks/memory/')).action((options) => {
|
|
79
|
+
if (options.dryRun === true && options.apply === true) {
|
|
80
|
+
printResult(io, fail('project.memories:extract', 'INVALID_MEMORY_EXTRACT_FLAGS', 'Use either --dry-run or --apply, not both', { sessionId: options.sessionId, projectRoot: options.project }, ['Run without --apply to preview writes, or pass --apply to write memories']), options.json);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const result = extractSessionMemories({
|
|
86
|
+
projectRoot: options.project,
|
|
87
|
+
sessionId: options.sessionId,
|
|
88
|
+
apply: options.apply === true
|
|
89
|
+
});
|
|
90
|
+
printResult(io, ok('project.memories:extract', {
|
|
91
|
+
scannedFiles: result.scannedFiles,
|
|
92
|
+
extractedCount: result.extractedCount,
|
|
93
|
+
writtenFiles: result.writtenFiles,
|
|
94
|
+
memoryDir: result.primaryMemoryDir,
|
|
95
|
+
indexUpdated: result.updatedIndex
|
|
96
|
+
}), options.json);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
printResult(io, fail('project.memories:extract', 'MEMORY_EXTRACT_FAILED', getErrorMessage(error), { sessionId: options.sessionId, projectRoot: options.project }, ['Check the session-id and project path']), options.json);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// --- Read memory index (lightweight, always-safe to load) ---
|
|
104
|
+
addJsonOption(project
|
|
105
|
+
.command('memory-index')
|
|
106
|
+
.description('Read the memory index — lightweight hot/warm分层 view of all project memories')
|
|
107
|
+
.requiredOption('--project <path>', 'target project root')).action((options) => {
|
|
108
|
+
try {
|
|
109
|
+
const index = readMemoryIndex(options.project);
|
|
110
|
+
if (!index) {
|
|
111
|
+
printResult(io, ok('project.memory-index', { exists: false, message: 'No memory index found. Run `peaks project memories:extract` first.' }), options.json);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
printResult(io, ok('project.memory-index', { exists: true, index }), options.json);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
printResult(io, fail('project.memory-index', 'MEMORY_INDEX_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path and .peaks/memory directory']), options.json);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
71
121
|
// --- Structured project memory (durable, LLM-authored, stored under .peaks/memory) ---
|
|
72
122
|
addJsonOption(project
|
|
73
123
|
.command('memories')
|
|
@@ -105,8 +105,8 @@ export function registerSopCommands(program, io) {
|
|
|
105
105
|
});
|
|
106
106
|
addJsonOption(sop
|
|
107
107
|
.command('registry')
|
|
108
|
-
.description('List registered SOPs and gates (global;
|
|
109
|
-
.option('--project <path>', 'also include and prefer the repo layer (<path>/.peaks/sops)')).action(async (options) => {
|
|
108
|
+
.description('List registered SOPs and gates (global; merges in the cwd project layer by default)')
|
|
109
|
+
.option('--project <path>', 'also include and prefer the repo layer (<path>/.peaks/sops) (default: current directory)', '.')).action(async (options) => {
|
|
110
110
|
try {
|
|
111
111
|
const registry = await readRegistry(options.project);
|
|
112
112
|
printResult(io, ok('sop.registry', registry), options.json);
|
|
@@ -1,16 +1,39 @@
|
|
|
1
|
-
import { initWorkspace, InvalidSessionIdError } from '../../services/workspace/workspace-service.js';
|
|
1
|
+
import { initWorkspace, InvalidSessionIdError, ConflictingSessionError } from '../../services/workspace/workspace-service.js';
|
|
2
|
+
import { ensureSession } from '../../services/session/session-manager.js';
|
|
2
3
|
import { fail, ok } from '../../shared/result.js';
|
|
3
4
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
5
|
export function registerWorkspaceCommands(program, io) {
|
|
5
6
|
const workspace = program.command('workspace').description('Manage the Peaks per-session artifact workspace (.peaks/<session-id>/)');
|
|
6
7
|
addJsonOption(workspace
|
|
7
8
|
.command('init')
|
|
8
|
-
.description('Create the .peaks/<session-id>/ directory structure (prd, ui, rd, qa, sc, txt, system)
|
|
9
|
+
.description('Create the .peaks/<session-id>/ directory structure (prd, ui, rd, qa, sc, txt, system) and bind the session as the project current one. Pass --session-id to use a specific id, or omit it to auto-generate one (and adopt an existing binding if present).')
|
|
9
10
|
.requiredOption('--project <path>', 'target project root')
|
|
10
|
-
.
|
|
11
|
+
.option('--session-id <id>', 'optional session id in YYYY-MM-DD-<kebab-slug> format. When omitted, the CLI is the single source of truth: an existing binding is reused, otherwise a fresh id is auto-generated.')
|
|
12
|
+
.option('--allow-session-rebind', 'overwrite an existing session binding when the requested session id differs from the project current one', false)).action(async (options) => {
|
|
11
13
|
try {
|
|
12
|
-
|
|
14
|
+
// Resolve the session id. Two paths:
|
|
15
|
+
// - explicit --session-id: use it as the requested binding target
|
|
16
|
+
// (ConflictingSessionError fires if it conflicts with an in-flight
|
|
17
|
+
// session, unless --allow-session-rebind is set)
|
|
18
|
+
// - omitted: defer to ensureSession(), which reuses an existing
|
|
19
|
+
// binding or auto-generates a fresh one. The init then writes
|
|
20
|
+
// .session.json so the binding sticks.
|
|
21
|
+
let sessionId;
|
|
22
|
+
if (options.sessionId !== undefined && options.sessionId.length > 0) {
|
|
23
|
+
sessionId = options.sessionId;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
sessionId = await ensureSession(options.project);
|
|
27
|
+
}
|
|
28
|
+
const report = await initWorkspace({
|
|
29
|
+
projectRoot: options.project,
|
|
30
|
+
sessionId,
|
|
31
|
+
allowSessionRebind: options.allowSessionRebind === true
|
|
32
|
+
});
|
|
13
33
|
const nextActions = [];
|
|
34
|
+
if (report.previousSessionId !== null && report.bound) {
|
|
35
|
+
nextActions.push(`Replaced prior session binding "${report.previousSessionId}" with "${report.sessionId}".`);
|
|
36
|
+
}
|
|
14
37
|
if (report.created.length === 0) {
|
|
15
38
|
nextActions.push('Workspace already initialized — proceed to project scan.');
|
|
16
39
|
}
|
|
@@ -25,6 +48,17 @@ export function registerWorkspaceCommands(program, io) {
|
|
|
25
48
|
process.exitCode = 1;
|
|
26
49
|
return;
|
|
27
50
|
}
|
|
51
|
+
if (error instanceof ConflictingSessionError) {
|
|
52
|
+
printResult(io, fail('workspace.init', error.code, error.message, {
|
|
53
|
+
existingSessionId: error.existingSessionId,
|
|
54
|
+
requestedSessionId: error.requestedSessionId
|
|
55
|
+
}, [
|
|
56
|
+
`Finish or abandon session "${error.existingSessionId}" first, then re-run workspace init.`,
|
|
57
|
+
'Or pass --allow-session-rebind to override the binding (overwrites the prior binding).'
|
|
58
|
+
]), options.json);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
28
62
|
printResult(io, fail('workspace.init', 'WORKSPACE_INIT_FAILED', getErrorMessage(error), { projectRoot: options.project, sessionId: options.sessionId }, ['Verify the project path exists and is writable']), options.json);
|
|
29
63
|
process.exitCode = 1;
|
|
30
64
|
}
|
|
@@ -74,6 +74,36 @@ export type ProjectMemoryReadResult = {
|
|
|
74
74
|
byKind: Record<ProjectMemoryKind, StoredProjectMemory[]>;
|
|
75
75
|
memories: StoredProjectMemory[];
|
|
76
76
|
};
|
|
77
|
+
export type MemoryIndexEntry = {
|
|
78
|
+
name: string;
|
|
79
|
+
kind: ProjectMemoryKind;
|
|
80
|
+
description: string;
|
|
81
|
+
sourcePath: string;
|
|
82
|
+
sourceArtifact: string | null;
|
|
83
|
+
updatedAt: string;
|
|
84
|
+
};
|
|
85
|
+
export type MemoryIndex = {
|
|
86
|
+
version: 1;
|
|
87
|
+
updatedAt: string;
|
|
88
|
+
hot: Record<ProjectMemoryKind, MemoryIndexEntry[]>;
|
|
89
|
+
warm: Record<ProjectMemoryKind, MemoryIndexEntry[]>;
|
|
90
|
+
};
|
|
91
|
+
export type ExtractSessionMemoriesOptions = {
|
|
92
|
+
projectRoot: string;
|
|
93
|
+
sessionId: string;
|
|
94
|
+
apply?: boolean;
|
|
95
|
+
};
|
|
96
|
+
export type ExtractSessionMemoriesResult = {
|
|
97
|
+
apply: boolean;
|
|
98
|
+
projectRoot: string;
|
|
99
|
+
sessionId: string;
|
|
100
|
+
primaryMemoryDir: string;
|
|
101
|
+
memoryIndexPath: string;
|
|
102
|
+
scannedFiles: number;
|
|
103
|
+
extractedCount: number;
|
|
104
|
+
writtenFiles: string[];
|
|
105
|
+
updatedIndex: boolean;
|
|
106
|
+
};
|
|
77
107
|
type ExtractPlanOptions = {
|
|
78
108
|
projectRoot: string;
|
|
79
109
|
artifactPaths: string[];
|
|
@@ -84,6 +114,8 @@ type BackupPlanOptions = {
|
|
|
84
114
|
artifactWorkspacePath: string;
|
|
85
115
|
apply?: boolean;
|
|
86
116
|
};
|
|
117
|
+
export declare function readMemoryIndex(projectRoot: string): MemoryIndex | null;
|
|
118
|
+
export declare function extractSessionMemories(options: ExtractSessionMemoriesOptions): ExtractSessionMemoriesResult;
|
|
87
119
|
export declare function extractStableProjectMemories(content: string, sourceArtifact: string): ExtractedProjectMemory[];
|
|
88
120
|
export declare function createProjectMemoryExtractPlan(options: ExtractPlanOptions): ProjectMemoryExtractPlan;
|
|
89
121
|
export declare function executeProjectMemoryExtract(options: ExtractPlanOptions): ProjectMemoryExtractResult;
|
|
@@ -91,5 +123,23 @@ export declare function createProjectMemoryBackupPlan(options: BackupPlanOptions
|
|
|
91
123
|
export declare function executeProjectMemoryBackup(options: BackupPlanOptions): ProjectMemoryBackupResult;
|
|
92
124
|
export declare function summarizeProjectMemoryExtractResult(result: ProjectMemoryExtractResult): ProjectMemoryExtractSummary;
|
|
93
125
|
export declare function summarizeProjectMemoryBackupResult(result: ProjectMemoryBackupResult): ProjectMemoryBackupSummary;
|
|
126
|
+
/**
|
|
127
|
+
* Ensure `.peaks/memory/` and its `index.json` exist for a project, with
|
|
128
|
+
* the same full-shape empty index the generator emits when there are zero
|
|
129
|
+
* memories. Idempotent — safe to call on every skill activation.
|
|
130
|
+
*
|
|
131
|
+
* Why this exists: before this helper, `.peaks/memory/` was only created
|
|
132
|
+
* by `extractSessionMemories` when at least one memory markdown was being
|
|
133
|
+
* written, and `index.json` was only emitted by the generator when at
|
|
134
|
+
* least one markdown was on disk. Stock projects therefore had no
|
|
135
|
+
* `.peaks/memory/` directory and no index, even after `peaks project
|
|
136
|
+
* memories` was read. Bootstrap closes that cold-start gap.
|
|
137
|
+
*
|
|
138
|
+
* This function is fail-open for the same reason the rest of the
|
|
139
|
+
* presence layer is fail-open: a failure here must NOT block skill
|
|
140
|
+
* activation. Any error is swallowed and surfaced only via the returned
|
|
141
|
+
* boolean. Callers that need the truth should check the result.
|
|
142
|
+
*/
|
|
143
|
+
export declare function ensureMemoryBootstrap(projectRoot: string): boolean;
|
|
94
144
|
export declare function readProjectMemories(projectRoot: string): ProjectMemoryReadResult;
|
|
95
145
|
export {};
|