peaks-cli 1.0.12 → 1.0.13
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/config-commands.js +1 -17
- package/dist/src/cli/commands/core-artifact-commands.js +23 -0
- package/dist/src/cli/commands/mcp-commands.d.ts +3 -0
- package/dist/src/cli/commands/mcp-commands.js +144 -0
- package/dist/src/cli/commands/openspec-commands.d.ts +3 -0
- package/dist/src/cli/commands/openspec-commands.js +169 -0
- package/dist/src/cli/commands/project-commands.d.ts +3 -0
- package/dist/src/cli/commands/project-commands.js +37 -0
- package/dist/src/cli/commands/request-commands.d.ts +3 -0
- package/dist/src/cli/commands/request-commands.js +140 -0
- package/dist/src/cli/commands/understand-commands.d.ts +3 -0
- package/dist/src/cli/commands/understand-commands.js +78 -0
- package/dist/src/cli/commands/workflow-commands.js +56 -94
- package/dist/src/cli/program.js +10 -0
- package/dist/src/services/artifacts/request-artifact-service.d.ts +58 -0
- package/dist/src/services/artifacts/request-artifact-service.js +432 -0
- package/dist/src/services/codegraph/codegraph-service.js +26 -45
- package/dist/src/services/config/config-service.js +2 -22
- package/dist/src/services/dashboard/project-dashboard-service.d.ts +64 -0
- package/dist/src/services/dashboard/project-dashboard-service.js +112 -0
- package/dist/src/services/doctor/doctor-service.d.ts +7 -0
- package/dist/src/services/doctor/doctor-service.js +139 -0
- package/dist/src/services/mcp/mcp-apply-service.d.ts +31 -0
- package/dist/src/services/mcp/mcp-apply-service.js +112 -0
- package/dist/src/services/mcp/mcp-call-service.d.ts +17 -0
- package/dist/src/services/mcp/mcp-call-service.js +34 -0
- package/dist/src/services/mcp/mcp-client-service.d.ts +14 -0
- package/dist/src/services/mcp/mcp-client-service.js +49 -0
- package/dist/src/services/mcp/mcp-install-registry.d.ts +11 -0
- package/dist/src/services/mcp/mcp-install-registry.js +38 -0
- package/dist/src/services/mcp/mcp-plan-service.d.ts +29 -0
- package/dist/src/services/mcp/mcp-plan-service.js +109 -0
- package/dist/src/services/mcp/mcp-protocol.d.ts +24 -0
- package/dist/src/services/mcp/mcp-protocol.js +41 -0
- package/dist/src/services/mcp/mcp-scan-service.d.ts +8 -0
- package/dist/src/services/mcp/mcp-scan-service.js +214 -0
- package/dist/src/services/mcp/mcp-stdio-transport.d.ts +10 -0
- package/dist/src/services/mcp/mcp-stdio-transport.js +50 -0
- package/dist/src/services/mcp/mcp-types.d.ts +31 -0
- package/dist/src/services/mcp/mcp-types.js +1 -0
- package/dist/src/services/openspec/openspec-archive-service.d.ts +12 -0
- package/dist/src/services/openspec/openspec-archive-service.js +28 -0
- package/dist/src/services/openspec/openspec-bridge-service.d.ts +16 -0
- package/dist/src/services/openspec/openspec-bridge-service.js +76 -0
- package/dist/src/services/openspec/openspec-render-service.d.ts +38 -0
- package/dist/src/services/openspec/openspec-render-service.js +130 -0
- package/dist/src/services/openspec/openspec-scan-service.d.ts +6 -0
- package/dist/src/services/openspec/openspec-scan-service.js +123 -0
- package/dist/src/services/openspec/openspec-types.d.ts +39 -0
- package/dist/src/services/openspec/openspec-types.js +1 -0
- package/dist/src/services/openspec/openspec-validate-service.d.ts +27 -0
- package/dist/src/services/openspec/openspec-validate-service.js +77 -0
- package/dist/src/services/recommendations/capability-seed-items.js +2 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +1 -1
- package/dist/src/services/recommendations/capability-seed-sources.js +1 -1
- package/dist/src/services/shadcn/shadcn-service.d.ts +4 -0
- package/dist/src/services/shadcn/shadcn-service.js +15 -30
- package/dist/src/services/skills/skill-runbook-service.d.ts +11 -0
- package/dist/src/services/skills/skill-runbook-service.js +60 -0
- package/dist/src/services/standards/project-standards-service.js +4 -9
- package/dist/src/services/understand/understand-scan-service.d.ts +28 -0
- package/dist/src/services/understand/understand-scan-service.js +157 -0
- package/dist/src/services/understand/understand-types.d.ts +24 -0
- package/dist/src/services/understand/understand-types.js +1 -0
- package/dist/src/shared/json-schema-mini.d.ts +10 -0
- package/dist/src/shared/json-schema-mini.js +113 -0
- package/dist/src/shared/paths.d.ts +1 -1
- package/dist/src/shared/paths.js +9 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +2 -8
- package/schemas/doctor-report.schema.json +34 -0
- package/schemas/mcp-apply-result.schema.json +46 -0
- package/schemas/mcp-install-plan.schema.json +71 -0
- package/schemas/mcp-install-spec.schema.json +29 -0
- package/schemas/mcp-server.schema.json +29 -0
- package/schemas/openspec-change-summary.schema.json +68 -0
- package/schemas/openspec-render-request.schema.json +61 -0
- package/schemas/openspec-validation-result.schema.json +36 -0
- package/skills/peaks-prd/SKILL.md +59 -8
- package/skills/peaks-prd/references/artifact-per-request.md +78 -0
- package/skills/peaks-prd/references/workflow.md +7 -5
- package/skills/peaks-qa/SKILL.md +74 -8
- package/skills/peaks-qa/references/artifact-contracts.md +2 -2
- package/skills/peaks-qa/references/artifact-per-request.md +83 -0
- package/skills/peaks-qa/references/openspec-validation-gate.md +55 -0
- package/skills/peaks-qa/references/regression-gates.md +2 -2
- package/skills/peaks-rd/SKILL.md +96 -9
- package/skills/peaks-rd/references/artifact-contracts.md +2 -2
- package/skills/peaks-rd/references/artifact-per-request.md +90 -0
- package/skills/peaks-rd/references/openspec-mcp-cli.md +65 -0
- package/skills/peaks-rd/references/refactor-workflow.md +2 -2
- package/skills/peaks-sc/SKILL.md +44 -0
- package/skills/peaks-sc/references/openspec-commit-boundaries.md +33 -0
- package/skills/peaks-solo/SKILL.md +90 -9
- package/skills/peaks-solo/references/artifact-contracts.md +2 -2
- package/skills/peaks-solo/references/browser-workflow.md +114 -0
- package/skills/peaks-solo/references/external-skill-invocation.md +70 -0
- package/skills/peaks-solo/references/openspec-mcp-workflow.md +53 -0
- package/skills/peaks-solo/references/refactor-mode.md +2 -2
- package/skills/peaks-solo/references/workflow.md +1 -1
- package/skills/peaks-txt/SKILL.md +42 -0
- package/skills/peaks-ui/SKILL.md +57 -33
- package/skills/peaks-ui/references/artifact-per-request.md +71 -0
- package/skills/peaks-ui/references/workflow.md +8 -11
- package/scripts/strip-internal-exports.mjs +0 -33
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type JsonSchemaIssue } from '../../shared/json-schema-mini.js';
|
|
2
|
+
export type OpenSpecRenderTaskSection = {
|
|
3
|
+
heading: string;
|
|
4
|
+
todos: string[];
|
|
5
|
+
doneItems?: string[];
|
|
6
|
+
};
|
|
7
|
+
export type OpenSpecRenderRequest = {
|
|
8
|
+
changeId: string;
|
|
9
|
+
why: string;
|
|
10
|
+
whatChanges: string[];
|
|
11
|
+
acceptanceCriteria: string[];
|
|
12
|
+
outOfScope?: string[];
|
|
13
|
+
dependencies?: string[];
|
|
14
|
+
risks?: string[];
|
|
15
|
+
tasks?: OpenSpecRenderTaskSection[];
|
|
16
|
+
design?: string;
|
|
17
|
+
};
|
|
18
|
+
export type OpenSpecRenderedFile = {
|
|
19
|
+
path: string;
|
|
20
|
+
content: string;
|
|
21
|
+
};
|
|
22
|
+
export type OpenSpecRenderResult = {
|
|
23
|
+
changeId: string;
|
|
24
|
+
changeRoot: string;
|
|
25
|
+
files: OpenSpecRenderedFile[];
|
|
26
|
+
applied: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type OpenSpecRenderOptions = {
|
|
29
|
+
openspecRoot?: string;
|
|
30
|
+
apply?: boolean;
|
|
31
|
+
overwrite?: boolean;
|
|
32
|
+
schemaPath?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare class OpenSpecRenderRequestInvalidError extends Error {
|
|
35
|
+
readonly issues: JsonSchemaIssue[];
|
|
36
|
+
constructor(issues: JsonSchemaIssue[]);
|
|
37
|
+
}
|
|
38
|
+
export declare function renderOpenSpecChange(request: OpenSpecRenderRequest, options?: OpenSpecRenderOptions): Promise<OpenSpecRenderResult>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { isDirectory, pathExists } from '../../shared/fs.js';
|
|
5
|
+
import { validateAgainstSchema } from '../../shared/json-schema-mini.js';
|
|
6
|
+
const CHANGE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
7
|
+
function defaultOpenSpecRoot() {
|
|
8
|
+
return join(process.cwd(), 'openspec');
|
|
9
|
+
}
|
|
10
|
+
function defaultSchemaPath() {
|
|
11
|
+
const here = fileURLToPath(import.meta.url);
|
|
12
|
+
return resolve(here, '..', '..', '..', '..', 'schemas', 'openspec-render-request.schema.json');
|
|
13
|
+
}
|
|
14
|
+
let cachedSchema = null;
|
|
15
|
+
let cachedSchemaPath = null;
|
|
16
|
+
async function loadSchema(schemaPath) {
|
|
17
|
+
if (cachedSchema !== null && cachedSchemaPath === schemaPath) {
|
|
18
|
+
return cachedSchema;
|
|
19
|
+
}
|
|
20
|
+
if (!(await pathExists(schemaPath))) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const raw = await readFile(schemaPath, 'utf8');
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
cachedSchema = parsed;
|
|
26
|
+
cachedSchemaPath = schemaPath;
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
function formatSchemaIssue(issue) {
|
|
30
|
+
return `${issue.path}: ${issue.message}`;
|
|
31
|
+
}
|
|
32
|
+
export class OpenSpecRenderRequestInvalidError extends Error {
|
|
33
|
+
issues;
|
|
34
|
+
constructor(issues) {
|
|
35
|
+
const summary = issues.map(formatSchemaIssue).join('; ');
|
|
36
|
+
super(`OpenSpec render request failed schema validation: ${summary}`);
|
|
37
|
+
this.name = 'OpenSpecRenderRequestInvalidError';
|
|
38
|
+
this.issues = issues;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function renderBullets(items) {
|
|
42
|
+
if (items === undefined || items.length === 0) {
|
|
43
|
+
return '_None_\n';
|
|
44
|
+
}
|
|
45
|
+
return `${items.map((item) => `- ${item}`).join('\n')}\n`;
|
|
46
|
+
}
|
|
47
|
+
function renderProposal(request) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push(`# Change: ${request.changeId}`);
|
|
50
|
+
lines.push('');
|
|
51
|
+
lines.push('## Why');
|
|
52
|
+
lines.push('');
|
|
53
|
+
lines.push(request.why.length > 0 ? request.why : '_None_');
|
|
54
|
+
lines.push('');
|
|
55
|
+
lines.push('## What Changes');
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push(renderBullets(request.whatChanges));
|
|
58
|
+
lines.push('## Out of Scope');
|
|
59
|
+
lines.push('');
|
|
60
|
+
lines.push(renderBullets(request.outOfScope));
|
|
61
|
+
lines.push('## Dependencies');
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push(renderBullets(request.dependencies));
|
|
64
|
+
lines.push('## Risks');
|
|
65
|
+
lines.push('');
|
|
66
|
+
lines.push(renderBullets(request.risks));
|
|
67
|
+
lines.push('## Acceptance Criteria');
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(renderBullets(request.acceptanceCriteria));
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
function renderTasks(tasks) {
|
|
73
|
+
const sections = ['# Tasks', ''];
|
|
74
|
+
for (const task of tasks) {
|
|
75
|
+
sections.push(`## ${task.heading}`);
|
|
76
|
+
sections.push('');
|
|
77
|
+
for (const todo of task.todos) {
|
|
78
|
+
sections.push(`- [ ] ${todo}`);
|
|
79
|
+
}
|
|
80
|
+
if (task.doneItems !== undefined) {
|
|
81
|
+
for (const done of task.doneItems) {
|
|
82
|
+
sections.push(`- [x] ${done}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
sections.push('');
|
|
86
|
+
}
|
|
87
|
+
return sections.join('\n');
|
|
88
|
+
}
|
|
89
|
+
function buildFiles(request, changeRoot) {
|
|
90
|
+
const files = [
|
|
91
|
+
{ path: join(changeRoot, 'proposal.md'), content: renderProposal(request) }
|
|
92
|
+
];
|
|
93
|
+
if (request.tasks !== undefined && request.tasks.length > 0) {
|
|
94
|
+
files.push({ path: join(changeRoot, 'tasks.md'), content: renderTasks(request.tasks) });
|
|
95
|
+
}
|
|
96
|
+
if (request.design !== undefined) {
|
|
97
|
+
files.push({ path: join(changeRoot, 'design.md'), content: request.design });
|
|
98
|
+
}
|
|
99
|
+
return files;
|
|
100
|
+
}
|
|
101
|
+
async function writeRenderedFiles(files) {
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
await mkdir(dirname(file.path), { recursive: true });
|
|
104
|
+
await writeFile(file.path, file.content, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function renderOpenSpecChange(request, options = {}) {
|
|
108
|
+
const schemaPath = options.schemaPath ?? defaultSchemaPath();
|
|
109
|
+
const schema = await loadSchema(schemaPath);
|
|
110
|
+
if (schema !== null) {
|
|
111
|
+
const result = validateAgainstSchema(request, schema);
|
|
112
|
+
if (!result.valid) {
|
|
113
|
+
throw new OpenSpecRenderRequestInvalidError(result.errors);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (!CHANGE_ID_PATTERN.test(request.changeId)) {
|
|
117
|
+
throw new Error(`Invalid changeId: ${request.changeId} (expected letters, digits, dots, underscores, or dashes)`);
|
|
118
|
+
}
|
|
119
|
+
const openspecRoot = options.openspecRoot ?? defaultOpenSpecRoot();
|
|
120
|
+
const changeRoot = join(openspecRoot, 'changes', request.changeId);
|
|
121
|
+
const files = buildFiles(request, changeRoot);
|
|
122
|
+
if (options.apply !== true) {
|
|
123
|
+
return { changeId: request.changeId, changeRoot, files, applied: false };
|
|
124
|
+
}
|
|
125
|
+
if (options.overwrite !== true && (await isDirectory(changeRoot))) {
|
|
126
|
+
throw new Error(`Refusing to render: change directory already exists at ${changeRoot}. Re-run with overwrite to replace it.`);
|
|
127
|
+
}
|
|
128
|
+
await writeRenderedFiles(files);
|
|
129
|
+
return { changeId: request.changeId, changeRoot, files, applied: true };
|
|
130
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OpenSpecChangeDetail, OpenSpecScanReport } from './openspec-types.js';
|
|
2
|
+
export type OpenSpecScanOptions = {
|
|
3
|
+
openspecRoot?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function scanOpenSpec(options?: OpenSpecScanOptions): Promise<OpenSpecScanReport>;
|
|
6
|
+
export declare function loadOpenSpecChange(changeId: string, options?: OpenSpecScanOptions): Promise<OpenSpecChangeDetail | null>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { isDirectory, listDirectories, pathExists, readText } from '../../shared/fs.js';
|
|
3
|
+
function defaultOpenSpecRoot() {
|
|
4
|
+
return join(process.cwd(), 'openspec');
|
|
5
|
+
}
|
|
6
|
+
function parseMarkdownSections(markdown) {
|
|
7
|
+
const sections = new Map();
|
|
8
|
+
const lines = markdown.split(/\r?\n/);
|
|
9
|
+
let currentHeading = null;
|
|
10
|
+
let buffer = [];
|
|
11
|
+
const flush = () => {
|
|
12
|
+
if (currentHeading !== null) {
|
|
13
|
+
sections.set(currentHeading, buffer.join('\n').trim());
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
if (line.startsWith('## ')) {
|
|
18
|
+
flush();
|
|
19
|
+
currentHeading = line.slice(3).trim();
|
|
20
|
+
buffer = [];
|
|
21
|
+
}
|
|
22
|
+
else if (currentHeading !== null) {
|
|
23
|
+
buffer.push(line);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
flush();
|
|
27
|
+
return sections;
|
|
28
|
+
}
|
|
29
|
+
function parseBullets(content) {
|
|
30
|
+
return content
|
|
31
|
+
.split(/\r?\n/)
|
|
32
|
+
.map((line) => line.trim())
|
|
33
|
+
.filter((line) => line.startsWith('- ') || line === '-')
|
|
34
|
+
.map((line) => line.replace(/^-\s*/, '').trim())
|
|
35
|
+
.filter((line) => line.length > 0 && !line.startsWith('[ ]') && !line.startsWith('[x]') && !line.startsWith('[X]'));
|
|
36
|
+
}
|
|
37
|
+
function parseProposal(markdown) {
|
|
38
|
+
const sections = parseMarkdownSections(markdown);
|
|
39
|
+
const why = sections.get('Why') ?? '';
|
|
40
|
+
return {
|
|
41
|
+
why,
|
|
42
|
+
whatChanges: parseBullets(sections.get('What Changes') ?? ''),
|
|
43
|
+
outOfScope: parseBullets(sections.get('Out of Scope') ?? ''),
|
|
44
|
+
dependencies: parseBullets(sections.get('Dependencies') ?? ''),
|
|
45
|
+
risks: parseBullets(sections.get('Risks') ?? ''),
|
|
46
|
+
acceptanceCriteria: parseBullets(sections.get('Acceptance Criteria') ?? '')
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseTaskProgress(markdown) {
|
|
50
|
+
const sections = parseMarkdownSections(markdown);
|
|
51
|
+
const sectionEntries = [];
|
|
52
|
+
let totalTodo = 0;
|
|
53
|
+
let doneTodo = 0;
|
|
54
|
+
for (const [heading, body] of sections.entries()) {
|
|
55
|
+
let sectionTotal = 0;
|
|
56
|
+
let sectionDone = 0;
|
|
57
|
+
for (const line of body.split(/\r?\n/)) {
|
|
58
|
+
const trimmed = line.trim();
|
|
59
|
+
if (/^- \[[ xX]\]/.test(trimmed)) {
|
|
60
|
+
sectionTotal += 1;
|
|
61
|
+
if (/^- \[[xX]\]/.test(trimmed)) {
|
|
62
|
+
sectionDone += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (sectionTotal > 0) {
|
|
67
|
+
sectionEntries.push({ heading, total: sectionTotal, done: sectionDone });
|
|
68
|
+
totalTodo += sectionTotal;
|
|
69
|
+
doneTodo += sectionDone;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { totalTodo, doneTodo, sections: sectionEntries };
|
|
73
|
+
}
|
|
74
|
+
async function listSpecs(changeRoot) {
|
|
75
|
+
const specsRoot = join(changeRoot, 'specs');
|
|
76
|
+
if (!(await isDirectory(specsRoot))) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
return listDirectories(specsRoot);
|
|
80
|
+
}
|
|
81
|
+
async function resolvePaths(changeRoot) {
|
|
82
|
+
const proposalPath = join(changeRoot, 'proposal.md');
|
|
83
|
+
const tasksPath = join(changeRoot, 'tasks.md');
|
|
84
|
+
const designPath = join(changeRoot, 'design.md');
|
|
85
|
+
return {
|
|
86
|
+
root: changeRoot,
|
|
87
|
+
proposal: (await pathExists(proposalPath)) ? proposalPath : null,
|
|
88
|
+
tasks: (await pathExists(tasksPath)) ? tasksPath : null,
|
|
89
|
+
design: (await pathExists(designPath)) ? designPath : null
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function buildSummary(id, changeRoot) {
|
|
93
|
+
const paths = await resolvePaths(changeRoot);
|
|
94
|
+
const specs = await listSpecs(changeRoot);
|
|
95
|
+
let taskProgress = null;
|
|
96
|
+
if (paths.tasks !== null) {
|
|
97
|
+
taskProgress = parseTaskProgress(await readText(paths.tasks));
|
|
98
|
+
}
|
|
99
|
+
return { id, paths, specs, taskProgress };
|
|
100
|
+
}
|
|
101
|
+
export async function scanOpenSpec(options = {}) {
|
|
102
|
+
const openspecRoot = options.openspecRoot ?? defaultOpenSpecRoot();
|
|
103
|
+
const changesRoot = join(openspecRoot, 'changes');
|
|
104
|
+
if (!(await isDirectory(openspecRoot))) {
|
|
105
|
+
return { openspecRoot, changesRoot, exists: false, changes: [] };
|
|
106
|
+
}
|
|
107
|
+
if (!(await isDirectory(changesRoot))) {
|
|
108
|
+
return { openspecRoot, changesRoot, exists: true, changes: [] };
|
|
109
|
+
}
|
|
110
|
+
const ids = await listDirectories(changesRoot);
|
|
111
|
+
const changes = await Promise.all(ids.map((id) => buildSummary(id, join(changesRoot, id))));
|
|
112
|
+
return { openspecRoot, changesRoot, exists: true, changes };
|
|
113
|
+
}
|
|
114
|
+
export async function loadOpenSpecChange(changeId, options = {}) {
|
|
115
|
+
const openspecRoot = options.openspecRoot ?? defaultOpenSpecRoot();
|
|
116
|
+
const changeRoot = join(openspecRoot, 'changes', changeId);
|
|
117
|
+
if (!(await isDirectory(changeRoot))) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const summary = await buildSummary(changeId, changeRoot);
|
|
121
|
+
const proposal = summary.paths.proposal === null ? null : parseProposal(await readText(summary.paths.proposal));
|
|
122
|
+
return { ...summary, proposal };
|
|
123
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type OpenSpecProposal = {
|
|
2
|
+
why: string;
|
|
3
|
+
whatChanges: string[];
|
|
4
|
+
outOfScope: string[];
|
|
5
|
+
dependencies: string[];
|
|
6
|
+
risks: string[];
|
|
7
|
+
acceptanceCriteria: string[];
|
|
8
|
+
};
|
|
9
|
+
export type OpenSpecTaskSection = {
|
|
10
|
+
heading: string;
|
|
11
|
+
total: number;
|
|
12
|
+
done: number;
|
|
13
|
+
};
|
|
14
|
+
export type OpenSpecTaskProgress = {
|
|
15
|
+
totalTodo: number;
|
|
16
|
+
doneTodo: number;
|
|
17
|
+
sections: OpenSpecTaskSection[];
|
|
18
|
+
};
|
|
19
|
+
export type OpenSpecChangePaths = {
|
|
20
|
+
root: string;
|
|
21
|
+
proposal: string | null;
|
|
22
|
+
tasks: string | null;
|
|
23
|
+
design: string | null;
|
|
24
|
+
};
|
|
25
|
+
export type OpenSpecChangeSummary = {
|
|
26
|
+
id: string;
|
|
27
|
+
paths: OpenSpecChangePaths;
|
|
28
|
+
specs: string[];
|
|
29
|
+
taskProgress: OpenSpecTaskProgress | null;
|
|
30
|
+
};
|
|
31
|
+
export type OpenSpecChangeDetail = OpenSpecChangeSummary & {
|
|
32
|
+
proposal: OpenSpecProposal | null;
|
|
33
|
+
};
|
|
34
|
+
export type OpenSpecScanReport = {
|
|
35
|
+
openspecRoot: string;
|
|
36
|
+
changesRoot: string;
|
|
37
|
+
exists: boolean;
|
|
38
|
+
changes: OpenSpecChangeSummary[];
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type OpenSpecScanOptions } from './openspec-scan-service.js';
|
|
2
|
+
export type OpenSpecValidationLevel = 'error' | 'warning';
|
|
3
|
+
export type OpenSpecValidationIssue = {
|
|
4
|
+
level: OpenSpecValidationLevel;
|
|
5
|
+
rule: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
export type OpenSpecValidationSource = 'internal' | 'openspec-cli';
|
|
9
|
+
export type OpenSpecValidationResult = {
|
|
10
|
+
changeId: string;
|
|
11
|
+
valid: boolean;
|
|
12
|
+
source: OpenSpecValidationSource;
|
|
13
|
+
issues: OpenSpecValidationIssue[];
|
|
14
|
+
cliOutput?: string;
|
|
15
|
+
};
|
|
16
|
+
export type ExternalRunnerOutcome = {
|
|
17
|
+
available: boolean;
|
|
18
|
+
exitCode: number | null;
|
|
19
|
+
stdout: string;
|
|
20
|
+
stderr: string;
|
|
21
|
+
};
|
|
22
|
+
export type ExternalRunner = (command: string, args: string[]) => Promise<ExternalRunnerOutcome>;
|
|
23
|
+
export type OpenSpecValidateOptions = OpenSpecScanOptions & {
|
|
24
|
+
preferExternal?: boolean;
|
|
25
|
+
externalRunner?: ExternalRunner;
|
|
26
|
+
};
|
|
27
|
+
export declare function validateOpenSpecChange(changeId: string, options?: OpenSpecValidateOptions): Promise<OpenSpecValidationResult | null>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { isDirectory } from '../../shared/fs.js';
|
|
3
|
+
import { loadOpenSpecChange } from './openspec-scan-service.js';
|
|
4
|
+
const CHANGE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
5
|
+
function defaultOpenSpecRoot() {
|
|
6
|
+
return join(process.cwd(), 'openspec');
|
|
7
|
+
}
|
|
8
|
+
async function defaultExternalRunner(_command, _args) {
|
|
9
|
+
return { available: false, exitCode: null, stdout: '', stderr: '' };
|
|
10
|
+
}
|
|
11
|
+
function buildInternalIssues(changeId, detail) {
|
|
12
|
+
const issues = [];
|
|
13
|
+
if (!CHANGE_ID_PATTERN.test(changeId)) {
|
|
14
|
+
issues.push({
|
|
15
|
+
level: 'error',
|
|
16
|
+
rule: 'change-id-format',
|
|
17
|
+
message: `changeId ${changeId} does not match [A-Za-z0-9][A-Za-z0-9._-]*`
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (detail === null || detail.proposal === null) {
|
|
21
|
+
issues.push({ level: 'error', rule: 'proposal-exists', message: 'proposal.md is missing' });
|
|
22
|
+
return issues;
|
|
23
|
+
}
|
|
24
|
+
const proposal = detail.proposal;
|
|
25
|
+
if (proposal.why.length === 0) {
|
|
26
|
+
issues.push({ level: 'warning', rule: 'why-non-empty', message: 'Why section is empty' });
|
|
27
|
+
}
|
|
28
|
+
if (proposal.whatChanges.length === 0) {
|
|
29
|
+
issues.push({ level: 'error', rule: 'what-changes-non-empty', message: 'What Changes section has no bullets' });
|
|
30
|
+
}
|
|
31
|
+
if (proposal.acceptanceCriteria.length === 0) {
|
|
32
|
+
issues.push({ level: 'error', rule: 'acceptance-non-empty', message: 'Acceptance Criteria section has no bullets' });
|
|
33
|
+
}
|
|
34
|
+
return issues;
|
|
35
|
+
}
|
|
36
|
+
function hasErrors(issues) {
|
|
37
|
+
return issues.some((issue) => issue.level === 'error');
|
|
38
|
+
}
|
|
39
|
+
async function runInternal(changeId, openspecRoot) {
|
|
40
|
+
const changeRoot = join(openspecRoot, 'changes', changeId);
|
|
41
|
+
if (!(await isDirectory(changeRoot))) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const detail = await loadOpenSpecChange(changeId, { openspecRoot });
|
|
45
|
+
const issues = buildInternalIssues(changeId, detail);
|
|
46
|
+
return {
|
|
47
|
+
changeId,
|
|
48
|
+
valid: !hasErrors(issues),
|
|
49
|
+
source: 'internal',
|
|
50
|
+
issues
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function validateOpenSpecChange(changeId, options = {}) {
|
|
54
|
+
const openspecRoot = options.openspecRoot ?? defaultOpenSpecRoot();
|
|
55
|
+
const runner = options.externalRunner ?? defaultExternalRunner;
|
|
56
|
+
if (options.preferExternal === true) {
|
|
57
|
+
const outcome = await runner('openspec', ['validate', changeId]);
|
|
58
|
+
if (outcome.available) {
|
|
59
|
+
const cliOutput = [outcome.stdout, outcome.stderr].filter((part) => part.length > 0).join('\n').trim();
|
|
60
|
+
const passed = outcome.exitCode === 0;
|
|
61
|
+
const issues = passed
|
|
62
|
+
? []
|
|
63
|
+
: [{ level: 'error', rule: 'openspec-cli-failed', message: `openspec validate exited with code ${outcome.exitCode ?? 'null'}` }];
|
|
64
|
+
return { changeId, valid: passed, source: 'openspec-cli', issues, cliOutput };
|
|
65
|
+
}
|
|
66
|
+
const internal = await runInternal(changeId, openspecRoot);
|
|
67
|
+
if (internal === null) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
internal.issues = [
|
|
71
|
+
{ level: 'warning', rule: 'openspec-cli-unavailable', message: 'openspec CLI not found, fell back to internal lint' },
|
|
72
|
+
...internal.issues
|
|
73
|
+
];
|
|
74
|
+
return internal;
|
|
75
|
+
}
|
|
76
|
+
return runInternal(changeId, openspecRoot);
|
|
77
|
+
}
|
|
@@ -87,7 +87,7 @@ export const seedCapabilityItems = [
|
|
|
87
87
|
capability('codegraph.semantic-query', 'codegraph', 'Codegraph Semantic Query', 'cli', 'project-analysis', ['engineer'], 'medium', 'peaks-rd-local-scan', 'Use local Grep/Glob and RD scanning when codegraph semantic query is unavailable.', 'Codegraph Semantic Query', 'Codegraph 语义查询', 'Queries local symbols and project relationships for RD planning evidence.', '查询本地符号和项目关系,为 RD 规划提供证据。'),
|
|
88
88
|
capability('codegraph.impact-analysis', 'codegraph', 'Codegraph Impact Analysis', 'cli', 'impact-analysis', ['engineer', 'qa'], 'medium', 'peaks-rd-qa-impact-review', 'Use RD changed-file analysis and QA regression planning when codegraph affected output is unavailable.', 'Codegraph Impact Analysis', 'Codegraph 影响面分析', 'Analyzes likely impact for changed files so RD and QA can focus planning and regression scope.', '分析变更文件的可能影响面,帮助 RD 与 QA 聚焦规划和回归范围。'),
|
|
89
89
|
capability('codegraph.context-pack', 'codegraph', 'Codegraph Context Pack', 'cli', 'context-pack', ['engineer', 'qa', 'product'], 'medium', 'peaks-txt-context-capsule', 'Use Peaks TXT context capsules and role-skill handoffs when codegraph context output is unavailable.', 'Codegraph Context Pack', 'Codegraph 上下文包', 'Builds task-specific local context that Solo, RD, and TXT can use as supporting evidence.', '生成任务相关的本地上下文,作为 Solo、RD 与 TXT 的辅助证据。'),
|
|
90
|
-
capability('
|
|
90
|
+
capability('playwright-mcp.browser-validation', 'playwright-mcp', 'Playwright MCP Browser Validation', 'mcp', 'browser-validation', ['engineer', 'qa'], 'medium', 'manual-browser-test', 'Use local Playwright or manual browser verification.', 'Playwright Browser Validation', 'Playwright 浏览器验证', 'Validates UI flows through controlled browser automation.', '通过受控浏览器自动化验证 UI 流程。'),
|
|
91
91
|
capability('chrome-devtools-mcp.browser-debug', 'chrome-devtools-mcp', 'Chrome DevTools Browser Debug', 'mcp', 'browser-debug', ['engineer', 'qa'], 'medium', 'manual-devtools-inspection', 'Use browser screenshots, console logs, and network traces supplied by the user.', 'Chrome DevTools Debug', 'Chrome DevTools 调试', 'Inspects runtime UI, console, network, and performance behavior.', '检查运行时 UI、控制台、网络和性能行为。'),
|
|
92
92
|
capability('figma-context-mcp.design-context', 'figma-context-mcp', 'Figma Design Context', 'mcp', 'design-context', ['designer', 'engineer'], 'medium', 'manual-design-input', 'Ask the user for screenshots, tokens, or exported design notes.', 'Figma Design Context', 'Figma 设计上下文', 'Reads design context for UI implementation planning.', '读取设计上下文以辅助 UI 实现规划。'),
|
|
93
93
|
capability('searchcode-mcp.code-search', 'searchcode-mcp', 'SearchCode MCP', 'mcp', 'code-search', ['engineer'], 'medium', 'local-code-search', 'Use local Grep/Glob and ask before sending private code externally.', 'External Code Search', '外部代码搜索', 'Searches code examples or repositories outside the current workspace.', '搜索当前工作区外部的代码示例或仓库。'),
|
|
@@ -100,6 +100,7 @@ export const seedCapabilityItems = [
|
|
|
100
100
|
capability('gitnexus.repo-intelligence', 'gitnexus', 'GitNexus Repository Intelligence', 'cli', 'repo-intelligence', ['engineer'], 'medium', 'local-repo-scan', 'Use local project scanning through Peaks RD.', 'Repository Intelligence', '仓库智能分析', 'Repository intelligence should be proxied through Peaks before use.', '仓库智能分析应先通过 Peaks 代理边界再使用。'),
|
|
101
101
|
capability('claude-code-best-practice.workflow-guidance', 'claude-code-best-practice', 'Claude Code Best Practice', 'doc', 'workflow-guidance', ['engineer'], 'low', 'peaks-built-in-rules', 'Use Peaks built-in workflow and review rules.', 'Claude Code Best Practice', 'Claude Code 最佳实践', 'Guidance for Claude Code engineering workflows.', 'Claude Code 工程工作流指导。'),
|
|
102
102
|
capability('superpowers.workflow-methodology', 'superpowers', 'Superpowers Methodology', 'workflow', 'workflow-methodology', ['product', 'engineer'], 'low', 'peaks-workflow-route', 'Use Peaks route/autonomous plans.', 'Superpowers Methodology', 'Superpowers 方法论', 'Workflow and artifact methodology reference.', '工作流与 artifact 方法论参考。'),
|
|
103
|
+
capability('understand-anything.knowledge-graph', 'understand-anything', 'Understand Anything Knowledge Graph', 'skill', 'project-analysis', ['engineer', 'product', 'qa'], 'low', 'peaks-codegraph-or-rd-scan', 'Use peaks codegraph or peaks RD project scan when Understand Anything is not installed. Optionally suggest installing it via Claude Code: /plugin marketplace add Lum1104/Understand-Anything then /plugin install understand-anything.', 'Understand Anything Knowledge Graph', 'Understand Anything 知识图谱', 'Multi-agent project knowledge graph that Peaks reads from .understand-anything/knowledge-graph.json when it has been generated by the Understand Anything plugin in Claude Code.', '通过 Claude Code 的 Understand Anything 插件生成的多 agent 知识图谱,Peaks 在 .understand-anything/knowledge-graph.json 存在时读取它作为项目分析证据。'),
|
|
103
104
|
capability('penpot.design-source', 'penpot', 'Penpot Design Source', 'doc', 'design-source', ['designer', 'engineer'], 'medium', 'manual-design-input', 'Ask for exported design notes or screenshots.', 'Penpot Design Source', 'Penpot 设计来源', 'Design-source reference for UI planning.', '用于 UI 规划的设计来源参考。'),
|
|
104
105
|
capability('gstack.product-stack-guidance', 'gstack', 'Product Stack Guidance', 'doc', 'product-guidance', ['product'], 'low', 'peaks-prd', 'Use peaks-prd for product goals and non-goals.', 'Product Stack Guidance', '产品栈指导', 'Product and startup stack guidance reference.', '产品与创业栈指导参考。'),
|
|
105
106
|
capability('awesome-design-md.design-reference', 'awesome-design-md', 'Design Markdown Reference', 'doc', 'design-reference', ['designer', 'engineer'], 'low', 'peaks-ui-visual-direction', 'Use peaks-ui to define visual direction and interaction constraints.', 'Design Reference', '设计参考', 'Design markdown examples and inspiration.', '设计 markdown 示例与灵感。'),
|
|
@@ -8,7 +8,7 @@ export const seedCapabilityLandingMappings = [
|
|
|
8
8
|
mapping({ capabilityId: 'codegraph.context-pack', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Dry-run reference only: peaks-rd may use peaks codegraph context --project <path> <task> to gather local evidence for RD analysis when execution is approved, without replacing standards dry-runs.' }),
|
|
9
9
|
mapping({ capabilityId: 'codegraph.context-pack', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-solo', skillName: 'peaks-solo', guidance: 'Solo may attach local context packs or affected summaries before role handoff so RD, QA, and TXT share the same project evidence.' }),
|
|
10
10
|
mapping({ capabilityId: 'codegraph.context-pack', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-txt', skillName: 'peaks-txt', guidance: 'TXT may summarize recorded codegraph context packs into handoffs while treating them as supporting evidence only.' }),
|
|
11
|
-
mapping({ capabilityId: '
|
|
11
|
+
mapping({ capabilityId: 'playwright-mcp.browser-validation', sourceId: 'playwright-mcp', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use for browser and E2E validation after user-approved app targets are available.' }),
|
|
12
12
|
mapping({ capabilityId: 'chrome-devtools-mcp.browser-debug', sourceId: 'chrome-devtools-mcp', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-ui', skillName: 'peaks-ui', guidance: 'Use for runtime UI, console, network, and performance inspection.' }),
|
|
13
13
|
mapping({ capabilityId: 'context-mode.context-management', sourceId: 'context-mode', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-txt', skillName: 'peaks-txt', guidance: 'Use only for explicit context management; durable memory requires user opt-in.' }),
|
|
14
14
|
mapping({ capabilityId: 'modelcontextprotocol-servers.collection', sourceId: 'modelcontextprotocol-servers', sourceGroup: 'access-repo', landingKind: 'catalog', target: 'future peaks mcp catalog', guidance: 'Treat as an unscanned MCP collection; do not auto-install unknown servers.' }),
|
|
@@ -2,7 +2,7 @@ export const seedCapabilitySources = [
|
|
|
2
2
|
{ sourceId: 'ruflo-access-repo', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Ruflo', url: 'https://github.com/ruvnet/ruflo', trustSignals: { notes: ['Workflow orchestration reference; do not execute or install from the capability map.'] }, discoveryStatus: 'unscanned', items: ['ruflo-access-repo.workflow-reference'] },
|
|
3
3
|
{ sourceId: 'context7', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Context7', url: 'https://github.com/upstash/context7', trustSignals: { sourceReputation: 'commonly used docs lookup MCP capability' }, discoveryStatus: 'indexed', items: ['context7.docs-lookup'] },
|
|
4
4
|
{ sourceId: 'codegraph', sourceType: 'repo', sourceGroup: 'access-repo', title: 'codegraph', url: 'https://github.com/colbymchenry/codegraph', trustSignals: { notes: ['Use through peaks codegraph only; do not run upstream install flows from the capability map.', 'Local project indexing can create .codegraph artifacts; do not commit generated databases unless explicitly requested.'] }, discoveryStatus: 'indexed', items: ['codegraph.project-indexing', 'codegraph.semantic-query', 'codegraph.impact-analysis', 'codegraph.context-pack'] },
|
|
5
|
-
{ sourceId: '
|
|
5
|
+
{ sourceId: 'playwright-mcp', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Playwright MCP', url: 'https://github.com/microsoft/playwright-mcp', trustSignals: { sourceReputation: 'Microsoft browser automation MCP server' }, discoveryStatus: 'indexed', items: ['playwright-mcp.browser-validation'] },
|
|
6
6
|
{ sourceId: 'chrome-devtools-mcp', sourceType: 'website', sourceGroup: 'access-repo', title: 'Chrome DevTools MCP', url: 'https://www.pulsemcp.com/servers/chrome-devtools', trustSignals: { notes: ['Browser inspection and performance debugging capability.'] }, discoveryStatus: 'indexed', items: ['chrome-devtools-mcp.browser-debug'] },
|
|
7
7
|
{ sourceId: 'context-mode', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Context Mode', url: 'https://github.com/mksglu/context-mode', trustSignals: { notes: ['Context and memory management reference.'] }, discoveryStatus: 'indexed', items: ['context-mode.context-management'] },
|
|
8
8
|
{ sourceId: 'modelcontextprotocol-servers', sourceType: 'mcp-collection', sourceGroup: 'access-repo', title: 'Model Context Protocol Servers', url: 'https://github.com/modelcontextprotocol/servers', trustSignals: { sourceReputation: 'official MCP server collection' }, discoveryStatus: 'unscanned', items: ['modelcontextprotocol-servers.collection'] },
|
|
@@ -18,6 +18,10 @@ export type ShadcnExecutionResult = {
|
|
|
18
18
|
stderr: string;
|
|
19
19
|
};
|
|
20
20
|
export type ShadcnProcessRunner = (invocation: ShadcnInvocation) => Promise<ShadcnExecutionResult>;
|
|
21
|
+
declare function createShadcnEnvironment(sourceEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
21
22
|
export declare function createShadcnInvocation(options: ShadcnInvocationOptions): ShadcnInvocation;
|
|
22
23
|
export declare function executeShadcnInvocation(invocation: ShadcnInvocation, runner?: ShadcnProcessRunner): Promise<ShadcnExecutionResult>;
|
|
24
|
+
export declare const testInternals: {
|
|
25
|
+
createShadcnEnvironment: typeof createShadcnEnvironment;
|
|
26
|
+
};
|
|
23
27
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import {
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
5
|
const SHADCN_PACKAGE_NAME = 'shadcn';
|
|
6
6
|
const SHADCN_PACKAGE_VERSION = '4.7.0';
|
|
7
7
|
const SHADCN_EXECUTABLE = process.execPath;
|
|
@@ -10,15 +10,12 @@ const SHADCN_PROCESS_TIMEOUT_MS = 600_000;
|
|
|
10
10
|
const SHADCN_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
11
11
|
const POSITIONAL_ARGUMENT_PREFIX = '-';
|
|
12
12
|
const PRESERVED_ENV_KEYS = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
|
|
13
|
-
function assertShadcnBinaryExists(binaryPath) {
|
|
14
|
-
if (!existsSync(binaryPath)) {
|
|
15
|
-
throw new Error('Unable to resolve local shadcn binary from shadcn');
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
13
|
function resolveShadcnBinaryPath() {
|
|
19
14
|
const require = createRequire(import.meta.url);
|
|
20
15
|
const binaryPath = require.resolve('shadcn');
|
|
21
|
-
|
|
16
|
+
if (!existsSync(binaryPath)) {
|
|
17
|
+
throw new Error('Unable to resolve local shadcn binary from shadcn');
|
|
18
|
+
}
|
|
22
19
|
return binaryPath;
|
|
23
20
|
}
|
|
24
21
|
function assertShadcnArgs(args) {
|
|
@@ -46,36 +43,24 @@ function assertOutputLimit(currentSize, chunkSize) {
|
|
|
46
43
|
}
|
|
47
44
|
return nextSize;
|
|
48
45
|
}
|
|
49
|
-
function
|
|
50
|
-
const candidates = [
|
|
51
|
-
win32.join('C:\\Windows', 'System32', 'taskkill.exe'),
|
|
52
|
-
win32.join('C:\\WINNT', 'System32', 'taskkill.exe')
|
|
53
|
-
];
|
|
54
|
-
return candidates.find((candidate) => fileExists(candidate)) ?? null;
|
|
55
|
-
}
|
|
56
|
-
function terminateShadcnProcess(childProcess, platform = process.platform, killProcess = process.kill, spawnProcess = spawn, taskkillPath = getWindowsTaskkillPath()) {
|
|
46
|
+
function terminateShadcnProcess(childProcess) {
|
|
57
47
|
if (childProcess.pid === undefined) {
|
|
58
48
|
childProcess.kill();
|
|
59
49
|
return;
|
|
60
50
|
}
|
|
61
|
-
if (platform === 'win32') {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const killerProcess = spawnProcess(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
|
|
67
|
-
killerProcess.on('error', () => childProcess.kill());
|
|
68
|
-
killerProcess.unref();
|
|
51
|
+
if (process.platform === 'win32') {
|
|
52
|
+
const taskkillPath = process.env.SystemRoot ? resolve(process.env.SystemRoot, 'System32', 'taskkill.exe') : 'taskkill.exe';
|
|
53
|
+
spawn(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
|
|
69
54
|
return;
|
|
70
55
|
}
|
|
71
56
|
try {
|
|
72
|
-
|
|
57
|
+
process.kill(-childProcess.pid, 'SIGTERM');
|
|
73
58
|
}
|
|
74
59
|
catch {
|
|
75
60
|
childProcess.kill('SIGTERM');
|
|
76
61
|
}
|
|
77
62
|
}
|
|
78
|
-
function
|
|
63
|
+
function defaultShadcnProcessRunner(invocation) {
|
|
79
64
|
return new Promise((resolveResult, reject) => {
|
|
80
65
|
const childProcess = spawn(invocation.executable, invocation.args, {
|
|
81
66
|
cwd: invocation.cwd,
|
|
@@ -85,8 +70,8 @@ function runShadcnProcess(invocation, timeoutMs = SHADCN_PROCESS_TIMEOUT_MS) {
|
|
|
85
70
|
});
|
|
86
71
|
const timeout = setTimeout(() => {
|
|
87
72
|
terminateShadcnProcess(childProcess);
|
|
88
|
-
reject(new Error(`shadcn process timed out after ${
|
|
89
|
-
},
|
|
73
|
+
reject(new Error(`shadcn process timed out after ${SHADCN_PROCESS_TIMEOUT_MS}ms`));
|
|
74
|
+
}, SHADCN_PROCESS_TIMEOUT_MS);
|
|
90
75
|
const stdoutChunks = [];
|
|
91
76
|
const stderrChunks = [];
|
|
92
77
|
let stdoutSize = 0;
|
|
@@ -125,9 +110,6 @@ function runShadcnProcess(invocation, timeoutMs = SHADCN_PROCESS_TIMEOUT_MS) {
|
|
|
125
110
|
});
|
|
126
111
|
});
|
|
127
112
|
}
|
|
128
|
-
function defaultShadcnProcessRunner(invocation) {
|
|
129
|
-
return runShadcnProcess(invocation);
|
|
130
|
-
}
|
|
131
113
|
export function createShadcnInvocation(options) {
|
|
132
114
|
assertShadcnArgs(options.args);
|
|
133
115
|
return {
|
|
@@ -141,3 +123,6 @@ export function createShadcnInvocation(options) {
|
|
|
141
123
|
export async function executeShadcnInvocation(invocation, runner = defaultShadcnProcessRunner) {
|
|
142
124
|
return runner(invocation);
|
|
143
125
|
}
|
|
126
|
+
export const testInternals = {
|
|
127
|
+
createShadcnEnvironment
|
|
128
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SkillRunbookInspection = {
|
|
2
|
+
name: string;
|
|
3
|
+
directory: string;
|
|
4
|
+
hasRunbook: boolean;
|
|
5
|
+
peaksCommandCount: number;
|
|
6
|
+
peaksCommandLines: string[];
|
|
7
|
+
destructiveApplyLines: string[];
|
|
8
|
+
hasAuthorizationNote: boolean;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function inspectSkillRunbook(name: string, baseDir?: string): Promise<SkillRunbookInspection>;
|