@abranjith/spec-lite 0.0.1 → 0.0.4
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/README.md +27 -25
- package/dist/index.js +516 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prompts/architect.md +495 -0
- package/prompts/brainstorm.md +8 -8
- package/prompts/code_review.md +11 -11
- package/prompts/devops.md +9 -9
- package/prompts/feature.md +23 -23
- package/prompts/fix.md +12 -12
- package/prompts/implement.md +20 -20
- package/prompts/integration_tests.md +8 -8
- package/prompts/memorize.md +41 -20
- package/prompts/orchestrator.md +48 -41
- package/prompts/performance_review.md +7 -7
- package/prompts/planner.md +25 -25
- package/prompts/readme.md +8 -8
- package/prompts/security_audit.md +9 -9
- package/prompts/spec_help.md +22 -19
- package/prompts/technical_docs.md +11 -11
- package/prompts/unit_tests.md +12 -12
package/dist/index.js
CHANGED
|
@@ -12,14 +12,255 @@ import inquirer from "inquirer";
|
|
|
12
12
|
// src/providers/copilot.ts
|
|
13
13
|
import path from "path";
|
|
14
14
|
import fs from "fs-extra";
|
|
15
|
+
var AGENT_HANDOFFS = {
|
|
16
|
+
spec_help: [],
|
|
17
|
+
brainstorm: [
|
|
18
|
+
{
|
|
19
|
+
label: "Create Plan",
|
|
20
|
+
agent: "spec.planner",
|
|
21
|
+
prompt: "Create a detailed technical plan based on the brainstorm above."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: "Capture Conventions",
|
|
25
|
+
agent: "spec.memorize",
|
|
26
|
+
prompt: "Bootstrap memory from the project context established in the brainstorm."
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
planner: [
|
|
30
|
+
{
|
|
31
|
+
label: "Break Down Features",
|
|
32
|
+
agent: "spec.feature",
|
|
33
|
+
prompt: "Break the plan into individual feature specification files."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: "Design Architecture",
|
|
37
|
+
agent: "spec.architect",
|
|
38
|
+
prompt: "Create a detailed cloud and infrastructure architecture for the plan."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: "Capture Conventions",
|
|
42
|
+
agent: "spec.memorize",
|
|
43
|
+
prompt: "Bootstrap memory from the plan's tech stack and conventions."
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
architect: [
|
|
47
|
+
{
|
|
48
|
+
label: "Break Down Features",
|
|
49
|
+
agent: "spec.feature",
|
|
50
|
+
prompt: "Break the plan into individual feature specification files."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Set Up Infrastructure",
|
|
54
|
+
agent: "spec.devops",
|
|
55
|
+
prompt: "Set up Docker, CI/CD, and deployment infrastructure based on the architecture."
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
feature: [
|
|
59
|
+
{
|
|
60
|
+
label: "Implement Feature",
|
|
61
|
+
agent: "spec.implement",
|
|
62
|
+
prompt: "Implement the feature spec produced above."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
label: "Write Unit Tests",
|
|
66
|
+
agent: "spec.unit_tests",
|
|
67
|
+
prompt: "Write unit tests for the feature spec produced above."
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
implement: [
|
|
71
|
+
{
|
|
72
|
+
label: "Write Unit Tests",
|
|
73
|
+
agent: "spec.unit_tests",
|
|
74
|
+
prompt: "Write comprehensive unit tests for the code just implemented."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: "Review Code",
|
|
78
|
+
agent: "spec.code_review",
|
|
79
|
+
prompt: "Review the code just implemented for correctness, architecture, and readability."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: "Write Integration Tests",
|
|
83
|
+
agent: "spec.integration_tests",
|
|
84
|
+
prompt: "Write integration test scenarios for the feature just implemented."
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
unit_tests: [
|
|
88
|
+
{
|
|
89
|
+
label: "Review Code",
|
|
90
|
+
agent: "spec.code_review",
|
|
91
|
+
prompt: "Review the implementation and tests for correctness, architecture, and readability."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
label: "Write Integration Tests",
|
|
95
|
+
agent: "spec.integration_tests",
|
|
96
|
+
prompt: "Write integration test scenarios to complement the unit tests."
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
code_review: [
|
|
100
|
+
{
|
|
101
|
+
label: "Fix Issues",
|
|
102
|
+
agent: "spec.fix",
|
|
103
|
+
prompt: "Fix the issues identified in the code review above."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: "Security Audit",
|
|
107
|
+
agent: "spec.security_audit",
|
|
108
|
+
prompt: "Run a security audit on the code reviewed above."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: "Write Technical Docs",
|
|
112
|
+
agent: "spec.technical_docs",
|
|
113
|
+
prompt: "Write technical documentation for the reviewed code."
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
integration_tests: [
|
|
117
|
+
{
|
|
118
|
+
label: "Security Audit",
|
|
119
|
+
agent: "spec.security_audit",
|
|
120
|
+
prompt: "Run a security audit on the features covered by integration tests."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: "Performance Review",
|
|
124
|
+
agent: "spec.performance_review",
|
|
125
|
+
prompt: "Review performance of the features covered by integration tests."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: "Write Technical Docs",
|
|
129
|
+
agent: "spec.technical_docs",
|
|
130
|
+
prompt: "Write technical documentation for the features covered by integration tests."
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
performance_review: [
|
|
134
|
+
{
|
|
135
|
+
label: "Fix Critical Issues",
|
|
136
|
+
agent: "spec.fix",
|
|
137
|
+
prompt: "Fix the critical performance issues identified in the review above."
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: "Security Audit",
|
|
141
|
+
agent: "spec.security_audit",
|
|
142
|
+
prompt: "Run a security audit alongside the performance improvements."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: "Write Technical Docs",
|
|
146
|
+
agent: "spec.technical_docs",
|
|
147
|
+
prompt: "Write technical documentation capturing the performance findings and fixes."
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
security_audit: [
|
|
151
|
+
{
|
|
152
|
+
label: "Fix Vulnerabilities",
|
|
153
|
+
agent: "spec.fix",
|
|
154
|
+
prompt: "Fix the vulnerabilities and security issues identified in the audit above."
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
label: "Write Technical Docs",
|
|
158
|
+
agent: "spec.technical_docs",
|
|
159
|
+
prompt: "Write technical documentation capturing the security findings and mitigations."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: "Update README",
|
|
163
|
+
agent: "spec.readme",
|
|
164
|
+
prompt: "Update the README to reflect the hardened security posture."
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
fix: [
|
|
168
|
+
{
|
|
169
|
+
label: "Review Fix",
|
|
170
|
+
agent: "spec.code_review",
|
|
171
|
+
prompt: "Review the fix applied above for correctness and regressions."
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: "Write Regression Tests",
|
|
175
|
+
agent: "spec.unit_tests",
|
|
176
|
+
prompt: "Write regression tests to cover the bug fixed above."
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
memorize: [
|
|
180
|
+
{
|
|
181
|
+
label: "Create Plan",
|
|
182
|
+
agent: "spec.planner",
|
|
183
|
+
prompt: "Create a technical plan for the project using the conventions captured in memory."
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
label: "Add Feature",
|
|
187
|
+
agent: "spec.feature",
|
|
188
|
+
prompt: "Define a feature specification using the conventions captured in memory."
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
technical_docs: [
|
|
192
|
+
{
|
|
193
|
+
label: "Update README",
|
|
194
|
+
agent: "spec.readme",
|
|
195
|
+
prompt: "Write or update the project README based on the technical docs produced above."
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
label: "Set Up DevOps",
|
|
199
|
+
agent: "spec.devops",
|
|
200
|
+
prompt: "Set up deployment infrastructure to complement the documented architecture."
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
readme: [
|
|
204
|
+
{
|
|
205
|
+
label: "Set Up DevOps",
|
|
206
|
+
agent: "spec.devops",
|
|
207
|
+
prompt: "Set up Docker, CI/CD, and deployment infrastructure for this project."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
label: "Security Audit",
|
|
211
|
+
agent: "spec.security_audit",
|
|
212
|
+
prompt: "Run a final security audit before releasing the project."
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
devops: [
|
|
216
|
+
{
|
|
217
|
+
label: "Security Audit",
|
|
218
|
+
agent: "spec.security_audit",
|
|
219
|
+
prompt: "Run a security audit on the infrastructure and deployment configuration."
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
label: "Update README",
|
|
223
|
+
agent: "spec.readme",
|
|
224
|
+
prompt: "Update the README with deployment and infrastructure instructions."
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
label: "Update Technical Docs",
|
|
228
|
+
agent: "spec.technical_docs",
|
|
229
|
+
prompt: "Update the technical documentation to include the DevOps setup."
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
};
|
|
233
|
+
function buildAgentFrontmatter(meta) {
|
|
234
|
+
const handoffs = AGENT_HANDOFFS[meta.name] ?? [];
|
|
235
|
+
const lines = ["---", `description: ${meta.description}`];
|
|
236
|
+
if (handoffs.length > 0) {
|
|
237
|
+
lines.push("handoffs:");
|
|
238
|
+
for (const h of handoffs) {
|
|
239
|
+
lines.push(` - label: ${h.label}`);
|
|
240
|
+
lines.push(` agent: ${h.agent}`);
|
|
241
|
+
lines.push(` prompt: ${h.prompt}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
lines.push("---", "");
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
|
15
247
|
var CopilotProvider = class {
|
|
16
248
|
name = "GitHub Copilot";
|
|
17
249
|
alias = "copilot";
|
|
18
250
|
description = "GitHub Copilot (VS Code, JetBrains, Neovim)";
|
|
251
|
+
/** Primary target: the .agent.md file in .github/agents/ */
|
|
19
252
|
getTargetPath(promptName) {
|
|
20
|
-
return path.join(".github", "
|
|
253
|
+
return path.join(".github", "agents", `spec.${promptName}.agent.md`);
|
|
21
254
|
}
|
|
255
|
+
/** Transform content into an agent file: YAML frontmatter + prompt body. */
|
|
22
256
|
transformPrompt(content, meta) {
|
|
257
|
+
return buildAgentFrontmatter(meta) + content;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Transform content into a prompt file (no frontmatter — just the managed-file
|
|
261
|
+
* header comment followed by the prompt body).
|
|
262
|
+
*/
|
|
263
|
+
transformPromptFile(content, meta) {
|
|
23
264
|
const header = [
|
|
24
265
|
`<!-- spec-lite | ${meta.name} | DO NOT EDIT below the project-context block \u2014 managed by spec-lite -->`,
|
|
25
266
|
`<!-- To update: run "spec-lite update" \u2014 your Project Context edits will be preserved -->`,
|
|
@@ -27,44 +268,107 @@ var CopilotProvider = class {
|
|
|
27
268
|
].join("\n");
|
|
28
269
|
return header + content;
|
|
29
270
|
}
|
|
271
|
+
/** Returns the path for the companion .prompt.md file */
|
|
272
|
+
getPromptFilePath(promptName) {
|
|
273
|
+
return path.join(".github", "prompts", `${promptName}.prompt.md`);
|
|
274
|
+
}
|
|
30
275
|
async detectExisting(workspaceRoot) {
|
|
31
276
|
const existing = [];
|
|
32
|
-
const
|
|
33
|
-
if (await fs.pathExists(
|
|
34
|
-
const files = await fs.readdir(
|
|
277
|
+
const agentsDir = path.join(workspaceRoot, ".github", "agents");
|
|
278
|
+
if (await fs.pathExists(agentsDir)) {
|
|
279
|
+
const files = await fs.readdir(agentsDir);
|
|
35
280
|
for (const f of files) {
|
|
36
|
-
if (f.
|
|
37
|
-
existing.push(path.join(".github", "
|
|
281
|
+
if (f.startsWith("spec.") && f.endsWith(".agent.md")) {
|
|
282
|
+
existing.push(path.join(".github", "agents", f));
|
|
38
283
|
}
|
|
39
284
|
}
|
|
40
285
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
286
|
+
const promptsDir = path.join(workspaceRoot, ".github", "prompts");
|
|
287
|
+
if (await fs.pathExists(promptsDir)) {
|
|
288
|
+
const files = await fs.readdir(promptsDir);
|
|
289
|
+
for (const f of files) {
|
|
290
|
+
if (f.endsWith(".prompt.md")) {
|
|
291
|
+
existing.push(path.join(".github", "prompts", f));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const mainFile = path.join(workspaceRoot, ".github", "copilot-instructions.md");
|
|
46
296
|
if (await fs.pathExists(mainFile)) {
|
|
47
297
|
existing.push(".github/copilot-instructions.md");
|
|
48
298
|
}
|
|
49
299
|
return existing;
|
|
50
300
|
}
|
|
301
|
+
async getMemorySeedSource(workspaceRoot) {
|
|
302
|
+
const p = path.join(workspaceRoot, ".github", "copilot-instructions.md");
|
|
303
|
+
if (await fs.pathExists(p)) {
|
|
304
|
+
return { path: ".github/copilot-instructions.md", label: "GitHub Copilot global instructions" };
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
51
308
|
getPostInitMessage() {
|
|
52
309
|
return [
|
|
53
310
|
"",
|
|
54
311
|
"\u{1F4CB} GitHub Copilot setup complete!",
|
|
55
312
|
"",
|
|
56
|
-
"
|
|
313
|
+
" Agent files : .github/agents/spec.<name>.agent.md",
|
|
314
|
+
" Prompt files : .github/prompts/<name>.prompt.md",
|
|
57
315
|
"",
|
|
58
316
|
" How to use:",
|
|
59
317
|
" 1. Open GitHub Copilot Chat in VS Code",
|
|
60
|
-
" 2.
|
|
61
|
-
"
|
|
62
|
-
" 3.
|
|
318
|
+
" 2. Select a sub-agent from the agents dropdown (e.g., spec.planner)",
|
|
319
|
+
" \u2014 or \u2014 reference a prompt file with #file or type / to browse",
|
|
320
|
+
" 3. Agent files include handoff buttons to guide you through the pipeline",
|
|
63
321
|
" 4. Customize the Project Context block in each file for your project",
|
|
64
322
|
""
|
|
65
323
|
].join("\n");
|
|
66
324
|
}
|
|
67
325
|
};
|
|
326
|
+
var SPEC_LITE_MARKER_START = "<!-- spec-lite:start -->";
|
|
327
|
+
var SPEC_LITE_MARKER_END = "<!-- spec-lite:end -->";
|
|
328
|
+
function generateSpecLiteBlock(installedPrompts) {
|
|
329
|
+
const lines = [
|
|
330
|
+
SPEC_LITE_MARKER_START,
|
|
331
|
+
"## spec-lite Sub-Agents",
|
|
332
|
+
"",
|
|
333
|
+
"This project uses [spec-lite](https://github.com/abranjith/spec-lite) sub-agent prompts",
|
|
334
|
+
"for structured software engineering workflows.",
|
|
335
|
+
"",
|
|
336
|
+
"The following specialist sub-agents are available:",
|
|
337
|
+
"",
|
|
338
|
+
"**Agent files** (`.github/agents/`) \u2014 select from the agents dropdown in Copilot Chat:",
|
|
339
|
+
""
|
|
340
|
+
];
|
|
341
|
+
for (const name of installedPrompts) {
|
|
342
|
+
lines.push(`- [spec.${name}](.github/agents/spec.${name}.agent.md)`);
|
|
343
|
+
}
|
|
344
|
+
lines.push(
|
|
345
|
+
"",
|
|
346
|
+
"**Prompt files** (`.github/prompts/`) \u2014 reference with `#file` or browse with `/`:",
|
|
347
|
+
""
|
|
348
|
+
);
|
|
349
|
+
for (const name of installedPrompts) {
|
|
350
|
+
lines.push(`- [${name}](.github/prompts/${name}.prompt.md)`);
|
|
351
|
+
}
|
|
352
|
+
lines.push(
|
|
353
|
+
"",
|
|
354
|
+
"To invoke a sub-agent, select it from the agents dropdown or use `#file` to reference a prompt file.",
|
|
355
|
+
SPEC_LITE_MARKER_END
|
|
356
|
+
);
|
|
357
|
+
return lines.join("\n");
|
|
358
|
+
}
|
|
359
|
+
function mergeCopilotInstructions(existingContent, installedPrompts) {
|
|
360
|
+
const block = generateSpecLiteBlock(installedPrompts);
|
|
361
|
+
if (!existingContent) {
|
|
362
|
+
return block + "\n";
|
|
363
|
+
}
|
|
364
|
+
const startIdx = existingContent.indexOf(SPEC_LITE_MARKER_START);
|
|
365
|
+
const endIdx = existingContent.indexOf(SPEC_LITE_MARKER_END);
|
|
366
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
367
|
+
return existingContent.slice(0, startIdx) + block + existingContent.slice(endIdx + SPEC_LITE_MARKER_END.length);
|
|
368
|
+
}
|
|
369
|
+
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
370
|
+
return existingContent + separator + block + "\n";
|
|
371
|
+
}
|
|
68
372
|
|
|
69
373
|
// src/providers/claude-code.ts
|
|
70
374
|
import path2 from "path";
|
|
@@ -105,6 +409,13 @@ var ClaudeCodeProvider = class {
|
|
|
105
409
|
}
|
|
106
410
|
return existing;
|
|
107
411
|
}
|
|
412
|
+
async getMemorySeedSource(workspaceRoot) {
|
|
413
|
+
const p = path2.join(workspaceRoot, "CLAUDE.md");
|
|
414
|
+
if (await fs2.pathExists(p)) {
|
|
415
|
+
return { path: "CLAUDE.md", label: "Claude root instructions (CLAUDE.md)" };
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
108
419
|
getPostInitMessage() {
|
|
109
420
|
return [
|
|
110
421
|
"",
|
|
@@ -127,7 +438,7 @@ function generateClaudeRootMd(installedPrompts) {
|
|
|
127
438
|
"",
|
|
128
439
|
"# Project Instructions",
|
|
129
440
|
"",
|
|
130
|
-
"This project uses [spec-lite](https://github.com/
|
|
441
|
+
"This project uses [spec-lite](https://github.com/abranjith/spec-lite) sub-agent prompts",
|
|
131
442
|
"for structured software engineering workflows.",
|
|
132
443
|
"",
|
|
133
444
|
"## Available Sub-Agents",
|
|
@@ -150,10 +461,10 @@ function generateClaudeRootMd(installedPrompts) {
|
|
|
150
461
|
"",
|
|
151
462
|
"## Output Directory",
|
|
152
463
|
"",
|
|
153
|
-
"Sub-agent outputs are written to the `.spec/` directory:",
|
|
464
|
+
"Sub-agent outputs are written to the `.spec-lite/` directory:",
|
|
154
465
|
"",
|
|
155
466
|
"```text",
|
|
156
|
-
".spec/",
|
|
467
|
+
".spec-lite/",
|
|
157
468
|
"\u251C\u2500\u2500 brainstorm.md",
|
|
158
469
|
"\u251C\u2500\u2500 plan.md # Default plan (simple projects)",
|
|
159
470
|
"\u251C\u2500\u2500 plan_<name>.md # Named plans (complex projects)",
|
|
@@ -197,6 +508,9 @@ var GenericProvider = class {
|
|
|
197
508
|
}
|
|
198
509
|
return existing;
|
|
199
510
|
}
|
|
511
|
+
async getMemorySeedSource(_workspaceRoot) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
200
514
|
getPostInitMessage() {
|
|
201
515
|
return [
|
|
202
516
|
"",
|
|
@@ -245,17 +559,17 @@ var PROMPT_CATALOG = {
|
|
|
245
559
|
brainstorm: {
|
|
246
560
|
title: "Brainstorm",
|
|
247
561
|
description: "Refines a vague idea into a clear, actionable vision",
|
|
248
|
-
output: ".spec/brainstorm.md"
|
|
562
|
+
output: ".spec-lite/brainstorm.md"
|
|
249
563
|
},
|
|
250
564
|
planner: {
|
|
251
565
|
title: "Planner",
|
|
252
566
|
description: "Creates a detailed technical blueprint from requirements",
|
|
253
|
-
output: ".spec/plan.md or .spec/plan_<name>.md"
|
|
567
|
+
output: ".spec-lite/plan.md or .spec-lite/plan_<name>.md"
|
|
254
568
|
},
|
|
255
569
|
feature: {
|
|
256
570
|
title: "Feature",
|
|
257
571
|
description: "Breaks one feature into granular, verifiable vertical slices",
|
|
258
|
-
output: ".spec/features/feature_<name>.md"
|
|
572
|
+
output: ".spec-lite/features/feature_<name>.md"
|
|
259
573
|
},
|
|
260
574
|
implement: {
|
|
261
575
|
title: "Implement",
|
|
@@ -265,17 +579,17 @@ var PROMPT_CATALOG = {
|
|
|
265
579
|
code_review: {
|
|
266
580
|
title: "Code Review",
|
|
267
581
|
description: "Reviews code for correctness, architecture, and readability",
|
|
268
|
-
output: ".spec/reviews/code_review_<name>.md"
|
|
582
|
+
output: ".spec-lite/reviews/code_review_<name>.md"
|
|
269
583
|
},
|
|
270
584
|
security_audit: {
|
|
271
585
|
title: "Security Audit",
|
|
272
586
|
description: "Scans for vulnerabilities, misconfigurations, and security risks",
|
|
273
|
-
output: ".spec/reviews/security_audit_<scope>.md"
|
|
587
|
+
output: ".spec-lite/reviews/security_audit_<scope>.md"
|
|
274
588
|
},
|
|
275
589
|
performance_review: {
|
|
276
590
|
title: "Performance Review",
|
|
277
591
|
description: "Identifies bottlenecks and optimization opportunities",
|
|
278
|
-
output: ".spec/reviews/performance_review_<scope>.md"
|
|
592
|
+
output: ".spec-lite/reviews/performance_review_<scope>.md"
|
|
279
593
|
},
|
|
280
594
|
integration_tests: {
|
|
281
595
|
title: "Integration Tests",
|
|
@@ -285,7 +599,7 @@ var PROMPT_CATALOG = {
|
|
|
285
599
|
unit_tests: {
|
|
286
600
|
title: "Unit Tests",
|
|
287
601
|
description: "Generates comprehensive unit tests with edge-case coverage and smart coverage exclusions",
|
|
288
|
-
output: ".spec/features/unit_tests_<name>.md"
|
|
602
|
+
output: ".spec-lite/features/unit_tests_<name>.md"
|
|
289
603
|
},
|
|
290
604
|
devops: {
|
|
291
605
|
title: "DevOps",
|
|
@@ -300,7 +614,7 @@ var PROMPT_CATALOG = {
|
|
|
300
614
|
memorize: {
|
|
301
615
|
title: "Memorize",
|
|
302
616
|
description: "Stores standing instructions that all sub-agents enforce. Use `/memorize bootstrap` to auto-generate from project analysis.",
|
|
303
|
-
output: ".spec/memory.md"
|
|
617
|
+
output: ".spec-lite/memory.md"
|
|
304
618
|
},
|
|
305
619
|
technical_docs: {
|
|
306
620
|
title: "Technical Docs",
|
|
@@ -311,6 +625,11 @@ var PROMPT_CATALOG = {
|
|
|
311
625
|
title: "README",
|
|
312
626
|
description: "Writes the project README and optional user guide",
|
|
313
627
|
output: "README.md + docs/user_guide.md"
|
|
628
|
+
},
|
|
629
|
+
architect: {
|
|
630
|
+
title: "Architect",
|
|
631
|
+
description: "Designs cloud infrastructure, database strategy, and scaling architecture with Mermaid diagrams",
|
|
632
|
+
output: ".spec-lite/architect_<name>.md"
|
|
314
633
|
}
|
|
315
634
|
};
|
|
316
635
|
var SKIP_FILES = /* @__PURE__ */ new Set(["orchestrator"]);
|
|
@@ -492,6 +811,34 @@ function buildProjectContextBlock(profile) {
|
|
|
492
811
|
lines.push("");
|
|
493
812
|
return lines.join("\n");
|
|
494
813
|
}
|
|
814
|
+
function buildSeededMemory(sourceContent, sourcePath, version) {
|
|
815
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
816
|
+
return [
|
|
817
|
+
`<!-- Generated by spec-lite v${version} | sub-agent: memorize | seeded-from: ${sourcePath} | updated: ${date} -->`,
|
|
818
|
+
"",
|
|
819
|
+
"# Memory \u2014 Standing Instructions",
|
|
820
|
+
"",
|
|
821
|
+
`> These instructions were **auto-seeded** from \`${sourcePath}\` during \`spec-lite init\`.`,
|
|
822
|
+
"> They have not yet been organized into sections.",
|
|
823
|
+
"> Run `/memorize bootstrap` in your AI assistant to review, reorganize, and refine them.",
|
|
824
|
+
">",
|
|
825
|
+
"> Memory is the **authoritative source** for coding standards, architecture, testing, logging, and security.",
|
|
826
|
+
"> Plans may contain plan-specific overrides but should not duplicate these rules.",
|
|
827
|
+
"> Managed by the Memorize sub-agent. Do not edit section headers manually.",
|
|
828
|
+
"> To add or change instructions, invoke: `/memorize <your instructions>`",
|
|
829
|
+
"> To override: `/memorize override <your instructions>`",
|
|
830
|
+
"> To generate from project analysis: `/memorize bootstrap`",
|
|
831
|
+
"",
|
|
832
|
+
`<!-- seed-start: raw content imported from ${sourcePath} -->`,
|
|
833
|
+
"",
|
|
834
|
+
`## Imported from \`${sourcePath}\``,
|
|
835
|
+
"",
|
|
836
|
+
sourceContent.trim(),
|
|
837
|
+
"",
|
|
838
|
+
"<!-- seed-end -->",
|
|
839
|
+
""
|
|
840
|
+
].join("\n");
|
|
841
|
+
}
|
|
495
842
|
async function initCommand(options) {
|
|
496
843
|
const cwd = process.cwd();
|
|
497
844
|
console.log(chalk.bold("\n\u26A1 spec-lite init\n"));
|
|
@@ -532,7 +879,16 @@ async function initCommand(options) {
|
|
|
532
879
|
if (exclude.length > 0) {
|
|
533
880
|
console.log(chalk.dim(` Excluding: ${exclude.join(", ")}`));
|
|
534
881
|
}
|
|
882
|
+
const memorySeedSource = await provider.getMemorySeedSource(cwd);
|
|
883
|
+
let preSeedContent = null;
|
|
884
|
+
if (memorySeedSource) {
|
|
885
|
+
const seedAbsPath = path6.join(cwd, memorySeedSource.path);
|
|
886
|
+
if (await fs6.pathExists(seedAbsPath)) {
|
|
887
|
+
preSeedContent = await fs6.readFile(seedAbsPath, "utf-8");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
535
890
|
const existingFiles = await provider.detectExisting(cwd);
|
|
891
|
+
let globalAction = null;
|
|
536
892
|
if (existingFiles.length > 0 && !options.force) {
|
|
537
893
|
console.log(
|
|
538
894
|
chalk.yellow(
|
|
@@ -563,7 +919,8 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
563
919
|
console.log(chalk.dim(" Aborted."));
|
|
564
920
|
return;
|
|
565
921
|
}
|
|
566
|
-
|
|
922
|
+
globalAction = answer.action;
|
|
923
|
+
if (globalAction === "skip") {
|
|
567
924
|
console.log(chalk.dim(" Skipping existing files."));
|
|
568
925
|
}
|
|
569
926
|
}
|
|
@@ -575,23 +932,10 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
575
932
|
for (const prompt of prompts) {
|
|
576
933
|
const targetRelPath = provider.getTargetPath(prompt.name);
|
|
577
934
|
const targetAbsPath = path6.join(cwd, targetRelPath);
|
|
578
|
-
if (!options.force && existingFiles.includes(targetRelPath) &&
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
name: "action",
|
|
583
|
-
message: `How should we handle existing files?`,
|
|
584
|
-
choices: [
|
|
585
|
-
{ name: "Overwrite", value: "overwrite" },
|
|
586
|
-
{ name: "Skip", value: "skip" }
|
|
587
|
-
]
|
|
588
|
-
}
|
|
589
|
-
]);
|
|
590
|
-
if (answer.action === "skip") {
|
|
591
|
-
skipped++;
|
|
592
|
-
installedPrompts.push(prompt.name);
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
935
|
+
if (!options.force && existingFiles.includes(targetRelPath) && globalAction === "skip") {
|
|
936
|
+
skipped++;
|
|
937
|
+
installedPrompts.push(prompt.name);
|
|
938
|
+
continue;
|
|
595
939
|
}
|
|
596
940
|
let content = prompt.content;
|
|
597
941
|
if (contextBlock) {
|
|
@@ -615,10 +959,43 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
615
959
|
console.log(chalk.green(` \u2713 CLAUDE.md`));
|
|
616
960
|
written++;
|
|
617
961
|
}
|
|
962
|
+
if (provider.alias === "copilot") {
|
|
963
|
+
const copilotProvider = provider;
|
|
964
|
+
for (const prompt of prompts) {
|
|
965
|
+
const promptRelPath = copilotProvider.getPromptFilePath(prompt.name);
|
|
966
|
+
const promptAbsPath = path6.join(cwd, promptRelPath);
|
|
967
|
+
if (!options.force && existingFiles.includes(promptRelPath) && globalAction === "skip") {
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
let content = prompt.content;
|
|
971
|
+
if (contextBlock) {
|
|
972
|
+
content = replaceProjectContext(content, contextBlock);
|
|
973
|
+
}
|
|
974
|
+
const transformed = copilotProvider.transformPromptFile(content, {
|
|
975
|
+
name: prompt.name,
|
|
976
|
+
title: prompt.title,
|
|
977
|
+
description: prompt.description
|
|
978
|
+
});
|
|
979
|
+
await fs6.ensureDir(path6.dirname(promptAbsPath));
|
|
980
|
+
await fs6.writeFile(promptAbsPath, transformed, "utf-8");
|
|
981
|
+
written++;
|
|
982
|
+
console.log(chalk.green(` \u2713 ${promptRelPath}`));
|
|
983
|
+
}
|
|
984
|
+
const copilotInstructionsPath = path6.join(cwd, ".github", "copilot-instructions.md");
|
|
985
|
+
await fs6.ensureDir(path6.join(cwd, ".github"));
|
|
986
|
+
const existingContent = await fs6.pathExists(copilotInstructionsPath) ? await fs6.readFile(copilotInstructionsPath, "utf-8") : null;
|
|
987
|
+
const merged = mergeCopilotInstructions(existingContent, installedPrompts);
|
|
988
|
+
await fs6.writeFile(copilotInstructionsPath, merged, "utf-8");
|
|
989
|
+
if (existingContent) {
|
|
990
|
+
console.log(chalk.green(` \u2713 .github/copilot-instructions.md (updated with spec-lite block)`));
|
|
991
|
+
} else {
|
|
992
|
+
console.log(chalk.green(` \u2713 .github/copilot-instructions.md`));
|
|
993
|
+
}
|
|
994
|
+
written++;
|
|
995
|
+
}
|
|
618
996
|
const specDirs = [
|
|
619
|
-
".spec",
|
|
620
|
-
path6.join(".spec", "
|
|
621
|
-
path6.join(".spec", "reviews")
|
|
997
|
+
path6.join(".spec-lite", "features"),
|
|
998
|
+
path6.join(".spec-lite", "reviews")
|
|
622
999
|
];
|
|
623
1000
|
for (const dir of specDirs) {
|
|
624
1001
|
const absDir = path6.join(cwd, dir);
|
|
@@ -627,7 +1004,7 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
627
1004
|
console.log(chalk.green(` \u2713 ${dir}/`));
|
|
628
1005
|
}
|
|
629
1006
|
}
|
|
630
|
-
const todoPath = path6.join(cwd, ".spec", "TODO.md");
|
|
1007
|
+
const todoPath = path6.join(cwd, ".spec-lite", "TODO.md");
|
|
631
1008
|
if (!await fs6.pathExists(todoPath)) {
|
|
632
1009
|
const todoContent = [
|
|
633
1010
|
"# TODO \u2014 Enhancements & Ideas",
|
|
@@ -649,7 +1026,7 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
649
1026
|
""
|
|
650
1027
|
].join("\n");
|
|
651
1028
|
await fs6.writeFile(todoPath, todoContent, "utf-8");
|
|
652
|
-
console.log(chalk.green(` \u2713 .spec/TODO.md`));
|
|
1029
|
+
console.log(chalk.green(` \u2713 .spec-lite/TODO.md`));
|
|
653
1030
|
}
|
|
654
1031
|
if (projectProfile) {
|
|
655
1032
|
const snippet = getStackSnippet(projectProfile.language);
|
|
@@ -674,6 +1051,32 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
674
1051
|
}
|
|
675
1052
|
}
|
|
676
1053
|
}
|
|
1054
|
+
let memorySeedWritten = false;
|
|
1055
|
+
const memoryPath = path6.join(cwd, ".spec-lite", "memory.md");
|
|
1056
|
+
if (preSeedContent && memorySeedSource && !await fs6.pathExists(memoryPath)) {
|
|
1057
|
+
console.log(
|
|
1058
|
+
chalk.cyan(`
|
|
1059
|
+
\u{1F4A1} Found existing ${memorySeedSource.label} (${memorySeedSource.path}).`)
|
|
1060
|
+
);
|
|
1061
|
+
const seedAnswer = await inquirer.prompt([
|
|
1062
|
+
{
|
|
1063
|
+
type: "confirm",
|
|
1064
|
+
name: "seedMemory",
|
|
1065
|
+
message: `Seed .spec-lite/memory.md from it so /memorize bootstrap can refine your existing conventions?`,
|
|
1066
|
+
default: true
|
|
1067
|
+
}
|
|
1068
|
+
]);
|
|
1069
|
+
if (seedAnswer.seedMemory) {
|
|
1070
|
+
const seedPkg = await loadPackageVersion();
|
|
1071
|
+
const seededContent = buildSeededMemory(preSeedContent, memorySeedSource.path, seedPkg);
|
|
1072
|
+
await fs6.ensureDir(path6.join(cwd, ".spec-lite"));
|
|
1073
|
+
await fs6.writeFile(memoryPath, seededContent, "utf-8");
|
|
1074
|
+
memorySeedWritten = true;
|
|
1075
|
+
written++;
|
|
1076
|
+
console.log(chalk.green(` \u2713 .spec-lite/memory.md (seeded from ${memorySeedSource.path})`));
|
|
1077
|
+
console.log(chalk.dim(" \u21B3 Run /memorize bootstrap to organize and refine into standing instructions"));
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
677
1080
|
const pkg2 = await loadPackageVersion();
|
|
678
1081
|
const config = {
|
|
679
1082
|
version: pkg2,
|
|
@@ -693,14 +1096,24 @@ ${existingFiles.map((f) => ` - ${f}`).join("\n")}`
|
|
|
693
1096
|
)
|
|
694
1097
|
);
|
|
695
1098
|
console.log(provider.getPostInitMessage());
|
|
696
|
-
if (projectProfile) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1099
|
+
if (projectProfile || memorySeedWritten) {
|
|
1100
|
+
if (memorySeedWritten) {
|
|
1101
|
+
console.log(
|
|
1102
|
+
chalk.cyan(
|
|
1103
|
+
"\n \u{1F4CC} Next step: Run "
|
|
1104
|
+
) + chalk.bold("/memorize bootstrap") + chalk.cyan(
|
|
1105
|
+
" in your AI assistant.\n It will see your seeded memory and offer to merge or refine it\n into properly organized standing instructions."
|
|
1106
|
+
)
|
|
1107
|
+
);
|
|
1108
|
+
} else {
|
|
1109
|
+
console.log(
|
|
1110
|
+
chalk.cyan(
|
|
1111
|
+
"\n \u{1F4CC} Next step: Run "
|
|
1112
|
+
) + chalk.bold("/memorize bootstrap") + chalk.cyan(
|
|
1113
|
+
" in your AI assistant to auto-generate\n coding standards, architecture guidelines, and best practices\n for your project based on the profile you just provided."
|
|
1114
|
+
)
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
704
1117
|
}
|
|
705
1118
|
}
|
|
706
1119
|
async function loadPackageVersion() {
|
|
@@ -710,7 +1123,7 @@ async function loadPackageVersion() {
|
|
|
710
1123
|
const pkg2 = require3("../../package.json");
|
|
711
1124
|
return pkg2.version;
|
|
712
1125
|
} catch {
|
|
713
|
-
return "
|
|
1126
|
+
return "0.0.4";
|
|
714
1127
|
}
|
|
715
1128
|
}
|
|
716
1129
|
|
|
@@ -750,24 +1163,9 @@ async function updateCommand(options) {
|
|
|
750
1163
|
let updated = 0;
|
|
751
1164
|
let preserved = 0;
|
|
752
1165
|
let unchanged = 0;
|
|
753
|
-
let migrated = 0;
|
|
754
1166
|
for (const prompt of prompts) {
|
|
755
1167
|
const targetRelPath = provider.getTargetPath(prompt.name);
|
|
756
1168
|
const targetAbsPath = path7.join(cwd, targetRelPath);
|
|
757
|
-
if (provider.alias === "copilot") {
|
|
758
|
-
const oldRelPath = path7.join(".github", "copilot", `${prompt.name}.md`);
|
|
759
|
-
const oldAbsPath = path7.join(cwd, oldRelPath);
|
|
760
|
-
if (targetRelPath !== oldRelPath && await fs7.pathExists(oldAbsPath) && !await fs7.pathExists(targetAbsPath)) {
|
|
761
|
-
await fs7.ensureDir(path7.dirname(targetAbsPath));
|
|
762
|
-
await fs7.rename(oldAbsPath, targetAbsPath);
|
|
763
|
-
console.log(
|
|
764
|
-
chalk2.cyan(
|
|
765
|
-
` \u2197 ${oldRelPath} \u2192 ${targetRelPath} (migrated to .prompt.md)`
|
|
766
|
-
)
|
|
767
|
-
);
|
|
768
|
-
migrated++;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
1169
|
const newContent = provider.transformPrompt(prompt.content, {
|
|
772
1170
|
name: prompt.name,
|
|
773
1171
|
title: prompt.title,
|
|
@@ -810,6 +1208,50 @@ async function updateCommand(options) {
|
|
|
810
1208
|
await fs7.writeFile(claudeMdPath, claudeMdContent, "utf-8");
|
|
811
1209
|
console.log(chalk2.green(` \u2713 CLAUDE.md (regenerated)`));
|
|
812
1210
|
}
|
|
1211
|
+
if (provider.alias === "copilot") {
|
|
1212
|
+
const copilotProvider = provider;
|
|
1213
|
+
for (const prompt of prompts) {
|
|
1214
|
+
const promptRelPath = copilotProvider.getPromptFilePath(prompt.name);
|
|
1215
|
+
const promptAbsPath = path7.join(cwd, promptRelPath);
|
|
1216
|
+
const newContent = copilotProvider.transformPromptFile(prompt.content, {
|
|
1217
|
+
name: prompt.name,
|
|
1218
|
+
title: prompt.title,
|
|
1219
|
+
description: prompt.description
|
|
1220
|
+
});
|
|
1221
|
+
if (!await fs7.pathExists(promptAbsPath)) {
|
|
1222
|
+
await fs7.ensureDir(path7.dirname(promptAbsPath));
|
|
1223
|
+
await fs7.writeFile(promptAbsPath, newContent, "utf-8");
|
|
1224
|
+
console.log(chalk2.green(` \u2713 ${promptRelPath} (restored)`));
|
|
1225
|
+
updated++;
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const currentContent = await fs7.readFile(promptAbsPath, "utf-8");
|
|
1229
|
+
if (currentContent === newContent) {
|
|
1230
|
+
unchanged++;
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (!options.force) {
|
|
1234
|
+
const userContext = extractProjectContext(currentContent);
|
|
1235
|
+
if (userContext) {
|
|
1236
|
+
const mergedContent = replaceProjectContext(newContent, userContext);
|
|
1237
|
+
await fs7.writeFile(promptAbsPath, mergedContent, "utf-8");
|
|
1238
|
+
console.log(chalk2.green(` \u2713 ${promptRelPath} (updated, Project Context preserved)`));
|
|
1239
|
+
preserved++;
|
|
1240
|
+
updated++;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
await fs7.writeFile(promptAbsPath, newContent, "utf-8");
|
|
1245
|
+
console.log(chalk2.green(` \u2713 ${promptRelPath} (updated)`));
|
|
1246
|
+
updated++;
|
|
1247
|
+
}
|
|
1248
|
+
const copilotInstructionsPath = path7.join(cwd, ".github", "copilot-instructions.md");
|
|
1249
|
+
await fs7.ensureDir(path7.join(cwd, ".github"));
|
|
1250
|
+
const existingContent = await fs7.pathExists(copilotInstructionsPath) ? await fs7.readFile(copilotInstructionsPath, "utf-8") : null;
|
|
1251
|
+
const merged = mergeCopilotInstructions(existingContent, config.installedPrompts);
|
|
1252
|
+
await fs7.writeFile(copilotInstructionsPath, merged, "utf-8");
|
|
1253
|
+
console.log(chalk2.green(` \u2713 .github/copilot-instructions.md (updated)`));
|
|
1254
|
+
}
|
|
813
1255
|
config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
814
1256
|
try {
|
|
815
1257
|
const { createRequire: createRequire2 } = await import("module");
|
|
@@ -822,7 +1264,7 @@ async function updateCommand(options) {
|
|
|
822
1264
|
console.log(
|
|
823
1265
|
chalk2.bold(
|
|
824
1266
|
`
|
|
825
|
-
Done! ${updated} updated, ${unchanged} unchanged, ${preserved} with preserved edits
|
|
1267
|
+
Done! ${updated} updated, ${unchanged} unchanged, ${preserved} with preserved edits.`
|
|
826
1268
|
)
|
|
827
1269
|
);
|
|
828
1270
|
}
|