spets 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1424 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateTaskId,
4
+ getOutputPath,
5
+ getSeptDir,
6
+ getWorkflowState,
7
+ listTasks,
8
+ loadConfig,
9
+ loadDocument,
10
+ loadStepDefinition,
11
+ loadTaskMetadata,
12
+ saveDocument,
13
+ saveTaskMetadata,
14
+ septExists,
15
+ updateDocumentStatus
16
+ } from "./chunk-KZQ5KNMC.js";
17
+
18
+ // src/index.ts
19
+ import { Command } from "commander";
20
+
21
+ // src/commands/init.ts
22
+ import { mkdirSync, writeFileSync } from "fs";
23
+ import { join, dirname } from "path";
24
+ import { fileURLToPath } from "url";
25
+ var __dirname = dirname(fileURLToPath(import.meta.url));
26
+ async function initCommand(options) {
27
+ const cwd = process.cwd();
28
+ const septDir = getSeptDir(cwd);
29
+ if (septExists(cwd) && !options.force) {
30
+ console.error("Spets already initialized. Use --force to overwrite.");
31
+ process.exit(1);
32
+ }
33
+ mkdirSync(septDir, { recursive: true });
34
+ mkdirSync(join(septDir, "steps"), { recursive: true });
35
+ mkdirSync(join(septDir, "outputs"), { recursive: true });
36
+ mkdirSync(join(septDir, "hooks"), { recursive: true });
37
+ const templatesDir = join(__dirname, "..", "templates");
38
+ writeFileSync(join(septDir, "config.yml"), getDefaultConfig());
39
+ createDefaultSteps(septDir);
40
+ console.log("Initialized spets in .sept/");
41
+ console.log("");
42
+ console.log("Created:");
43
+ console.log(" .sept/config.yml - Workflow configuration");
44
+ console.log(" .sept/steps/01-plan/ - Planning step");
45
+ console.log(" .sept/steps/02-implement/ - Implementation step");
46
+ if (options.github) {
47
+ createGitHubWorkflow(cwd);
48
+ console.log(" .github/workflows/sept.yml - GitHub Actions workflow");
49
+ }
50
+ console.log("");
51
+ console.log("Next steps:");
52
+ console.log(" 1. Edit .sept/config.yml to customize your workflow");
53
+ console.log(" 2. Customize step instructions in .sept/steps/");
54
+ if (options.github) {
55
+ console.log(" 3. Add ANTHROPIC_API_KEY to your repo secrets");
56
+ console.log(' 4. Run: spets start "task" --platform github --owner <owner> --repo <repo> --issue <n>');
57
+ } else {
58
+ console.log(' 3. Run: spets start "your task description"');
59
+ }
60
+ }
61
+ function getDefaultConfig() {
62
+ return `# Spets Configuration
63
+ # Define your workflow steps here
64
+
65
+ steps:
66
+ - 01-plan
67
+ - 02-implement
68
+
69
+ # Optional hooks (shell scripts)
70
+ # hooks:
71
+ # preStep: "./hooks/pre-step.sh"
72
+ # postStep: "./hooks/post-step.sh"
73
+ # onApprove: "./hooks/on-approve.sh"
74
+ # onReject: "./hooks/on-reject.sh"
75
+ # onComplete: "./hooks/on-complete.sh"
76
+ `;
77
+ }
78
+ function createDefaultSteps(septDir) {
79
+ const planDir = join(septDir, "steps", "01-plan");
80
+ mkdirSync(planDir, { recursive: true });
81
+ writeFileSync(join(planDir, "instruction.md"), getPlanInstruction());
82
+ writeFileSync(join(planDir, "template.md"), getPlanTemplate());
83
+ const implementDir = join(septDir, "steps", "02-implement");
84
+ mkdirSync(implementDir, { recursive: true });
85
+ writeFileSync(join(implementDir, "instruction.md"), getImplementInstruction());
86
+ writeFileSync(join(implementDir, "template.md"), getImplementTemplate());
87
+ }
88
+ function getPlanInstruction() {
89
+ return `# Plan Step
90
+
91
+ You are creating a technical plan for the given task.
92
+
93
+ ## Your Goal
94
+
95
+ Analyze the user's request and create a detailed implementation plan.
96
+
97
+ ## Process
98
+
99
+ 1. **Understand the Request**
100
+ - Parse the user's query to identify the core requirements
101
+ - Identify any ambiguities or missing information
102
+
103
+ 2. **Ask Clarifying Questions** (if needed)
104
+ - If requirements are unclear, list questions in the \`open_questions\` section
105
+ - Questions should be specific and actionable
106
+
107
+ 3. **Create the Plan**
108
+ - Break down the task into concrete steps
109
+ - Identify files to create/modify
110
+ - Consider edge cases and potential issues
111
+
112
+ ## Output Format
113
+
114
+ Follow the template provided. Include:
115
+ - Summary of what will be built
116
+ - Step-by-step implementation plan
117
+ - Files to be created/modified
118
+ - Any open questions (if requirements are unclear)
119
+ `;
120
+ }
121
+ function getPlanTemplate() {
122
+ return `# Plan: {{title}}
123
+
124
+ ## Summary
125
+
126
+ Brief description of what will be implemented.
127
+
128
+ ## Requirements
129
+
130
+ - Requirement 1
131
+ - Requirement 2
132
+
133
+ ## Implementation Steps
134
+
135
+ ### Step 1: [Description]
136
+
137
+ Details...
138
+
139
+ ### Step 2: [Description]
140
+
141
+ Details...
142
+
143
+ ## Files to Modify
144
+
145
+ | File | Action | Description |
146
+ |------|--------|-------------|
147
+ | path/to/file | Create/Modify | What changes |
148
+
149
+ ## Open Questions
150
+
151
+ <!-- Remove this section if no questions -->
152
+
153
+ - Question 1?
154
+ - Question 2?
155
+
156
+ ## Risks & Considerations
157
+
158
+ - Risk 1
159
+ - Risk 2
160
+ `;
161
+ }
162
+ function getImplementInstruction() {
163
+ return `# Implement Step
164
+
165
+ You are implementing the plan from the previous step.
166
+
167
+ ## Your Goal
168
+
169
+ Write the actual code based on the approved plan.
170
+
171
+ ## Process
172
+
173
+ 1. **Review the Plan**
174
+ - Read the approved plan document carefully
175
+ - Understand all requirements and steps
176
+
177
+ 2. **Implement**
178
+ - Follow the plan step by step
179
+ - Write clean, well-documented code
180
+ - Handle edge cases identified in the plan
181
+
182
+ 3. **Document Changes**
183
+ - List all files created/modified
184
+ - Explain key decisions made during implementation
185
+
186
+ ## Output Format
187
+
188
+ Follow the template provided. Include:
189
+ - Summary of implementation
190
+ - List of all changes made
191
+ - Any deviations from the plan (with justification)
192
+ - Testing notes
193
+ `;
194
+ }
195
+ function getImplementTemplate() {
196
+ return `# Implementation: {{title}}
197
+
198
+ ## Summary
199
+
200
+ Brief description of what was implemented.
201
+
202
+ ## Changes Made
203
+
204
+ ### New Files
205
+
206
+ | File | Description |
207
+ |------|-------------|
208
+ | path/to/file | What it does |
209
+
210
+ ### Modified Files
211
+
212
+ | File | Changes |
213
+ |------|---------|
214
+ | path/to/file | What changed |
215
+
216
+ ## Key Decisions
217
+
218
+ - Decision 1: Explanation
219
+ - Decision 2: Explanation
220
+
221
+ ## Deviations from Plan
222
+
223
+ <!-- Remove if no deviations -->
224
+
225
+ None / List any deviations with justification.
226
+
227
+ ## Testing
228
+
229
+ - [ ] Manual testing completed
230
+ - [ ] Unit tests added
231
+ - [ ] Integration tests pass
232
+
233
+ ## Next Steps
234
+
235
+ - Follow-up task 1
236
+ - Follow-up task 2
237
+ `;
238
+ }
239
+ function createGitHubWorkflow(cwd) {
240
+ const workflowDir = join(cwd, ".github", "workflows");
241
+ mkdirSync(workflowDir, { recursive: true });
242
+ writeFileSync(join(workflowDir, "sept.yml"), getGitHubWorkflow());
243
+ }
244
+ function getGitHubWorkflow() {
245
+ const gh = (expr) => `\${{ ${expr} }}`;
246
+ return `# Spets GitHub Action
247
+ # Runs Claude Code to handle /approve, /revise, /reject commands
248
+
249
+ name: Spets Workflow
250
+
251
+ on:
252
+ issue_comment:
253
+ types: [created]
254
+
255
+ jobs:
256
+ handle-sept-command:
257
+ # Only run if comment contains a sept command
258
+ if: |
259
+ contains(github.event.comment.body, '/approve') ||
260
+ contains(github.event.comment.body, '/revise') ||
261
+ contains(github.event.comment.body, '/reject') ||
262
+ contains(github.event.comment.body, '/answer')
263
+
264
+ runs-on: ubuntu-latest
265
+
266
+ steps:
267
+ - name: Checkout
268
+ uses: actions/checkout@v4
269
+ with:
270
+ fetch-depth: 0
271
+
272
+ - name: Setup Node.js
273
+ uses: actions/setup-node@v4
274
+ with:
275
+ node-version: '20'
276
+
277
+ - name: Install Claude Code
278
+ run: npm install -g @anthropic-ai/claude-code
279
+
280
+ - name: Install dependencies
281
+ run: npm ci
282
+
283
+ - name: Determine context
284
+ id: context
285
+ run: |
286
+ if [ -n "${gh("github.event.issue.pull_request")}" ]; then
287
+ echo "type=pr" >> $GITHUB_OUTPUT
288
+ else
289
+ echo "type=issue" >> $GITHUB_OUTPUT
290
+ fi
291
+ echo "number=${gh("github.event.issue.number")}" >> $GITHUB_OUTPUT
292
+
293
+ - name: Run Claude Code
294
+ run: |
295
+ claude -p "
296
+ You are running a Spets workflow via GitHub Actions.
297
+
298
+ Context:
299
+ - Repository: ${gh("github.repository")}
300
+ - Issue/PR: #${gh("github.event.issue.number")}
301
+ - Comment author: ${gh("github.event.comment.user.login")}
302
+ - Command received: $COMMENT_BODY
303
+
304
+ Instructions:
305
+ 1. Parse the command from the comment (/approve, /revise, /reject, or /answer)
306
+ 2. Find the active sept task in .sept/outputs/
307
+ 3. Execute the appropriate action:
308
+ - /approve: Mark current step as approved, generate next step
309
+ - /revise <feedback>: Regenerate current step with feedback
310
+ - /reject: Mark workflow as rejected
311
+ - /answer: Process answers and continue
312
+ 4. If generating content (plan or implementation), actually write the code/changes
313
+ 5. Post a summary comment to the PR/Issue using gh cli
314
+ 6. Commit any changes with a descriptive message
315
+
316
+ Use 'gh issue comment' or 'gh pr comment' to post updates.
317
+ "
318
+ env:
319
+ COMMENT_BODY: ${gh("github.event.comment.body")}
320
+ ANTHROPIC_API_KEY: ${gh("secrets.ANTHROPIC_API_KEY")}
321
+ GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
322
+
323
+ - name: Push changes
324
+ run: |
325
+ git config user.name "github-actions[bot]"
326
+ git config user.email "github-actions[bot]@users.noreply.github.com"
327
+ git add -A
328
+ git diff --staged --quiet || git commit -m "Sept: Update from workflow"
329
+ git push
330
+ env:
331
+ GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
332
+ `;
333
+ }
334
+
335
+ // src/commands/status.ts
336
+ async function statusCommand(options) {
337
+ const cwd = process.cwd();
338
+ if (!septExists(cwd)) {
339
+ console.error('Spets not initialized. Run "spets init" first.');
340
+ process.exit(1);
341
+ }
342
+ const config = loadConfig(cwd);
343
+ if (options.task) {
344
+ showTaskStatus(options.task, config, cwd);
345
+ } else {
346
+ showAllTasks(config, cwd);
347
+ }
348
+ }
349
+ function showTaskStatus(taskId, config, cwd) {
350
+ const state = getWorkflowState(taskId, config, cwd);
351
+ const meta = loadTaskMetadata(taskId, cwd);
352
+ if (!state) {
353
+ console.error(`Task '${taskId}' not found.`);
354
+ process.exit(1);
355
+ }
356
+ console.log(`Task: ${taskId}`);
357
+ console.log(`Query: ${meta?.userQuery || state.userQuery || "(unknown)"}`);
358
+ console.log(`Status: ${formatStatus(state.status)}`);
359
+ console.log(`Current Step: ${state.currentStepName} (${state.currentStepIndex + 1}/${config.steps.length})`);
360
+ console.log("");
361
+ console.log("Steps:");
362
+ for (let i = 0; i < config.steps.length; i++) {
363
+ const stepName = config.steps[i];
364
+ const output = state.outputs.get(stepName);
365
+ const isCurrent = i === state.currentStepIndex;
366
+ const marker = isCurrent ? "\u2192" : " ";
367
+ const status = output ? formatDocStatus(output.status) : "\u25CB";
368
+ console.log(` ${marker} ${i + 1}. ${stepName} ${status}`);
369
+ }
370
+ }
371
+ function showAllTasks(config, cwd) {
372
+ const tasks = listTasks(cwd);
373
+ if (tasks.length === 0) {
374
+ console.log("No tasks found.");
375
+ console.log("");
376
+ console.log('Start a new task with: sept start "your task description"');
377
+ return;
378
+ }
379
+ console.log("Tasks:");
380
+ console.log("");
381
+ for (const taskId of tasks.slice(0, 10)) {
382
+ const state = getWorkflowState(taskId, config, cwd);
383
+ const meta = loadTaskMetadata(taskId, cwd);
384
+ if (!state) continue;
385
+ const query = meta?.userQuery || state.userQuery || "(no query)";
386
+ const truncatedQuery = query.length > 50 ? query.substring(0, 47) + "..." : query;
387
+ const progress = `${state.currentStepIndex + 1}/${config.steps.length}`;
388
+ console.log(` ${taskId} ${formatStatus(state.status)} [${progress}]`);
389
+ console.log(` ${truncatedQuery}`);
390
+ console.log("");
391
+ }
392
+ if (tasks.length > 10) {
393
+ console.log(` ... and ${tasks.length - 10} more tasks`);
394
+ }
395
+ console.log("");
396
+ console.log('Use "sept status -t <taskId>" for details');
397
+ }
398
+ function formatStatus(status) {
399
+ const icons = {
400
+ in_progress: "\u{1F504} in_progress",
401
+ completed: "\u2705 completed",
402
+ paused: "\u23F8\uFE0F paused",
403
+ rejected: "\u274C rejected"
404
+ };
405
+ return icons[status] || status;
406
+ }
407
+ function formatDocStatus(status) {
408
+ const icons = {
409
+ draft: "\u25D0",
410
+ approved: "\u25CF",
411
+ rejected: "\u2717"
412
+ };
413
+ return icons[status] || "\u25CB";
414
+ }
415
+
416
+ // src/hooks/runner.ts
417
+ import { spawn } from "child_process";
418
+ import { existsSync as existsSync2 } from "fs";
419
+ import { join as join2, isAbsolute } from "path";
420
+ async function runHook(hookPath, context, cwd = process.cwd()) {
421
+ const resolvedPath = isAbsolute(hookPath) ? hookPath : join2(getSeptDir(cwd), hookPath);
422
+ if (!existsSync2(resolvedPath)) {
423
+ console.warn(`Hook not found: ${resolvedPath}`);
424
+ return;
425
+ }
426
+ const env = {
427
+ ...process.env,
428
+ SEPT_TASK_ID: context.taskId,
429
+ SEPT_STEP_NAME: context.stepName,
430
+ SEPT_STEP_INDEX: String(context.stepIndex),
431
+ SEPT_OUTPUT_PATH: context.outputPath,
432
+ SEPT_CWD: cwd
433
+ };
434
+ return new Promise((resolve, reject) => {
435
+ const proc = spawn(resolvedPath, [], {
436
+ cwd,
437
+ env,
438
+ stdio: "inherit",
439
+ shell: true
440
+ });
441
+ proc.on("close", (code) => {
442
+ if (code !== 0) {
443
+ console.warn(`Hook exited with code ${code}: ${resolvedPath}`);
444
+ }
445
+ resolve();
446
+ });
447
+ proc.on("error", (err) => {
448
+ console.warn(`Failed to run hook: ${err.message}`);
449
+ resolve();
450
+ });
451
+ });
452
+ }
453
+
454
+ // src/core/executor.ts
455
+ var Executor = class {
456
+ platform;
457
+ config;
458
+ cwd;
459
+ constructor(options) {
460
+ this.platform = options.platform;
461
+ this.config = options.config;
462
+ this.cwd = options.cwd || process.cwd();
463
+ }
464
+ async executeWorkflow(taskId, userQuery, startIndex = 0) {
465
+ let previousOutput;
466
+ if (startIndex > 0) {
467
+ const prevStepName = this.config.steps[startIndex - 1];
468
+ const prevDoc = loadDocument(taskId, prevStepName, this.cwd);
469
+ if (prevDoc) {
470
+ previousOutput = prevDoc.content;
471
+ }
472
+ }
473
+ for (let i = startIndex; i < this.config.steps.length; i++) {
474
+ const stepName = this.config.steps[i];
475
+ const stepDef = loadStepDefinition(stepName, this.cwd);
476
+ const context = {
477
+ taskId,
478
+ userQuery,
479
+ stepName,
480
+ stepIndex: i,
481
+ instruction: stepDef.instruction,
482
+ template: stepDef.template,
483
+ previousOutput,
484
+ outputPath: getOutputPath(taskId, stepName, this.cwd)
485
+ };
486
+ if (this.config.hooks?.preStep) {
487
+ await runHook(this.config.hooks.preStep, {
488
+ taskId,
489
+ stepName,
490
+ stepIndex: i,
491
+ outputPath: context.outputPath,
492
+ document: ""
493
+ }, this.cwd);
494
+ }
495
+ const result = await this.executeStep(context);
496
+ if (this.config.hooks?.postStep) {
497
+ await runHook(this.config.hooks.postStep, {
498
+ taskId,
499
+ stepName,
500
+ stepIndex: i,
501
+ outputPath: context.outputPath,
502
+ document: result.document
503
+ }, this.cwd);
504
+ }
505
+ if (result.rejected) {
506
+ console.log("\n\u274C Workflow rejected.");
507
+ if (this.config.hooks?.onReject) {
508
+ await runHook(this.config.hooks.onReject, {
509
+ taskId,
510
+ stepName,
511
+ stepIndex: i,
512
+ outputPath: context.outputPath,
513
+ document: result.document
514
+ }, this.cwd);
515
+ }
516
+ return;
517
+ }
518
+ if (result.paused) {
519
+ console.log("\n\u23F8\uFE0F Workflow paused. Resume with: sept resume");
520
+ return;
521
+ }
522
+ if (result.approved) {
523
+ if (this.config.hooks?.onApprove) {
524
+ await runHook(this.config.hooks.onApprove, {
525
+ taskId,
526
+ stepName,
527
+ stepIndex: i,
528
+ outputPath: context.outputPath,
529
+ document: result.document
530
+ }, this.cwd);
531
+ }
532
+ previousOutput = result.document;
533
+ }
534
+ }
535
+ console.log("\n\u2705 Workflow completed!");
536
+ if (this.config.hooks?.onComplete) {
537
+ await runHook(this.config.hooks.onComplete, {
538
+ taskId,
539
+ stepName: this.config.steps[this.config.steps.length - 1],
540
+ stepIndex: this.config.steps.length - 1,
541
+ outputPath: "",
542
+ document: previousOutput || ""
543
+ }, this.cwd);
544
+ }
545
+ }
546
+ async executeStep(context) {
547
+ let result = await this.platform.generateDocument(context);
548
+ saveDocument(
549
+ context.taskId,
550
+ context.stepName,
551
+ result.document,
552
+ "draft",
553
+ result.questions,
554
+ this.cwd
555
+ );
556
+ while (result.questions.length > 0) {
557
+ const answers = await this.platform.askUser(result.questions);
558
+ result = await this.platform.generateDocument({
559
+ ...context,
560
+ answers
561
+ });
562
+ saveDocument(
563
+ context.taskId,
564
+ context.stepName,
565
+ result.document,
566
+ "draft",
567
+ result.questions,
568
+ this.cwd
569
+ );
570
+ }
571
+ const approval = await this.platform.requestApproval(result.document, context.stepName);
572
+ if (approval.action === "revise") {
573
+ return this.executeStep({
574
+ ...context,
575
+ feedback: approval.feedback
576
+ });
577
+ }
578
+ const outputPath = getOutputPath(context.taskId, context.stepName, this.cwd);
579
+ if (approval.action === "approve") {
580
+ updateDocumentStatus(outputPath, "approved");
581
+ } else if (approval.action === "reject") {
582
+ updateDocumentStatus(outputPath, "rejected");
583
+ }
584
+ return {
585
+ document: result.document,
586
+ approved: approval.action === "approve",
587
+ rejected: approval.action === "reject",
588
+ paused: approval.action === "pause"
589
+ };
590
+ }
591
+ };
592
+
593
+ // src/platform/cli.ts
594
+ import { spawn as spawn2 } from "child_process";
595
+ import { input, select, confirm } from "@inquirer/prompts";
596
+
597
+ // src/platform/interface.ts
598
+ var BasePlatform = class {
599
+ };
600
+
601
+ // src/platform/cli.ts
602
+ var CliPlatform = class extends BasePlatform {
603
+ claudeCommand;
604
+ constructor(claudeCommand = "claude") {
605
+ super();
606
+ this.claudeCommand = claudeCommand;
607
+ }
608
+ async generateDocument(context) {
609
+ const prompt = this.buildPrompt(context);
610
+ console.log(`
611
+ \u{1F4DD} Generating ${context.stepName}...`);
612
+ const response = await this.callClaude(prompt);
613
+ const { document, questions } = this.parseResponse(response);
614
+ return { document, questions };
615
+ }
616
+ async askUser(questions) {
617
+ const answers = [];
618
+ console.log("\n\u2753 Please answer the following questions:\n");
619
+ for (const q of questions) {
620
+ if (q.context) {
621
+ console.log(`Context: ${q.context}`);
622
+ }
623
+ const answer = await input({
624
+ message: q.question
625
+ });
626
+ answers.push({
627
+ questionId: q.id,
628
+ answer
629
+ });
630
+ console.log("");
631
+ }
632
+ return answers;
633
+ }
634
+ async requestApproval(doc, stepName) {
635
+ console.log("\n" + "=".repeat(60));
636
+ console.log(`\u{1F4C4} ${stepName} Document`);
637
+ console.log("=".repeat(60));
638
+ console.log(doc);
639
+ console.log("=".repeat(60) + "\n");
640
+ const action = await select({
641
+ message: "What would you like to do?",
642
+ choices: [
643
+ { value: "approve", name: "\u2705 Approve - Continue to next step" },
644
+ { value: "revise", name: "\u270F\uFE0F Revise - Request changes" },
645
+ { value: "reject", name: "\u274C Reject - Stop workflow" },
646
+ { value: "pause", name: "\u23F8\uFE0F Pause - Save and exit" }
647
+ ]
648
+ });
649
+ if (action === "revise") {
650
+ const feedback = await input({
651
+ message: "What changes would you like?"
652
+ });
653
+ return { action: "revise", feedback };
654
+ }
655
+ if (action === "reject") {
656
+ const confirmed = await confirm({
657
+ message: "Are you sure you want to reject? This will stop the workflow.",
658
+ default: false
659
+ });
660
+ if (!confirmed) {
661
+ return this.requestApproval(doc, stepName);
662
+ }
663
+ }
664
+ return { action };
665
+ }
666
+ buildPrompt(context) {
667
+ const parts = [];
668
+ parts.push("# Task");
669
+ parts.push(`You are executing step "${context.stepName}" of a spec-driven development workflow.`);
670
+ parts.push("");
671
+ parts.push("## User Query");
672
+ parts.push(context.userQuery);
673
+ parts.push("");
674
+ parts.push("## Step Instructions");
675
+ parts.push(context.instruction);
676
+ parts.push("");
677
+ if (context.template) {
678
+ parts.push("## Output Template");
679
+ parts.push("Follow this template structure:");
680
+ parts.push(context.template);
681
+ parts.push("");
682
+ }
683
+ if (context.previousOutput) {
684
+ parts.push("## Previous Step Output");
685
+ parts.push(context.previousOutput);
686
+ parts.push("");
687
+ }
688
+ if (context.answers && context.answers.length > 0) {
689
+ parts.push("## Answers to Questions");
690
+ for (const a of context.answers) {
691
+ parts.push(`- ${a.questionId}: ${a.answer}`);
692
+ }
693
+ parts.push("");
694
+ }
695
+ if (context.feedback) {
696
+ parts.push("## Revision Feedback");
697
+ parts.push(context.feedback);
698
+ parts.push("");
699
+ }
700
+ parts.push("## Output Format");
701
+ parts.push("Generate the document content.");
702
+ parts.push("If you have clarifying questions, include them at the end in this format:");
703
+ parts.push("```questions");
704
+ parts.push("Q1: [Your question here]");
705
+ parts.push("Q2: [Another question if needed]");
706
+ parts.push("```");
707
+ parts.push("");
708
+ parts.push("Only ask questions if absolutely necessary for the task.");
709
+ return parts.join("\n");
710
+ }
711
+ async callClaude(prompt) {
712
+ return new Promise((resolve, reject) => {
713
+ const proc = spawn2(this.claudeCommand, ["-p", prompt], {
714
+ stdio: ["pipe", "pipe", "pipe"]
715
+ });
716
+ let stdout = "";
717
+ let stderr = "";
718
+ proc.stdout.on("data", (data) => {
719
+ stdout += data.toString();
720
+ process.stdout.write(data);
721
+ });
722
+ proc.stderr.on("data", (data) => {
723
+ stderr += data.toString();
724
+ });
725
+ proc.on("close", (code) => {
726
+ if (code !== 0) {
727
+ reject(new Error(`Claude CLI exited with code ${code}: ${stderr}`));
728
+ } else {
729
+ resolve(stdout);
730
+ }
731
+ });
732
+ proc.on("error", (err) => {
733
+ reject(new Error(`Failed to run Claude CLI: ${err.message}`));
734
+ });
735
+ });
736
+ }
737
+ parseResponse(response) {
738
+ const questions = [];
739
+ const questionMatch = response.match(/```questions\n([\s\S]*?)```/);
740
+ if (questionMatch) {
741
+ const questionsText = questionMatch[1];
742
+ const questionLines = questionsText.split("\n").filter((l) => l.trim());
743
+ for (const line of questionLines) {
744
+ const match = line.match(/^Q(\d+):\s*(.+)$/);
745
+ if (match) {
746
+ questions.push({
747
+ id: `q${match[1]}`,
748
+ question: match[2].trim()
749
+ });
750
+ }
751
+ }
752
+ }
753
+ const document = response.replace(/```questions\n[\s\S]*?```\n?/, "").trim();
754
+ return { document, questions };
755
+ }
756
+ };
757
+
758
+ // src/platform/github.ts
759
+ import { spawn as spawn3 } from "child_process";
760
+ var PauseForInputError = class extends Error {
761
+ constructor(inputType, data) {
762
+ super(`Paused waiting for ${inputType}`);
763
+ this.inputType = inputType;
764
+ this.data = data;
765
+ this.name = "PauseForInputError";
766
+ }
767
+ };
768
+ function parseGitHubCommand(comment) {
769
+ const trimmed = comment.trim();
770
+ if (trimmed === "/approve") {
771
+ return { command: "approve" };
772
+ }
773
+ if (trimmed === "/reject") {
774
+ return { command: "reject" };
775
+ }
776
+ const reviseMatch = trimmed.match(/^\/revise\s+(.+)$/s);
777
+ if (reviseMatch) {
778
+ return { command: "revise", feedback: reviseMatch[1].trim() };
779
+ }
780
+ const answerMatch = trimmed.match(/^\/answer\s*\n([\s\S]+)$/m);
781
+ if (answerMatch) {
782
+ const answersText = answerMatch[1];
783
+ const answers = {};
784
+ const answerLines = answersText.split("\n");
785
+ for (const line of answerLines) {
786
+ const match = line.match(/^(Q\d+):\s*(.+)$/i);
787
+ if (match) {
788
+ answers[match[1].toLowerCase()] = match[2].trim();
789
+ }
790
+ }
791
+ return { command: "answer", answers };
792
+ }
793
+ return { command: null };
794
+ }
795
+ var GitHubPlatform = class extends BasePlatform {
796
+ config;
797
+ currentTaskId;
798
+ constructor(config) {
799
+ super();
800
+ this.config = config;
801
+ }
802
+ setTaskId(taskId) {
803
+ this.currentTaskId = taskId;
804
+ }
805
+ async generateDocument(context) {
806
+ this.currentTaskId = context.taskId;
807
+ const prompt = this.buildPrompt(context);
808
+ const response = await this.callClaude(prompt);
809
+ const { document, questions } = this.parseResponse(response);
810
+ return { document, questions };
811
+ }
812
+ async askUser(questions) {
813
+ const comment = this.formatQuestionsComment(questions, this.currentTaskId);
814
+ await this.postComment(comment);
815
+ console.log("\n\u23F8\uFE0F Waiting for answers on GitHub...");
816
+ console.log(" The workflow will resume when questions are answered.");
817
+ throw new PauseForInputError("questions", questions);
818
+ }
819
+ async requestApproval(doc, stepName) {
820
+ const comment = this.formatApprovalComment(doc, stepName, this.currentTaskId);
821
+ await this.postComment(comment);
822
+ console.log("\n\u23F8\uFE0F Waiting for approval on GitHub...");
823
+ console.log(" Comment /approve, /revise <feedback>, or /reject on the PR/Issue.");
824
+ return { action: "pause" };
825
+ }
826
+ async postComment(body) {
827
+ if (this.config.prNumber) {
828
+ await this.postPRComment(body);
829
+ } else if (this.config.issueNumber) {
830
+ await this.postIssueComment(body);
831
+ } else {
832
+ throw new Error("Either prNumber or issueNumber must be specified");
833
+ }
834
+ }
835
+ formatQuestionsComment(questions, taskId) {
836
+ const lines = [
837
+ "## \u{1F4CB} Sept: Questions Need Answers",
838
+ "",
839
+ `> Task ID: \`${taskId}\``,
840
+ "",
841
+ "Please answer the following questions:",
842
+ ""
843
+ ];
844
+ for (let i = 0; i < questions.length; i++) {
845
+ const q = questions[i];
846
+ lines.push(`### Q${i + 1}: ${q.question}`);
847
+ if (q.context) {
848
+ lines.push(`> ${q.context}`);
849
+ }
850
+ lines.push("");
851
+ }
852
+ lines.push("---");
853
+ lines.push("**Reply with:**");
854
+ lines.push("```");
855
+ lines.push("/answer");
856
+ for (let i = 0; i < questions.length; i++) {
857
+ lines.push(`Q${i + 1}: your answer here`);
858
+ }
859
+ lines.push("```");
860
+ return lines.join("\n");
861
+ }
862
+ formatApprovalComment(doc, stepName, taskId) {
863
+ const lines = [
864
+ `## \u{1F4C4} Sept: ${stepName} - Review Required`,
865
+ "",
866
+ `> Task ID: \`${taskId}\``,
867
+ "",
868
+ "<details>",
869
+ "<summary>\u{1F4DD} View Document</summary>",
870
+ "",
871
+ "```markdown",
872
+ doc,
873
+ "```",
874
+ "",
875
+ "</details>",
876
+ "",
877
+ "---",
878
+ "",
879
+ "**Commands:**",
880
+ "| Command | Description |",
881
+ "|---------|-------------|",
882
+ "| `/approve` | Approve and continue to next step |",
883
+ "| `/revise <feedback>` | Request changes with feedback |",
884
+ "| `/reject` | Reject and stop workflow |"
885
+ ];
886
+ return lines.join("\n");
887
+ }
888
+ async postPRComment(body) {
889
+ const { owner, repo, prNumber } = this.config;
890
+ await this.runGh(["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`]);
891
+ }
892
+ async postIssueComment(body) {
893
+ const { owner, repo, issueNumber } = this.config;
894
+ await this.runGh(["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`]);
895
+ }
896
+ async runGh(args) {
897
+ return new Promise((resolve, reject) => {
898
+ const proc = spawn3("gh", args, { stdio: ["pipe", "pipe", "pipe"] });
899
+ let stdout = "";
900
+ let stderr = "";
901
+ proc.stdout.on("data", (data) => {
902
+ stdout += data.toString();
903
+ });
904
+ proc.stderr.on("data", (data) => {
905
+ stderr += data.toString();
906
+ });
907
+ proc.on("close", (code) => {
908
+ if (code !== 0) {
909
+ reject(new Error(`gh command failed: ${stderr}`));
910
+ } else {
911
+ resolve(stdout);
912
+ }
913
+ });
914
+ proc.on("error", (err) => {
915
+ reject(new Error(`Failed to run gh: ${err.message}`));
916
+ });
917
+ });
918
+ }
919
+ buildPrompt(context) {
920
+ const parts = [];
921
+ parts.push("# Task");
922
+ parts.push(`You are executing step "${context.stepName}" of a spec-driven development workflow.`);
923
+ parts.push("");
924
+ parts.push("## User Query");
925
+ parts.push(context.userQuery);
926
+ parts.push("");
927
+ parts.push("## Step Instructions");
928
+ parts.push(context.instruction);
929
+ if (context.template) {
930
+ parts.push("");
931
+ parts.push("## Output Template");
932
+ parts.push(context.template);
933
+ }
934
+ if (context.previousOutput) {
935
+ parts.push("");
936
+ parts.push("## Previous Step Output");
937
+ parts.push(context.previousOutput);
938
+ }
939
+ if (context.feedback) {
940
+ parts.push("");
941
+ parts.push("## Revision Feedback");
942
+ parts.push(context.feedback);
943
+ }
944
+ return parts.join("\n");
945
+ }
946
+ async callClaude(prompt) {
947
+ return new Promise((resolve, reject) => {
948
+ const proc = spawn3("claude", ["-p", prompt], { stdio: ["pipe", "pipe", "pipe"] });
949
+ let stdout = "";
950
+ let stderr = "";
951
+ proc.stdout.on("data", (data) => {
952
+ stdout += data.toString();
953
+ });
954
+ proc.stderr.on("data", (data) => {
955
+ stderr += data.toString();
956
+ });
957
+ proc.on("close", (code) => {
958
+ if (code !== 0) {
959
+ reject(new Error(`Claude CLI failed: ${stderr}`));
960
+ } else {
961
+ resolve(stdout);
962
+ }
963
+ });
964
+ proc.on("error", (err) => {
965
+ reject(new Error(`Failed to run Claude: ${err.message}`));
966
+ });
967
+ });
968
+ }
969
+ parseResponse(response) {
970
+ const questions = [];
971
+ const questionMatch = response.match(/```questions\n([\s\S]*?)```/);
972
+ if (questionMatch) {
973
+ const questionsText = questionMatch[1];
974
+ const questionLines = questionsText.split("\n").filter((l) => l.trim());
975
+ for (const line of questionLines) {
976
+ const match = line.match(/^Q(\d+):\s*(.+)$/);
977
+ if (match) {
978
+ questions.push({
979
+ id: `q${match[1]}`,
980
+ question: match[2].trim()
981
+ });
982
+ }
983
+ }
984
+ }
985
+ const document = response.replace(/```questions\n[\s\S]*?```\n?/, "").trim();
986
+ return { document, questions };
987
+ }
988
+ };
989
+
990
+ // src/commands/start.ts
991
+ async function startCommand(query, options) {
992
+ const cwd = process.cwd();
993
+ if (!septExists(cwd)) {
994
+ console.error('Spets not initialized. Run "spets init" first.');
995
+ process.exit(1);
996
+ }
997
+ const config = loadConfig(cwd);
998
+ const taskId = generateTaskId();
999
+ console.log(`Starting new workflow: ${taskId}`);
1000
+ console.log(`Query: ${query}`);
1001
+ console.log(`Platform: ${options.platform || "cli"}`);
1002
+ console.log("");
1003
+ saveTaskMetadata(taskId, query, cwd);
1004
+ let platform;
1005
+ if (options.platform === "github") {
1006
+ if (!options.owner || !options.repo) {
1007
+ console.error("GitHub platform requires --owner and --repo");
1008
+ process.exit(1);
1009
+ }
1010
+ if (!options.pr && !options.issue) {
1011
+ console.error("GitHub platform requires --pr or --issue");
1012
+ process.exit(1);
1013
+ }
1014
+ platform = new GitHubPlatform({
1015
+ owner: options.owner,
1016
+ repo: options.repo,
1017
+ prNumber: options.pr ? parseInt(options.pr, 10) : void 0,
1018
+ issueNumber: options.issue ? parseInt(options.issue, 10) : void 0
1019
+ });
1020
+ } else {
1021
+ platform = new CliPlatform();
1022
+ }
1023
+ const executor = new Executor({ platform, config, cwd });
1024
+ try {
1025
+ await executor.executeWorkflow(taskId, query);
1026
+ } catch (error) {
1027
+ if (error instanceof PauseForInputError) {
1028
+ console.log("\nWorkflow paused. Waiting for GitHub input.");
1029
+ console.log(`Task ID: ${taskId}`);
1030
+ return;
1031
+ }
1032
+ console.error("Workflow failed:", error instanceof Error ? error.message : error);
1033
+ process.exit(1);
1034
+ }
1035
+ }
1036
+
1037
+ // src/commands/resume.ts
1038
+ import { select as select2 } from "@inquirer/prompts";
1039
+ async function resumeCommand(options) {
1040
+ const cwd = process.cwd();
1041
+ if (!septExists(cwd)) {
1042
+ console.error('Spets not initialized. Run "spets init" first.');
1043
+ process.exit(1);
1044
+ }
1045
+ const config = loadConfig(cwd);
1046
+ let taskId = options.task;
1047
+ if (!taskId) {
1048
+ const tasks = listTasks(cwd);
1049
+ const resumableTasks = [];
1050
+ for (const tid of tasks) {
1051
+ const state2 = getWorkflowState(tid, config, cwd);
1052
+ const meta2 = loadTaskMetadata(tid, cwd);
1053
+ if (state2 && (state2.status === "in_progress" || state2.status === "paused")) {
1054
+ resumableTasks.push({
1055
+ taskId: tid,
1056
+ query: meta2?.userQuery || state2.userQuery || "(unknown)",
1057
+ status: state2.status
1058
+ });
1059
+ }
1060
+ }
1061
+ if (resumableTasks.length === 0) {
1062
+ console.log("No tasks to resume.");
1063
+ console.log('Start a new task with: sept start "your task description"');
1064
+ return;
1065
+ }
1066
+ if (resumableTasks.length === 1) {
1067
+ taskId = resumableTasks[0].taskId;
1068
+ } else {
1069
+ taskId = await select2({
1070
+ message: "Select task to resume:",
1071
+ choices: resumableTasks.map((t) => ({
1072
+ value: t.taskId,
1073
+ name: `${t.taskId} [${t.status}] - ${t.query.substring(0, 50)}${t.query.length > 50 ? "..." : ""}`
1074
+ }))
1075
+ });
1076
+ }
1077
+ }
1078
+ const state = getWorkflowState(taskId, config, cwd);
1079
+ const meta = loadTaskMetadata(taskId, cwd);
1080
+ if (!state) {
1081
+ console.error(`Task '${taskId}' not found.`);
1082
+ process.exit(1);
1083
+ }
1084
+ if (state.status === "completed") {
1085
+ console.log("Task already completed.");
1086
+ return;
1087
+ }
1088
+ if (state.status === "rejected") {
1089
+ console.log("Task was rejected. Cannot resume.");
1090
+ return;
1091
+ }
1092
+ const userQuery = meta?.userQuery || state.userQuery || "";
1093
+ console.log(`Resuming task: ${taskId}`);
1094
+ console.log(`Query: ${userQuery}`);
1095
+ console.log(`Current step: ${state.currentStepName}`);
1096
+ console.log("");
1097
+ if (options.approve) {
1098
+ const outputPath = getOutputPath(taskId, state.currentStepName, cwd);
1099
+ updateDocumentStatus(outputPath, "approved");
1100
+ console.log(`Approved: ${state.currentStepName}`);
1101
+ const nextIndex = state.currentStepIndex + 1;
1102
+ if (nextIndex >= config.steps.length) {
1103
+ console.log("\n\u2705 Workflow completed!");
1104
+ return;
1105
+ }
1106
+ const platform2 = new CliPlatform();
1107
+ const executor2 = new Executor({ platform: platform2, config, cwd });
1108
+ await executor2.executeWorkflow(taskId, userQuery, nextIndex);
1109
+ return;
1110
+ }
1111
+ if (options.revise) {
1112
+ const platform2 = new CliPlatform();
1113
+ const executor2 = new Executor({ platform: platform2, config, cwd });
1114
+ console.log(`Revising with feedback: ${options.revise}`);
1115
+ await executor2.executeWorkflow(taskId, userQuery, state.currentStepIndex);
1116
+ return;
1117
+ }
1118
+ const platform = new CliPlatform();
1119
+ const executor = new Executor({ platform, config, cwd });
1120
+ await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
1121
+ }
1122
+
1123
+ // src/commands/plugin.ts
1124
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync } from "fs";
1125
+ import { join as join3 } from "path";
1126
+ import { homedir } from "os";
1127
+ async function pluginCommand(action, name) {
1128
+ switch (action) {
1129
+ case "install":
1130
+ if (!name) {
1131
+ console.error("Plugin name required.");
1132
+ console.error("Usage: spets plugin install <name>");
1133
+ process.exit(1);
1134
+ }
1135
+ await installPlugin(name);
1136
+ break;
1137
+ case "uninstall":
1138
+ if (!name) {
1139
+ console.error("Plugin name required.");
1140
+ console.error("Usage: spets plugin uninstall <name>");
1141
+ process.exit(1);
1142
+ }
1143
+ await uninstallPlugin(name);
1144
+ break;
1145
+ case "list":
1146
+ await listPlugins();
1147
+ break;
1148
+ default:
1149
+ console.error(`Unknown action: ${action}`);
1150
+ console.error("Available actions: install, uninstall, list");
1151
+ process.exit(1);
1152
+ }
1153
+ }
1154
+ async function installPlugin(name) {
1155
+ const plugins = {
1156
+ claude: installClaudePlugin
1157
+ };
1158
+ const installer = plugins[name];
1159
+ if (!installer) {
1160
+ console.error(`Unknown plugin: ${name}`);
1161
+ console.error("Available plugins: claude");
1162
+ process.exit(1);
1163
+ }
1164
+ installer();
1165
+ }
1166
+ function installClaudePlugin() {
1167
+ const claudeDir = join3(homedir(), ".claude");
1168
+ const commandsDir = join3(claudeDir, "commands");
1169
+ mkdirSync2(commandsDir, { recursive: true });
1170
+ const skillPath = join3(commandsDir, "spets.md");
1171
+ writeFileSync2(skillPath, getClaudeSkillContent());
1172
+ console.log("Installed Claude Code plugin.");
1173
+ console.log(`Location: ${skillPath}`);
1174
+ console.log("");
1175
+ console.log("Usage in Claude Code:");
1176
+ console.log(' /spets start "your task description"');
1177
+ console.log(" /spets status");
1178
+ console.log(" /spets resume");
1179
+ }
1180
+ async function uninstallPlugin(name) {
1181
+ if (name === "claude") {
1182
+ const skillPath = join3(homedir(), ".claude", "commands", "spets.md");
1183
+ if (existsSync3(skillPath)) {
1184
+ rmSync(skillPath);
1185
+ console.log("Uninstalled Claude Code plugin.");
1186
+ } else {
1187
+ console.log("Claude Code plugin not installed.");
1188
+ }
1189
+ return;
1190
+ }
1191
+ console.error(`Unknown plugin: ${name}`);
1192
+ process.exit(1);
1193
+ }
1194
+ async function listPlugins() {
1195
+ console.log("Available plugins:");
1196
+ console.log("");
1197
+ console.log(" claude - Claude Code slash command integration");
1198
+ console.log("");
1199
+ const claudeSkillPath = join3(homedir(), ".claude", "commands", "spets.md");
1200
+ const claudeInstalled = existsSync3(claudeSkillPath);
1201
+ console.log("Installed:");
1202
+ if (claudeInstalled) {
1203
+ console.log(" - claude");
1204
+ } else {
1205
+ console.log(" (none)");
1206
+ }
1207
+ }
1208
+ function getClaudeSkillContent() {
1209
+ return `# Spets - Spec Driven Development
1210
+
1211
+ Run spets workflows from Claude Code.
1212
+
1213
+ ## Usage
1214
+
1215
+ \`\`\`
1216
+ /spets start "task description" - Start a new workflow
1217
+ /spets status - Show workflow status
1218
+ /spets resume - Resume paused workflow
1219
+ \`\`\`
1220
+
1221
+ ## Instructions
1222
+
1223
+ When the user invokes this command:
1224
+
1225
+ 1. Parse the subcommand (start, status, resume)
1226
+ 2. Execute the appropriate spets CLI command using Bash
1227
+ 3. For 'start', read the generated documents and help iterate
1228
+
1229
+ ### Start Flow
1230
+
1231
+ 1. Run: \`spets start "<query>"\`
1232
+ 2. Read the generated plan document from .sept/outputs/<taskId>/
1233
+ 3. Present the plan to the user
1234
+ 4. If user approves, continue. If they want changes, provide feedback.
1235
+
1236
+ ### Status Flow
1237
+
1238
+ 1. Run: \`spets status\`
1239
+ 2. Present the current workflow state
1240
+
1241
+ ### Resume Flow
1242
+
1243
+ 1. Run: \`spets resume\`
1244
+ 2. Continue the workflow from where it paused
1245
+
1246
+ ## Example Session
1247
+
1248
+ User: /spets start "Create a REST API for user management"
1249
+
1250
+ Claude:
1251
+ 1. Runs \`spets start "Create a REST API for user management"\`
1252
+ 2. Reads the generated plan
1253
+ 3. Asks user: "Here's the plan. [shows plan] Would you like to approve or revise?"
1254
+ 4. On approve: \`spets resume --approve\`
1255
+ 5. On revise: \`spets resume --revise "user feedback"\`
1256
+
1257
+ $ARGUMENTS
1258
+ command: The spets command to run (start, status, resume)
1259
+ args: Additional arguments for the command
1260
+ `;
1261
+ }
1262
+
1263
+ // src/commands/github.ts
1264
+ async function githubCommand(options) {
1265
+ const cwd = process.cwd();
1266
+ if (!septExists(cwd)) {
1267
+ console.error("Spets not initialized.");
1268
+ process.exit(1);
1269
+ }
1270
+ const { owner, repo, pr, issue, comment, task } = options;
1271
+ if (!pr && !issue) {
1272
+ console.error("Either --pr or --issue is required");
1273
+ process.exit(1);
1274
+ }
1275
+ const parsed = parseGitHubCommand(comment);
1276
+ if (!parsed.command) {
1277
+ console.log("No sept command found in comment. Ignoring.");
1278
+ return;
1279
+ }
1280
+ console.log(`Received command: ${parsed.command}`);
1281
+ let taskId = task;
1282
+ if (!taskId) {
1283
+ const taskMatch = comment.match(/Task ID: `([^`]+)`/);
1284
+ if (taskMatch) {
1285
+ taskId = taskMatch[1];
1286
+ }
1287
+ }
1288
+ if (!taskId) {
1289
+ const config2 = loadConfig(cwd);
1290
+ const { listTasks: listTasks2 } = await import("./state-MCFJFWJC.js");
1291
+ const tasks = listTasks2(cwd);
1292
+ for (const tid of tasks) {
1293
+ const state2 = getWorkflowState(tid, config2, cwd);
1294
+ if (state2 && (state2.status === "in_progress" || state2.status === "paused")) {
1295
+ taskId = tid;
1296
+ break;
1297
+ }
1298
+ }
1299
+ }
1300
+ if (!taskId) {
1301
+ console.error("Could not determine task ID. Use --task option.");
1302
+ process.exit(1);
1303
+ }
1304
+ const config = loadConfig(cwd);
1305
+ const state = getWorkflowState(taskId, config, cwd);
1306
+ const meta = loadTaskMetadata(taskId, cwd);
1307
+ if (!state) {
1308
+ console.error(`Task '${taskId}' not found.`);
1309
+ process.exit(1);
1310
+ }
1311
+ const userQuery = meta?.userQuery || state.userQuery || "";
1312
+ const outputPath = getOutputPath(taskId, state.currentStepName, cwd);
1313
+ const githubConfig = {
1314
+ owner,
1315
+ repo,
1316
+ prNumber: pr ? parseInt(pr, 10) : void 0,
1317
+ issueNumber: issue ? parseInt(issue, 10) : void 0
1318
+ };
1319
+ switch (parsed.command) {
1320
+ case "approve": {
1321
+ console.log(`Approving step: ${state.currentStepName}`);
1322
+ updateDocumentStatus(outputPath, "approved");
1323
+ const nextIndex = state.currentStepIndex + 1;
1324
+ if (nextIndex >= config.steps.length) {
1325
+ console.log("\u2705 Workflow completed!");
1326
+ await postCompletionComment(githubConfig, taskId);
1327
+ return;
1328
+ }
1329
+ const platform = new GitHubPlatform(githubConfig);
1330
+ const executor = new Executor({ platform, config, cwd });
1331
+ try {
1332
+ await executor.executeWorkflow(taskId, userQuery, nextIndex);
1333
+ } catch (error) {
1334
+ if (error.name !== "PauseForInputError") {
1335
+ throw error;
1336
+ }
1337
+ }
1338
+ break;
1339
+ }
1340
+ case "revise": {
1341
+ console.log(`Revising step with feedback: ${parsed.feedback}`);
1342
+ const platform = new GitHubPlatform(githubConfig);
1343
+ const executor = new Executor({ platform, config, cwd });
1344
+ try {
1345
+ await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
1346
+ } catch (error) {
1347
+ if (error.name !== "PauseForInputError") {
1348
+ throw error;
1349
+ }
1350
+ }
1351
+ break;
1352
+ }
1353
+ case "reject": {
1354
+ console.log("Rejecting workflow");
1355
+ updateDocumentStatus(outputPath, "rejected");
1356
+ await postRejectionComment(githubConfig, taskId, state.currentStepName);
1357
+ break;
1358
+ }
1359
+ case "answer": {
1360
+ console.log("Processing answers");
1361
+ const doc = loadDocument(taskId, state.currentStepName, cwd);
1362
+ if (!doc?.frontmatter.open_questions?.length) {
1363
+ console.log("No open questions for this task.");
1364
+ return;
1365
+ }
1366
+ const answers = Object.entries(parsed.answers || {}).map(([id, answer], index) => ({
1367
+ questionId: id,
1368
+ answer
1369
+ }));
1370
+ const platform = new GitHubPlatform(githubConfig);
1371
+ const executor = new Executor({ platform, config, cwd });
1372
+ try {
1373
+ await executor.executeWorkflow(taskId, userQuery, state.currentStepIndex);
1374
+ } catch (error) {
1375
+ if (error.name !== "PauseForInputError") {
1376
+ throw error;
1377
+ }
1378
+ }
1379
+ break;
1380
+ }
1381
+ }
1382
+ }
1383
+ async function postCompletionComment(config, taskId) {
1384
+ const comment = [
1385
+ "## \u2705 Spets Workflow Completed",
1386
+ "",
1387
+ `Task \`${taskId}\` has been completed successfully!`,
1388
+ "",
1389
+ "All steps have been approved."
1390
+ ].join("\n");
1391
+ await postComment(config, comment);
1392
+ }
1393
+ async function postRejectionComment(config, taskId, stepName) {
1394
+ const comment = [
1395
+ "## \u274C Spets Workflow Rejected",
1396
+ "",
1397
+ `Task \`${taskId}\` was rejected at step: **${stepName}**`
1398
+ ].join("\n");
1399
+ await postComment(config, comment);
1400
+ }
1401
+ async function postComment(config, body) {
1402
+ const { spawn: spawn4 } = await import("child_process");
1403
+ const { owner, repo, prNumber, issueNumber } = config;
1404
+ const args = prNumber ? ["pr", "comment", String(prNumber), "--body", body, "-R", `${owner}/${repo}`] : ["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`];
1405
+ return new Promise((resolve, reject) => {
1406
+ const proc = spawn4("gh", args, { stdio: "inherit" });
1407
+ proc.on("close", (code) => {
1408
+ if (code !== 0) reject(new Error(`gh failed with code ${code}`));
1409
+ else resolve();
1410
+ });
1411
+ proc.on("error", reject);
1412
+ });
1413
+ }
1414
+
1415
+ // src/index.ts
1416
+ var program = new Command();
1417
+ program.name("spets").description("Spec Driven Development Execution Framework").version("0.1.0");
1418
+ program.command("init").description("Initialize sept in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
1419
+ program.command("status").description("Show current workflow status").option("-t, --task <taskId>", "Show status for specific task").action(statusCommand);
1420
+ program.command("start").description("Start a new workflow").argument("<query>", "User query describing the task").option("-p, --platform <platform>", "Platform to use (cli, github)", "cli").option("--owner <owner>", "GitHub owner (for github platform)").option("--repo <repo>", "GitHub repo (for github platform)").option("--pr <number>", "GitHub PR number (for github platform)").option("--issue <number>", "GitHub issue number (for github platform)").action(startCommand);
1421
+ program.command("resume").description("Resume paused workflow").option("-t, --task <taskId>", "Resume specific task").option("--approve", "Approve current document and proceed").option("--revise <feedback>", "Request revision with feedback").action(resumeCommand);
1422
+ program.command("github").description("Handle GitHub Action callback (internal)").requiredOption("--owner <owner>", "GitHub owner").requiredOption("--repo <repo>", "GitHub repo").option("--pr <number>", "PR number").option("--issue <number>", "Issue number").option("-t, --task <taskId>", "Task ID").requiredOption("--comment <comment>", "Comment body").action(githubCommand);
1423
+ program.command("plugin").description("Manage plugins").argument("<action>", "Action: install, uninstall, list").argument("[name]", "Plugin name").action(pluginCommand);
1424
+ program.parse();