spets 0.1.19 → 0.1.20
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 +864 -408
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { readFileSync as
|
|
6
|
-
import { dirname as
|
|
5
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
6
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
@@ -589,7 +589,7 @@ function formatDocStatus(status) {
|
|
|
589
589
|
}
|
|
590
590
|
|
|
591
591
|
// src/commands/start.ts
|
|
592
|
-
import { execSync as
|
|
592
|
+
import { execSync as execSync2 } from "child_process";
|
|
593
593
|
|
|
594
594
|
// src/orchestrator/index.ts
|
|
595
595
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
@@ -671,7 +671,56 @@ var Orchestrator = class {
|
|
|
671
671
|
// ===========================================================================
|
|
672
672
|
// Protocol Response Builders
|
|
673
673
|
// ===========================================================================
|
|
674
|
-
|
|
674
|
+
/**
|
|
675
|
+
* Phase 1: Context gathering
|
|
676
|
+
*/
|
|
677
|
+
responsePhaseContext(state) {
|
|
678
|
+
const steps = this.getSteps();
|
|
679
|
+
const outputPath = this.getOutputPath();
|
|
680
|
+
let previousOutput;
|
|
681
|
+
if (state.stepIndex > 1) {
|
|
682
|
+
const prevStep = steps[state.stepIndex - 2];
|
|
683
|
+
const prevPath = join4(outputPath, state.taskId, `${prevStep}.md`);
|
|
684
|
+
if (existsSync4(prevPath)) {
|
|
685
|
+
previousOutput = prevPath;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
type: "phase",
|
|
690
|
+
phase: "context",
|
|
691
|
+
step: state.currentStep,
|
|
692
|
+
stepIndex: state.stepIndex,
|
|
693
|
+
totalSteps: state.totalSteps,
|
|
694
|
+
taskId: state.taskId,
|
|
695
|
+
description: state.description,
|
|
696
|
+
context: {
|
|
697
|
+
instruction: this.getStepInstructionPath(state.currentStep),
|
|
698
|
+
previousOutput
|
|
699
|
+
},
|
|
700
|
+
onComplete: `context-done ${state.taskId}`
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Phase 2: Question generation
|
|
705
|
+
*/
|
|
706
|
+
responsePhaseClarify(state) {
|
|
707
|
+
return {
|
|
708
|
+
type: "phase",
|
|
709
|
+
phase: "clarify",
|
|
710
|
+
step: state.currentStep,
|
|
711
|
+
taskId: state.taskId,
|
|
712
|
+
description: state.description,
|
|
713
|
+
gatheredContext: state.context || "",
|
|
714
|
+
context: {
|
|
715
|
+
instruction: this.getStepInstructionPath(state.currentStep)
|
|
716
|
+
},
|
|
717
|
+
onComplete: `clarify-done ${state.taskId}`
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Phase 3: Document generation
|
|
722
|
+
*/
|
|
723
|
+
responsePhaseGenerate(state) {
|
|
675
724
|
const steps = this.getSteps();
|
|
676
725
|
const outputPath = this.getOutputPath();
|
|
677
726
|
let previousOutput;
|
|
@@ -685,12 +734,15 @@ var Orchestrator = class {
|
|
|
685
734
|
const templatePath = this.getStepTemplatePath(state.currentStep);
|
|
686
735
|
const hasTemplate = existsSync4(templatePath);
|
|
687
736
|
return {
|
|
688
|
-
type: "
|
|
737
|
+
type: "phase",
|
|
738
|
+
phase: "generate",
|
|
689
739
|
step: state.currentStep,
|
|
690
740
|
stepIndex: state.stepIndex,
|
|
691
741
|
totalSteps: state.totalSteps,
|
|
692
742
|
taskId: state.taskId,
|
|
693
743
|
description: state.description,
|
|
744
|
+
gatheredContext: state.context || "",
|
|
745
|
+
answers: state.answers,
|
|
694
746
|
context: {
|
|
695
747
|
instruction: this.getStepInstructionPath(state.currentStep),
|
|
696
748
|
template: hasTemplate ? templatePath : void 0,
|
|
@@ -698,24 +750,25 @@ var Orchestrator = class {
|
|
|
698
750
|
output: join4(outputPath, state.taskId, `${state.currentStep}.md`),
|
|
699
751
|
revisionFeedback: state.revisionFeedback
|
|
700
752
|
},
|
|
701
|
-
onComplete: `done ${state.taskId}`
|
|
753
|
+
onComplete: `generate-done ${state.taskId}`
|
|
702
754
|
};
|
|
703
755
|
}
|
|
704
|
-
|
|
756
|
+
/**
|
|
757
|
+
* Checkpoint: Questions need answers
|
|
758
|
+
*/
|
|
759
|
+
responseCheckpointClarify(state) {
|
|
705
760
|
return {
|
|
706
761
|
type: "checkpoint",
|
|
707
762
|
checkpoint: "clarify",
|
|
708
763
|
taskId: state.taskId,
|
|
709
764
|
step: state.currentStep,
|
|
710
|
-
questions: questions
|
|
711
|
-
id: q.id,
|
|
712
|
-
question: q.question,
|
|
713
|
-
context: q.context,
|
|
714
|
-
options: q.options
|
|
715
|
-
})),
|
|
765
|
+
questions: state.questions || [],
|
|
716
766
|
onComplete: `clarified ${state.taskId} '<answers_json>'`
|
|
717
767
|
};
|
|
718
768
|
}
|
|
769
|
+
/**
|
|
770
|
+
* Checkpoint: Document needs approval
|
|
771
|
+
*/
|
|
719
772
|
responseCheckpointApprove(state) {
|
|
720
773
|
const outputPath = this.getOutputPath();
|
|
721
774
|
return {
|
|
@@ -768,7 +821,7 @@ var Orchestrator = class {
|
|
|
768
821
|
// Command Handlers
|
|
769
822
|
// ===========================================================================
|
|
770
823
|
/**
|
|
771
|
-
* Initialize a new workflow
|
|
824
|
+
* Initialize a new workflow - starts at phase_context
|
|
772
825
|
*/
|
|
773
826
|
cmdInit(description) {
|
|
774
827
|
try {
|
|
@@ -780,62 +833,107 @@ var Orchestrator = class {
|
|
|
780
833
|
currentStep: steps[0],
|
|
781
834
|
stepIndex: 1,
|
|
782
835
|
totalSteps: steps.length,
|
|
783
|
-
status: "
|
|
836
|
+
status: "phase_context",
|
|
837
|
+
phase: "context",
|
|
784
838
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
785
839
|
};
|
|
786
840
|
this.saveState(state);
|
|
787
|
-
return this.
|
|
841
|
+
return this.responsePhaseContext(state);
|
|
788
842
|
} catch (e) {
|
|
789
843
|
return this.responseError(e.message);
|
|
790
844
|
}
|
|
791
845
|
}
|
|
792
846
|
/**
|
|
793
|
-
*
|
|
847
|
+
* Phase 1 complete: Context gathered, move to clarify phase
|
|
794
848
|
*/
|
|
795
|
-
|
|
849
|
+
cmdContextDone(taskId, context) {
|
|
796
850
|
const state = this.loadState(taskId);
|
|
797
851
|
if (!state) {
|
|
798
852
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
799
853
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
854
|
+
state.context = context;
|
|
855
|
+
state.status = "phase_clarify";
|
|
856
|
+
state.phase = "clarify";
|
|
857
|
+
this.saveState(state);
|
|
858
|
+
return this.responsePhaseClarify(state);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Phase 2 complete: Questions generated (or none)
|
|
862
|
+
* If questions exist → checkpoint
|
|
863
|
+
* If no questions → move to generate phase
|
|
864
|
+
*/
|
|
865
|
+
cmdClarifyDone(taskId, questions) {
|
|
866
|
+
const state = this.loadState(taskId);
|
|
867
|
+
if (!state) {
|
|
868
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
803
869
|
}
|
|
804
|
-
const questions = this.checkUnresolvedQuestions(specPath);
|
|
805
870
|
if (questions.length > 0) {
|
|
806
|
-
state.
|
|
871
|
+
state.questions = questions;
|
|
872
|
+
state.status = "clarify_pending";
|
|
873
|
+
state.phase = "clarify";
|
|
807
874
|
this.saveState(state);
|
|
808
|
-
return this.responseCheckpointClarify(state
|
|
875
|
+
return this.responseCheckpointClarify(state);
|
|
809
876
|
}
|
|
810
|
-
state.status = "
|
|
877
|
+
state.status = "phase_generate";
|
|
878
|
+
state.phase = "generate";
|
|
811
879
|
this.saveState(state);
|
|
812
|
-
return this.
|
|
880
|
+
return this.responsePhaseGenerate(state);
|
|
813
881
|
}
|
|
814
882
|
/**
|
|
815
|
-
*
|
|
883
|
+
* Human answered questions, move to generate phase
|
|
816
884
|
*/
|
|
817
885
|
cmdClarified(taskId, answers) {
|
|
886
|
+
const state = this.loadState(taskId);
|
|
887
|
+
if (!state) {
|
|
888
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
889
|
+
}
|
|
890
|
+
state.answers = answers;
|
|
891
|
+
state.status = "phase_generate";
|
|
892
|
+
state.phase = "generate";
|
|
893
|
+
this.saveState(state);
|
|
894
|
+
return this.responsePhaseGenerate(state);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Phase 3 complete: Document generated, move to approve checkpoint
|
|
898
|
+
*/
|
|
899
|
+
cmdGenerateDone(taskId) {
|
|
818
900
|
const state = this.loadState(taskId);
|
|
819
901
|
if (!state) {
|
|
820
902
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
821
903
|
}
|
|
822
904
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
823
|
-
if (existsSync4(specPath)) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
905
|
+
if (!existsSync4(specPath)) {
|
|
906
|
+
return this.responseError(`Document not found: ${specPath}`, taskId, state.currentStep);
|
|
907
|
+
}
|
|
908
|
+
state.status = "approve_pending";
|
|
909
|
+
state.phase = "review";
|
|
910
|
+
this.saveState(state);
|
|
911
|
+
return this.responseCheckpointApprove(state);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* @deprecated Use cmdContextDone, cmdClarifyDone, cmdGenerateDone instead
|
|
915
|
+
*/
|
|
916
|
+
cmdDone(taskId) {
|
|
917
|
+
const state = this.loadState(taskId);
|
|
918
|
+
if (!state) {
|
|
919
|
+
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
920
|
+
}
|
|
921
|
+
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
922
|
+
if (!existsSync4(specPath)) {
|
|
923
|
+
return this.responseError(`Spec not found: ${specPath}`, taskId, state.currentStep);
|
|
924
|
+
}
|
|
925
|
+
const questions = this.checkUnresolvedQuestions(specPath);
|
|
926
|
+
if (questions.length > 0) {
|
|
927
|
+
state.questions = questions;
|
|
928
|
+
state.status = "clarify_pending";
|
|
929
|
+
state.phase = "clarify";
|
|
930
|
+
this.saveState(state);
|
|
931
|
+
return this.responseCheckpointClarify(state);
|
|
834
932
|
}
|
|
835
|
-
state.status = "
|
|
836
|
-
state.
|
|
933
|
+
state.status = "approve_pending";
|
|
934
|
+
state.phase = "review";
|
|
837
935
|
this.saveState(state);
|
|
838
|
-
return this.
|
|
936
|
+
return this.responseCheckpointApprove(state);
|
|
839
937
|
}
|
|
840
938
|
/**
|
|
841
939
|
* Approve current step and move to next
|
|
@@ -857,10 +955,14 @@ var Orchestrator = class {
|
|
|
857
955
|
if (state.stepIndex < state.totalSteps) {
|
|
858
956
|
state.currentStep = steps[state.stepIndex];
|
|
859
957
|
state.stepIndex += 1;
|
|
860
|
-
state.status = "
|
|
958
|
+
state.status = "phase_context";
|
|
959
|
+
state.phase = "context";
|
|
960
|
+
state.context = void 0;
|
|
961
|
+
state.questions = void 0;
|
|
962
|
+
state.answers = void 0;
|
|
861
963
|
state.revisionFeedback = void 0;
|
|
862
964
|
this.saveState(state);
|
|
863
|
-
return this.
|
|
965
|
+
return this.responsePhaseContext(state);
|
|
864
966
|
} else {
|
|
865
967
|
state.status = "completed";
|
|
866
968
|
this.saveState(state);
|
|
@@ -868,17 +970,18 @@ var Orchestrator = class {
|
|
|
868
970
|
}
|
|
869
971
|
}
|
|
870
972
|
/**
|
|
871
|
-
* Request revision with feedback
|
|
973
|
+
* Request revision with feedback - goes back to generate phase
|
|
872
974
|
*/
|
|
873
975
|
cmdRevise(taskId, feedback) {
|
|
874
976
|
const state = this.loadState(taskId);
|
|
875
977
|
if (!state) {
|
|
876
978
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
877
979
|
}
|
|
878
|
-
state.status = "
|
|
980
|
+
state.status = "phase_generate";
|
|
981
|
+
state.phase = "generate";
|
|
879
982
|
state.revisionFeedback = feedback;
|
|
880
983
|
this.saveState(state);
|
|
881
|
-
return this.
|
|
984
|
+
return this.responsePhaseGenerate(state);
|
|
882
985
|
}
|
|
883
986
|
/**
|
|
884
987
|
* Reject and stop workflow
|
|
@@ -921,13 +1024,15 @@ var Orchestrator = class {
|
|
|
921
1024
|
return this.responseError(`No workflow found: ${taskId}`, taskId);
|
|
922
1025
|
}
|
|
923
1026
|
switch (state.status) {
|
|
924
|
-
case "
|
|
925
|
-
return this.
|
|
926
|
-
case "
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
case "
|
|
1027
|
+
case "phase_context":
|
|
1028
|
+
return this.responsePhaseContext(state);
|
|
1029
|
+
case "phase_clarify":
|
|
1030
|
+
return this.responsePhaseClarify(state);
|
|
1031
|
+
case "clarify_pending":
|
|
1032
|
+
return this.responseCheckpointClarify(state);
|
|
1033
|
+
case "phase_generate":
|
|
1034
|
+
return this.responsePhaseGenerate(state);
|
|
1035
|
+
case "approve_pending":
|
|
931
1036
|
return this.responseCheckpointApprove(state);
|
|
932
1037
|
case "completed":
|
|
933
1038
|
case "stopped":
|
|
@@ -940,9 +1045,233 @@ var Orchestrator = class {
|
|
|
940
1045
|
};
|
|
941
1046
|
|
|
942
1047
|
// src/core/step-executor.ts
|
|
943
|
-
import { existsSync as
|
|
944
|
-
import { join as join5 } from "path";
|
|
1048
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
945
1049
|
import matter3 from "gray-matter";
|
|
1050
|
+
|
|
1051
|
+
// src/core/prompt-builder.ts
|
|
1052
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
1053
|
+
import { join as join5 } from "path";
|
|
1054
|
+
function buildContextPrompt(params) {
|
|
1055
|
+
const cwd = params.cwd || process.cwd();
|
|
1056
|
+
const stepsDir = getStepsDir(cwd);
|
|
1057
|
+
const isFirstStep = params.stepIndex === 1;
|
|
1058
|
+
const instructionPath = join5(stepsDir, params.step, "instruction.md");
|
|
1059
|
+
const instruction = existsSync5(instructionPath) ? readFileSync5(instructionPath, "utf-8") : "";
|
|
1060
|
+
const parts = [];
|
|
1061
|
+
parts.push("# Context Gathering Phase");
|
|
1062
|
+
parts.push("");
|
|
1063
|
+
parts.push("Your task is to gather context before generating a document.");
|
|
1064
|
+
parts.push("Do NOT generate the document yet - only gather information.");
|
|
1065
|
+
parts.push("");
|
|
1066
|
+
parts.push("## Task Information");
|
|
1067
|
+
parts.push("");
|
|
1068
|
+
parts.push(`- **Task ID**: ${params.taskId}`);
|
|
1069
|
+
parts.push(`- **Current Step**: ${params.step} (${params.stepIndex}/${params.totalSteps})`);
|
|
1070
|
+
parts.push("");
|
|
1071
|
+
if (isFirstStep) {
|
|
1072
|
+
parts.push("## User Request");
|
|
1073
|
+
parts.push("");
|
|
1074
|
+
parts.push(`> ${params.description}`);
|
|
1075
|
+
parts.push("");
|
|
1076
|
+
} else if (params.previousOutput) {
|
|
1077
|
+
parts.push("## Previous Step Output");
|
|
1078
|
+
parts.push("");
|
|
1079
|
+
if (existsSync5(params.previousOutput)) {
|
|
1080
|
+
parts.push(readFileSync5(params.previousOutput, "utf-8"));
|
|
1081
|
+
}
|
|
1082
|
+
parts.push("");
|
|
1083
|
+
}
|
|
1084
|
+
if (instruction) {
|
|
1085
|
+
parts.push("## Step Instruction (Preview)");
|
|
1086
|
+
parts.push("");
|
|
1087
|
+
parts.push("The next phase will use this instruction to generate a document:");
|
|
1088
|
+
parts.push("");
|
|
1089
|
+
parts.push("```");
|
|
1090
|
+
parts.push(instruction.slice(0, 500) + (instruction.length > 500 ? "..." : ""));
|
|
1091
|
+
parts.push("```");
|
|
1092
|
+
parts.push("");
|
|
1093
|
+
}
|
|
1094
|
+
parts.push("## Your Task");
|
|
1095
|
+
parts.push("");
|
|
1096
|
+
parts.push("1. **Analyze** the request/input to understand what is being asked");
|
|
1097
|
+
parts.push("2. **Search** the codebase for relevant files, patterns, conventions");
|
|
1098
|
+
parts.push("3. **Identify** key constraints, dependencies, and considerations");
|
|
1099
|
+
parts.push("");
|
|
1100
|
+
parts.push("## Output Format");
|
|
1101
|
+
parts.push("");
|
|
1102
|
+
parts.push("Output a JSON object with this structure:");
|
|
1103
|
+
parts.push("");
|
|
1104
|
+
parts.push("```json");
|
|
1105
|
+
parts.push("{");
|
|
1106
|
+
parts.push(' "summary": "Brief summary of what you understood and found",');
|
|
1107
|
+
parts.push(' "relevantFiles": ["path/to/file1.ts", "path/to/file2.ts"],');
|
|
1108
|
+
parts.push(' "keyFindings": [');
|
|
1109
|
+
parts.push(' "Finding 1: description",');
|
|
1110
|
+
parts.push(' "Finding 2: description"');
|
|
1111
|
+
parts.push(" ]");
|
|
1112
|
+
parts.push("}");
|
|
1113
|
+
parts.push("```");
|
|
1114
|
+
parts.push("");
|
|
1115
|
+
parts.push("**Important:** Output ONLY the JSON, no other text.");
|
|
1116
|
+
parts.push("");
|
|
1117
|
+
return parts.join("\n");
|
|
1118
|
+
}
|
|
1119
|
+
function buildClarifyPrompt(params) {
|
|
1120
|
+
const cwd = params.cwd || process.cwd();
|
|
1121
|
+
const stepsDir = getStepsDir(cwd);
|
|
1122
|
+
const instructionPath = join5(stepsDir, params.step, "instruction.md");
|
|
1123
|
+
const instruction = existsSync5(instructionPath) ? readFileSync5(instructionPath, "utf-8") : "";
|
|
1124
|
+
const parts = [];
|
|
1125
|
+
parts.push("# Clarify Phase");
|
|
1126
|
+
parts.push("");
|
|
1127
|
+
parts.push("Your task is to identify any questions that need user input before generating the document.");
|
|
1128
|
+
parts.push("Do NOT generate the document yet - only generate questions if needed.");
|
|
1129
|
+
parts.push("");
|
|
1130
|
+
parts.push("## Task Information");
|
|
1131
|
+
parts.push("");
|
|
1132
|
+
parts.push(`- **Task ID**: ${params.taskId}`);
|
|
1133
|
+
parts.push(`- **Current Step**: ${params.step}`);
|
|
1134
|
+
parts.push(`- **Description**: ${params.description}`);
|
|
1135
|
+
parts.push("");
|
|
1136
|
+
parts.push("## Gathered Context");
|
|
1137
|
+
parts.push("");
|
|
1138
|
+
parts.push(params.gatheredContext);
|
|
1139
|
+
parts.push("");
|
|
1140
|
+
if (instruction) {
|
|
1141
|
+
parts.push("## Step Instruction");
|
|
1142
|
+
parts.push("");
|
|
1143
|
+
parts.push(instruction);
|
|
1144
|
+
parts.push("");
|
|
1145
|
+
}
|
|
1146
|
+
parts.push("## Consider These Questions");
|
|
1147
|
+
parts.push("");
|
|
1148
|
+
parts.push("1. Is the task description clear enough to proceed?");
|
|
1149
|
+
parts.push("2. Are there multiple valid approaches that need user decision?");
|
|
1150
|
+
parts.push("3. Are there ambiguities that could lead to wrong implementation?");
|
|
1151
|
+
parts.push("4. Are there missing requirements or constraints?");
|
|
1152
|
+
parts.push("");
|
|
1153
|
+
parts.push("## Output Format");
|
|
1154
|
+
parts.push("");
|
|
1155
|
+
parts.push("Output a JSON object with questions array:");
|
|
1156
|
+
parts.push("");
|
|
1157
|
+
parts.push("```json");
|
|
1158
|
+
parts.push("{");
|
|
1159
|
+
parts.push(' "questions": [');
|
|
1160
|
+
parts.push(" {");
|
|
1161
|
+
parts.push(' "id": "q1",');
|
|
1162
|
+
parts.push(' "question": "Your specific question?",');
|
|
1163
|
+
parts.push(' "context": "Why this matters for the document",');
|
|
1164
|
+
parts.push(' "options": ["Option A", "Option B"] // optional');
|
|
1165
|
+
parts.push(" }");
|
|
1166
|
+
parts.push(" ]");
|
|
1167
|
+
parts.push("}");
|
|
1168
|
+
parts.push("```");
|
|
1169
|
+
parts.push("");
|
|
1170
|
+
parts.push('If no questions are needed, return: `{"questions": []}`');
|
|
1171
|
+
parts.push("");
|
|
1172
|
+
parts.push("**Important:** Output ONLY the JSON, no other text.");
|
|
1173
|
+
parts.push("");
|
|
1174
|
+
return parts.join("\n");
|
|
1175
|
+
}
|
|
1176
|
+
function buildGeneratePrompt(params) {
|
|
1177
|
+
const cwd = params.cwd || process.cwd();
|
|
1178
|
+
const config = loadConfig(cwd);
|
|
1179
|
+
const stepsDir = getStepsDir(cwd);
|
|
1180
|
+
const outputsDir = getOutputsDir(cwd);
|
|
1181
|
+
const isFirstStep = params.stepIndex === 1;
|
|
1182
|
+
const prevStep = params.stepIndex > 1 ? config.steps[params.stepIndex - 2] : null;
|
|
1183
|
+
const instructionPath = join5(stepsDir, params.step, "instruction.md");
|
|
1184
|
+
const templatePath = join5(stepsDir, params.step, "template.md");
|
|
1185
|
+
const outputPath = join5(outputsDir, params.taskId, `${params.step}.md`);
|
|
1186
|
+
if (!existsSync5(instructionPath)) {
|
|
1187
|
+
throw new Error(`Instruction not found: ${instructionPath}`);
|
|
1188
|
+
}
|
|
1189
|
+
const instruction = readFileSync5(instructionPath, "utf-8");
|
|
1190
|
+
const template = existsSync5(templatePath) ? readFileSync5(templatePath, "utf-8") : null;
|
|
1191
|
+
let previousSpec = null;
|
|
1192
|
+
if (prevStep) {
|
|
1193
|
+
const prevPath = join5(outputsDir, params.taskId, `${prevStep}.md`);
|
|
1194
|
+
if (existsSync5(prevPath)) {
|
|
1195
|
+
previousSpec = {
|
|
1196
|
+
step: prevStep,
|
|
1197
|
+
content: readFileSync5(prevPath, "utf-8")
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
const parts = [];
|
|
1202
|
+
parts.push("# Document Generation Phase");
|
|
1203
|
+
parts.push("");
|
|
1204
|
+
parts.push("Generate the document based on the gathered context and user answers.");
|
|
1205
|
+
parts.push("");
|
|
1206
|
+
parts.push("## Step Instruction");
|
|
1207
|
+
parts.push("");
|
|
1208
|
+
parts.push(instruction);
|
|
1209
|
+
parts.push("");
|
|
1210
|
+
if (template) {
|
|
1211
|
+
parts.push("## Document Template");
|
|
1212
|
+
parts.push("");
|
|
1213
|
+
parts.push("**CRITICAL: You MUST follow this template structure completely.**");
|
|
1214
|
+
parts.push("");
|
|
1215
|
+
parts.push(template);
|
|
1216
|
+
parts.push("");
|
|
1217
|
+
}
|
|
1218
|
+
parts.push("## Gathered Context");
|
|
1219
|
+
parts.push("");
|
|
1220
|
+
parts.push(params.gatheredContext);
|
|
1221
|
+
parts.push("");
|
|
1222
|
+
if (params.answers && params.answers.length > 0) {
|
|
1223
|
+
parts.push("## User Answers");
|
|
1224
|
+
parts.push("");
|
|
1225
|
+
for (const answer of params.answers) {
|
|
1226
|
+
parts.push(`- **${answer.questionId}**: ${answer.answer}`);
|
|
1227
|
+
}
|
|
1228
|
+
parts.push("");
|
|
1229
|
+
}
|
|
1230
|
+
if (isFirstStep) {
|
|
1231
|
+
parts.push("## User Request");
|
|
1232
|
+
parts.push("");
|
|
1233
|
+
parts.push(`> ${params.description}`);
|
|
1234
|
+
parts.push("");
|
|
1235
|
+
} else if (previousSpec) {
|
|
1236
|
+
parts.push(`## Input: ${previousSpec.step}.md`);
|
|
1237
|
+
parts.push("");
|
|
1238
|
+
parts.push(previousSpec.content);
|
|
1239
|
+
parts.push("");
|
|
1240
|
+
}
|
|
1241
|
+
parts.push("## Task Context");
|
|
1242
|
+
parts.push("");
|
|
1243
|
+
parts.push(`- **Task ID**: ${params.taskId}`);
|
|
1244
|
+
parts.push(`- **Current Step**: ${params.step} (${params.stepIndex}/${params.totalSteps})`);
|
|
1245
|
+
parts.push("");
|
|
1246
|
+
if (params.revisionFeedback) {
|
|
1247
|
+
parts.push("## Revision Feedback");
|
|
1248
|
+
parts.push("");
|
|
1249
|
+
parts.push("Previous version was not approved. Please revise based on this feedback:");
|
|
1250
|
+
parts.push("");
|
|
1251
|
+
parts.push(`> ${params.revisionFeedback}`);
|
|
1252
|
+
parts.push("");
|
|
1253
|
+
}
|
|
1254
|
+
parts.push("## Output Instructions");
|
|
1255
|
+
parts.push("");
|
|
1256
|
+
parts.push(`Generate the document and save to: \`${outputPath}\``);
|
|
1257
|
+
parts.push("");
|
|
1258
|
+
parts.push("**Required frontmatter:**");
|
|
1259
|
+
parts.push("```yaml");
|
|
1260
|
+
parts.push("---");
|
|
1261
|
+
parts.push(`id: ${params.taskId}`);
|
|
1262
|
+
parts.push(`step: ${params.step}`);
|
|
1263
|
+
parts.push("status: pending_approval");
|
|
1264
|
+
parts.push("---");
|
|
1265
|
+
parts.push("```");
|
|
1266
|
+
parts.push("");
|
|
1267
|
+
return {
|
|
1268
|
+
prompt: parts.join("\n"),
|
|
1269
|
+
outputPath
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/core/step-executor.ts
|
|
1274
|
+
import { join as join6 } from "path";
|
|
946
1275
|
var StepExecutor = class {
|
|
947
1276
|
adapter;
|
|
948
1277
|
config;
|
|
@@ -952,132 +1281,237 @@ var StepExecutor = class {
|
|
|
952
1281
|
this.config = config;
|
|
953
1282
|
this.cwd = cwd;
|
|
954
1283
|
}
|
|
1284
|
+
// ==========================================================================
|
|
1285
|
+
// Phase 1: Context Gathering
|
|
1286
|
+
// ==========================================================================
|
|
1287
|
+
/**
|
|
1288
|
+
* Execute context phase - AI gathers context
|
|
1289
|
+
* Returns gathered context as string
|
|
1290
|
+
*/
|
|
1291
|
+
async executeContextPhase(step, context) {
|
|
1292
|
+
this.adapter.io.notify(`Phase 1/4: Gathering context for ${step}`, "info");
|
|
1293
|
+
const params = {
|
|
1294
|
+
taskId: context.taskId,
|
|
1295
|
+
step,
|
|
1296
|
+
description: context.description,
|
|
1297
|
+
stepIndex: context.stepIndex,
|
|
1298
|
+
totalSteps: context.totalSteps,
|
|
1299
|
+
previousOutput: context.previousOutput,
|
|
1300
|
+
cwd: this.cwd
|
|
1301
|
+
};
|
|
1302
|
+
const prompt = buildContextPrompt(params);
|
|
1303
|
+
const response = await this.adapter.ai.execute({
|
|
1304
|
+
prompt,
|
|
1305
|
+
outputPath: ""
|
|
1306
|
+
// No file output needed
|
|
1307
|
+
});
|
|
1308
|
+
const contextOutput = this.parseContextOutput(response || "");
|
|
1309
|
+
return {
|
|
1310
|
+
phase: "context",
|
|
1311
|
+
context: JSON.stringify(contextOutput)
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
// ==========================================================================
|
|
1315
|
+
// Phase 2: Clarify (Question Generation)
|
|
1316
|
+
// ==========================================================================
|
|
1317
|
+
/**
|
|
1318
|
+
* Execute clarify phase - AI generates questions
|
|
1319
|
+
* Returns questions (may be empty)
|
|
1320
|
+
*/
|
|
1321
|
+
async executeClarifyPhase(step, context) {
|
|
1322
|
+
this.adapter.io.notify(`Phase 2/4: Generating questions for ${step}`, "info");
|
|
1323
|
+
if (!context.gatheredContext) {
|
|
1324
|
+
throw new Error("Context phase must be completed before clarify phase");
|
|
1325
|
+
}
|
|
1326
|
+
const params = {
|
|
1327
|
+
taskId: context.taskId,
|
|
1328
|
+
step,
|
|
1329
|
+
description: context.description,
|
|
1330
|
+
gatheredContext: context.gatheredContext,
|
|
1331
|
+
cwd: this.cwd
|
|
1332
|
+
};
|
|
1333
|
+
const prompt = buildClarifyPrompt(params);
|
|
1334
|
+
const response = await this.adapter.ai.execute({
|
|
1335
|
+
prompt,
|
|
1336
|
+
outputPath: ""
|
|
1337
|
+
// No file output needed
|
|
1338
|
+
});
|
|
1339
|
+
const clarifyOutput = this.parseClarifyOutput(response || "");
|
|
1340
|
+
if (clarifyOutput.questions.length > 0) {
|
|
1341
|
+
this.adapter.io.notify(`${clarifyOutput.questions.length} question(s) need answers`, "warning");
|
|
1342
|
+
} else {
|
|
1343
|
+
this.adapter.io.notify("No questions needed, proceeding to generate", "info");
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
phase: "clarify",
|
|
1347
|
+
questions: clarifyOutput.questions
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Ask questions to human
|
|
1352
|
+
* Returns answers (or pending if async mode)
|
|
1353
|
+
*/
|
|
1354
|
+
async askQuestions(questions) {
|
|
1355
|
+
const answers = await this.adapter.io.askMultiple(questions);
|
|
1356
|
+
if (answers.length === 0) {
|
|
1357
|
+
return { phase: "clarify", questions, pending: true };
|
|
1358
|
+
}
|
|
1359
|
+
return { phase: "clarify", questions: [] };
|
|
1360
|
+
}
|
|
1361
|
+
// ==========================================================================
|
|
1362
|
+
// Phase 3: Generate Document
|
|
1363
|
+
// ==========================================================================
|
|
1364
|
+
/**
|
|
1365
|
+
* Execute generate phase - AI creates document
|
|
1366
|
+
*/
|
|
1367
|
+
async executeGeneratePhase(step, context) {
|
|
1368
|
+
this.adapter.io.notify(`Phase 3/4: Generating document for ${step}`, "info");
|
|
1369
|
+
if (!context.gatheredContext) {
|
|
1370
|
+
throw new Error("Context phase must be completed before generate phase");
|
|
1371
|
+
}
|
|
1372
|
+
const params = {
|
|
1373
|
+
taskId: context.taskId,
|
|
1374
|
+
step,
|
|
1375
|
+
description: context.description,
|
|
1376
|
+
stepIndex: context.stepIndex,
|
|
1377
|
+
totalSteps: context.totalSteps,
|
|
1378
|
+
gatheredContext: context.gatheredContext,
|
|
1379
|
+
answers: context.answers,
|
|
1380
|
+
revisionFeedback: context.revisionFeedback,
|
|
1381
|
+
cwd: this.cwd
|
|
1382
|
+
};
|
|
1383
|
+
const { prompt, outputPath } = buildGeneratePrompt(params);
|
|
1384
|
+
await this.adapter.ai.execute({ prompt, outputPath });
|
|
1385
|
+
if (!existsSync6(outputPath)) {
|
|
1386
|
+
throw new Error(`AI did not create document at ${outputPath}`);
|
|
1387
|
+
}
|
|
1388
|
+
this.adapter.io.notify(`Document created: ${outputPath}`, "success");
|
|
1389
|
+
return { phase: "generate" };
|
|
1390
|
+
}
|
|
1391
|
+
// ==========================================================================
|
|
1392
|
+
// Phase 4: Review (Approval)
|
|
1393
|
+
// ==========================================================================
|
|
1394
|
+
/**
|
|
1395
|
+
* Execute review phase - human approves/revises/rejects
|
|
1396
|
+
*/
|
|
1397
|
+
async executeReviewPhase(step, context) {
|
|
1398
|
+
this.adapter.io.notify(`Phase 4/4: Review for ${step}`, "info");
|
|
1399
|
+
const outputsDir = getOutputsDir(this.cwd);
|
|
1400
|
+
const outputPath = join6(outputsDir, context.taskId, `${step}.md`);
|
|
1401
|
+
if (!existsSync6(outputPath)) {
|
|
1402
|
+
throw new Error(`Document not found: ${outputPath}`);
|
|
1403
|
+
}
|
|
1404
|
+
const approval = await this.adapter.io.approve(
|
|
1405
|
+
outputPath,
|
|
1406
|
+
step,
|
|
1407
|
+
context.stepIndex,
|
|
1408
|
+
context.totalSteps
|
|
1409
|
+
);
|
|
1410
|
+
if (approval.pending) {
|
|
1411
|
+
return { phase: "review", pending: true };
|
|
1412
|
+
}
|
|
1413
|
+
if (approval.action === "approve") {
|
|
1414
|
+
this.updateDocumentStatus(outputPath, "approved");
|
|
1415
|
+
this.adapter.io.notify(`Step ${step} approved`, "success");
|
|
1416
|
+
return { phase: "review", approved: true };
|
|
1417
|
+
}
|
|
1418
|
+
if (approval.action === "stop") {
|
|
1419
|
+
this.adapter.io.notify(`Workflow stopped at step ${step}`, "info");
|
|
1420
|
+
return { phase: "review", stopped: true };
|
|
1421
|
+
}
|
|
1422
|
+
if (approval.action === "reject") {
|
|
1423
|
+
this.updateDocumentStatus(outputPath, "rejected");
|
|
1424
|
+
this.adapter.io.notify(`Step ${step} rejected`, "error");
|
|
1425
|
+
return { phase: "review", rejected: true };
|
|
1426
|
+
}
|
|
1427
|
+
this.adapter.io.notify(`Revision requested for step ${step}`, "info");
|
|
1428
|
+
return {
|
|
1429
|
+
phase: "review",
|
|
1430
|
+
revisionFeedback: approval.feedback || "Please revise the document"
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
// ==========================================================================
|
|
1434
|
+
// Full Step Execution (convenience method)
|
|
1435
|
+
// ==========================================================================
|
|
955
1436
|
/**
|
|
956
|
-
* Execute a
|
|
1437
|
+
* Execute all phases of a step
|
|
1438
|
+
* For synchronous environments (CLI) - runs through all phases
|
|
1439
|
+
* For async environments (GitHub) - may return pending
|
|
957
1440
|
*
|
|
958
|
-
*
|
|
959
|
-
* 1. Generate document (AI)
|
|
960
|
-
* 2. Check for open_questions in frontmatter
|
|
961
|
-
* 3. If questions exist, ask user and regenerate
|
|
962
|
-
* 4. Request approval
|
|
963
|
-
* 5. Return result
|
|
1441
|
+
* @deprecated Use individual phase methods for 4-phase control
|
|
964
1442
|
*/
|
|
965
1443
|
async execute(step, context) {
|
|
966
|
-
const stepsDir = getStepsDir(this.cwd);
|
|
967
|
-
const outputsDir = getOutputsDir(this.cwd);
|
|
968
|
-
const instructionPath = join5(stepsDir, step, "instruction.md");
|
|
969
|
-
const templatePath = join5(stepsDir, step, "template.md");
|
|
970
|
-
const outputPath = join5(outputsDir, context.taskId, `${step}.md`);
|
|
971
|
-
let previousOutputPath;
|
|
972
|
-
if (context.stepIndex > 1) {
|
|
973
|
-
const steps = this.config.steps;
|
|
974
|
-
const prevStep = steps[context.stepIndex - 2];
|
|
975
|
-
const prevPath = join5(outputsDir, context.taskId, `${prevStep}.md`);
|
|
976
|
-
if (existsSync5(prevPath)) {
|
|
977
|
-
previousOutputPath = prevPath;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
this.adapter.io.notify(`Starting step: ${step}`, "info");
|
|
981
1444
|
let attempts = 0;
|
|
982
1445
|
const maxAttempts = 10;
|
|
983
|
-
let
|
|
1446
|
+
let currentContext = { ...context };
|
|
984
1447
|
while (attempts < maxAttempts) {
|
|
985
1448
|
attempts++;
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
previousOutputPath,
|
|
990
|
-
outputPath,
|
|
991
|
-
taskId: context.taskId,
|
|
992
|
-
description: context.description,
|
|
993
|
-
revisionFeedback
|
|
994
|
-
};
|
|
995
|
-
await this.adapter.ai.converse(converseParams);
|
|
996
|
-
if (!existsSync5(outputPath)) {
|
|
997
|
-
throw new Error(`AI did not create document at ${outputPath}`);
|
|
998
|
-
}
|
|
999
|
-
const questions = this.getUnresolvedQuestions(outputPath);
|
|
1000
|
-
if (questions.length > 0) {
|
|
1001
|
-
this.adapter.io.notify(`${questions.length} question(s) need answers`, "warning");
|
|
1002
|
-
const answers = await this.adapter.io.askMultiple(questions);
|
|
1003
|
-
this.resolveQuestions(outputPath, answers);
|
|
1004
|
-
revisionFeedback = `User answered questions:
|
|
1005
|
-
${answers.map((a) => `- ${a.questionId}: ${a.answer}`).join("\n")}
|
|
1006
|
-
|
|
1007
|
-
Regenerate the document incorporating these answers.`;
|
|
1008
|
-
this.adapter.io.notify("Regenerating with answers...", "info");
|
|
1009
|
-
continue;
|
|
1449
|
+
if (!currentContext.gatheredContext) {
|
|
1450
|
+
const contextResult = await this.executeContextPhase(step, currentContext);
|
|
1451
|
+
currentContext.gatheredContext = contextResult.context;
|
|
1010
1452
|
}
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
this.updateDocumentStatus(outputPath, "approved");
|
|
1019
|
-
this.adapter.io.notify(`Step ${step} approved`, "success");
|
|
1020
|
-
return { approved: true, rejected: false, stopped: false };
|
|
1453
|
+
const clarifyResult = await this.executeClarifyPhase(step, currentContext);
|
|
1454
|
+
if (clarifyResult.questions && clarifyResult.questions.length > 0) {
|
|
1455
|
+
const answers = await this.adapter.io.askMultiple(clarifyResult.questions);
|
|
1456
|
+
if (answers.length === 0) {
|
|
1457
|
+
return { phase: "clarify", questions: clarifyResult.questions, pending: true };
|
|
1458
|
+
}
|
|
1459
|
+
currentContext.answers = answers;
|
|
1021
1460
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1461
|
+
await this.executeGeneratePhase(step, currentContext);
|
|
1462
|
+
const reviewResult = await this.executeReviewPhase(step, currentContext);
|
|
1463
|
+
if (reviewResult.pending) {
|
|
1464
|
+
return reviewResult;
|
|
1025
1465
|
}
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1028
|
-
this.adapter.io.notify(`Step ${step} rejected`, "error");
|
|
1029
|
-
return { approved: false, rejected: true, stopped: false };
|
|
1466
|
+
if (reviewResult.approved || reviewResult.rejected || reviewResult.stopped) {
|
|
1467
|
+
return reviewResult;
|
|
1030
1468
|
}
|
|
1031
|
-
revisionFeedback =
|
|
1469
|
+
currentContext.revisionFeedback = reviewResult.revisionFeedback;
|
|
1032
1470
|
this.adapter.io.notify(`Revising step ${step}...`, "info");
|
|
1033
1471
|
}
|
|
1034
1472
|
throw new Error(`Maximum revision attempts (${maxAttempts}) exceeded for step ${step}`);
|
|
1035
1473
|
}
|
|
1474
|
+
// ==========================================================================
|
|
1475
|
+
// Helpers
|
|
1476
|
+
// ==========================================================================
|
|
1036
1477
|
/**
|
|
1037
|
-
*
|
|
1478
|
+
* Parse context phase output (JSON)
|
|
1038
1479
|
*/
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
for (let i = 0; i < data.open_questions.length; i++) {
|
|
1045
|
-
const q = data.open_questions[i];
|
|
1046
|
-
if (!q.resolved) {
|
|
1047
|
-
questions.push({
|
|
1048
|
-
id: `q${i + 1}`,
|
|
1049
|
-
question: q.question,
|
|
1050
|
-
context: q.context,
|
|
1051
|
-
options: q.options
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1480
|
+
parseContextOutput(response) {
|
|
1481
|
+
try {
|
|
1482
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
1483
|
+
if (jsonMatch) {
|
|
1484
|
+
return JSON.parse(jsonMatch[0]);
|
|
1054
1485
|
}
|
|
1486
|
+
} catch {
|
|
1055
1487
|
}
|
|
1056
|
-
return
|
|
1488
|
+
return {
|
|
1489
|
+
summary: response,
|
|
1490
|
+
relevantFiles: [],
|
|
1491
|
+
keyFindings: []
|
|
1492
|
+
};
|
|
1057
1493
|
}
|
|
1058
1494
|
/**
|
|
1059
|
-
*
|
|
1495
|
+
* Parse clarify phase output (JSON)
|
|
1060
1496
|
*/
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
};
|
|
1072
|
-
});
|
|
1497
|
+
parseClarifyOutput(response) {
|
|
1498
|
+
try {
|
|
1499
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
1500
|
+
if (jsonMatch) {
|
|
1501
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
1502
|
+
if (Array.isArray(parsed.questions)) {
|
|
1503
|
+
return parsed;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
} catch {
|
|
1073
1507
|
}
|
|
1074
|
-
|
|
1508
|
+
return { questions: [] };
|
|
1075
1509
|
}
|
|
1076
1510
|
/**
|
|
1077
1511
|
* Update document status in frontmatter
|
|
1078
1512
|
*/
|
|
1079
1513
|
updateDocumentStatus(docPath, status) {
|
|
1080
|
-
const content =
|
|
1514
|
+
const content = readFileSync6(docPath, "utf-8");
|
|
1081
1515
|
const { content: body, data } = matter3(content);
|
|
1082
1516
|
data.status = status;
|
|
1083
1517
|
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1087,69 +1521,26 @@ Regenerate the document incorporating these answers.`;
|
|
|
1087
1521
|
|
|
1088
1522
|
// src/adapters/cli.ts
|
|
1089
1523
|
import { spawn, spawnSync } from "child_process";
|
|
1090
|
-
import { readFileSync as
|
|
1091
|
-
import { dirname as
|
|
1524
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
1525
|
+
import { dirname as dirname3 } from "path";
|
|
1092
1526
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
1093
1527
|
var CLIAIAdapter = class {
|
|
1094
1528
|
claudeCommand;
|
|
1095
1529
|
constructor(claudeCommand = "claude") {
|
|
1096
1530
|
this.claudeCommand = claudeCommand;
|
|
1097
1531
|
}
|
|
1098
|
-
async
|
|
1099
|
-
const prompt = this.buildPrompt(params);
|
|
1532
|
+
async execute(params) {
|
|
1100
1533
|
console.log(`
|
|
1101
|
-
\u{1F4DD} Generating
|
|
1102
|
-
const response = await this.callClaude(prompt);
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const instruction = readFileSync6(params.instructionPath, "utf-8");
|
|
1112
|
-
parts.push("# Task");
|
|
1113
|
-
parts.push(`You are generating a document as part of a spec-driven development workflow.`);
|
|
1114
|
-
parts.push("");
|
|
1115
|
-
parts.push("## Task Description");
|
|
1116
|
-
parts.push(params.description);
|
|
1117
|
-
parts.push("");
|
|
1118
|
-
parts.push("## Instructions");
|
|
1119
|
-
parts.push(instruction);
|
|
1120
|
-
parts.push("");
|
|
1121
|
-
if (params.templatePath && existsSync6(params.templatePath)) {
|
|
1122
|
-
const template = readFileSync6(params.templatePath, "utf-8");
|
|
1123
|
-
parts.push("## Output Template");
|
|
1124
|
-
parts.push("Follow this template structure:");
|
|
1125
|
-
parts.push(template);
|
|
1126
|
-
parts.push("");
|
|
1127
|
-
}
|
|
1128
|
-
if (params.previousOutputPath && existsSync6(params.previousOutputPath)) {
|
|
1129
|
-
const previousOutput = readFileSync6(params.previousOutputPath, "utf-8");
|
|
1130
|
-
parts.push("## Previous Step Output");
|
|
1131
|
-
parts.push(previousOutput);
|
|
1132
|
-
parts.push("");
|
|
1133
|
-
}
|
|
1134
|
-
if (params.revisionFeedback) {
|
|
1135
|
-
parts.push("## Revision Feedback");
|
|
1136
|
-
parts.push(params.revisionFeedback);
|
|
1137
|
-
parts.push("");
|
|
1138
|
-
}
|
|
1139
|
-
parts.push("## Output Format");
|
|
1140
|
-
parts.push("Generate the document content directly.");
|
|
1141
|
-
parts.push("If you have clarifying questions that MUST be answered, include them in the frontmatter as:");
|
|
1142
|
-
parts.push("```yaml");
|
|
1143
|
-
parts.push("---");
|
|
1144
|
-
parts.push("status: draft");
|
|
1145
|
-
parts.push("open_questions:");
|
|
1146
|
-
parts.push(' - question: "Your question here"');
|
|
1147
|
-
parts.push(" resolved: false");
|
|
1148
|
-
parts.push("---");
|
|
1149
|
-
parts.push("```");
|
|
1150
|
-
parts.push("");
|
|
1151
|
-
parts.push("Only ask questions if absolutely necessary.");
|
|
1152
|
-
return parts.join("\n");
|
|
1534
|
+
\u{1F4DD} Generating...`);
|
|
1535
|
+
const response = await this.callClaude(params.prompt);
|
|
1536
|
+
if (params.outputPath) {
|
|
1537
|
+
const outputDir = dirname3(params.outputPath);
|
|
1538
|
+
if (!existsSync7(outputDir)) {
|
|
1539
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
1540
|
+
}
|
|
1541
|
+
writeFileSync5(params.outputPath, response);
|
|
1542
|
+
}
|
|
1543
|
+
return response;
|
|
1153
1544
|
}
|
|
1154
1545
|
async callClaude(prompt) {
|
|
1155
1546
|
return new Promise((resolve, reject) => {
|
|
@@ -1216,8 +1607,8 @@ Context: ${question.context}`);
|
|
|
1216
1607
|
return answers;
|
|
1217
1608
|
}
|
|
1218
1609
|
async approve(specPath, stepName, stepIndex, totalSteps) {
|
|
1219
|
-
if (
|
|
1220
|
-
const doc =
|
|
1610
|
+
if (existsSync7(specPath)) {
|
|
1611
|
+
const doc = readFileSync7(specPath, "utf-8");
|
|
1221
1612
|
console.log("\n" + "=".repeat(60));
|
|
1222
1613
|
console.log(`\u{1F4C4} ${stepName} Document (Step ${stepIndex}/${totalSteps})`);
|
|
1223
1614
|
console.log("=".repeat(60));
|
|
@@ -1262,17 +1653,17 @@ Context: ${question.context}`);
|
|
|
1262
1653
|
};
|
|
1263
1654
|
var CLISystemAdapter = class {
|
|
1264
1655
|
readFile(path) {
|
|
1265
|
-
return
|
|
1656
|
+
return readFileSync7(path, "utf-8");
|
|
1266
1657
|
}
|
|
1267
1658
|
writeFile(path, content) {
|
|
1268
|
-
const dir =
|
|
1269
|
-
if (!
|
|
1659
|
+
const dir = dirname3(path);
|
|
1660
|
+
if (!existsSync7(dir)) {
|
|
1270
1661
|
mkdirSync4(dir, { recursive: true });
|
|
1271
1662
|
}
|
|
1272
1663
|
writeFileSync5(path, content);
|
|
1273
1664
|
}
|
|
1274
1665
|
fileExists(path) {
|
|
1275
|
-
return
|
|
1666
|
+
return existsSync7(path);
|
|
1276
1667
|
}
|
|
1277
1668
|
exec(command) {
|
|
1278
1669
|
try {
|
|
@@ -1303,76 +1694,25 @@ function createCLIAdapter(claudeCommand = "claude") {
|
|
|
1303
1694
|
|
|
1304
1695
|
// src/adapters/github.ts
|
|
1305
1696
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
1306
|
-
import { readFileSync as
|
|
1307
|
-
import { dirname as
|
|
1308
|
-
var PauseForInputError = class extends Error {
|
|
1309
|
-
constructor(inputType, data) {
|
|
1310
|
-
super(`Paused waiting for ${inputType}`);
|
|
1311
|
-
this.inputType = inputType;
|
|
1312
|
-
this.data = data;
|
|
1313
|
-
this.name = "PauseForInputError";
|
|
1314
|
-
}
|
|
1315
|
-
};
|
|
1697
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
1698
|
+
import { dirname as dirname4 } from "path";
|
|
1316
1699
|
var GitHubAIAdapter = class {
|
|
1317
1700
|
claudeCommand;
|
|
1318
1701
|
constructor(claudeCommand = "claude") {
|
|
1319
1702
|
this.claudeCommand = claudeCommand;
|
|
1320
1703
|
}
|
|
1321
|
-
async
|
|
1322
|
-
const prompt = this.buildPrompt(params);
|
|
1704
|
+
async execute(params) {
|
|
1323
1705
|
console.log(`
|
|
1324
|
-
\u{1F4DD} Generating
|
|
1325
|
-
const response = await this.callClaude(prompt);
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
const instruction = readFileSync7(params.instructionPath, "utf-8");
|
|
1335
|
-
parts.push("# Task");
|
|
1336
|
-
parts.push(`You are generating a document as part of a spec-driven development workflow.`);
|
|
1337
|
-
parts.push("");
|
|
1338
|
-
parts.push("## Task Description");
|
|
1339
|
-
parts.push(params.description);
|
|
1340
|
-
parts.push("");
|
|
1341
|
-
parts.push("## Instructions");
|
|
1342
|
-
parts.push(instruction);
|
|
1343
|
-
parts.push("");
|
|
1344
|
-
if (params.templatePath && existsSync7(params.templatePath)) {
|
|
1345
|
-
const template = readFileSync7(params.templatePath, "utf-8");
|
|
1346
|
-
parts.push("## Output Template");
|
|
1347
|
-
parts.push("Follow this template structure:");
|
|
1348
|
-
parts.push(template);
|
|
1349
|
-
parts.push("");
|
|
1350
|
-
}
|
|
1351
|
-
if (params.previousOutputPath && existsSync7(params.previousOutputPath)) {
|
|
1352
|
-
const previousOutput = readFileSync7(params.previousOutputPath, "utf-8");
|
|
1353
|
-
parts.push("## Previous Step Output");
|
|
1354
|
-
parts.push(previousOutput);
|
|
1355
|
-
parts.push("");
|
|
1356
|
-
}
|
|
1357
|
-
if (params.revisionFeedback) {
|
|
1358
|
-
parts.push("## Revision Feedback");
|
|
1359
|
-
parts.push(params.revisionFeedback);
|
|
1360
|
-
parts.push("");
|
|
1361
|
-
}
|
|
1362
|
-
parts.push("## Output Format");
|
|
1363
|
-
parts.push("Generate the document content directly.");
|
|
1364
|
-
parts.push("If you have clarifying questions that MUST be answered, include them in the frontmatter as:");
|
|
1365
|
-
parts.push("```yaml");
|
|
1366
|
-
parts.push("---");
|
|
1367
|
-
parts.push("status: draft");
|
|
1368
|
-
parts.push("open_questions:");
|
|
1369
|
-
parts.push(' - question: "Your question here"');
|
|
1370
|
-
parts.push(" resolved: false");
|
|
1371
|
-
parts.push("---");
|
|
1372
|
-
parts.push("```");
|
|
1373
|
-
parts.push("");
|
|
1374
|
-
parts.push("Only ask questions if absolutely necessary.");
|
|
1375
|
-
return parts.join("\n");
|
|
1706
|
+
\u{1F4DD} Generating...`);
|
|
1707
|
+
const response = await this.callClaude(params.prompt);
|
|
1708
|
+
if (params.outputPath) {
|
|
1709
|
+
const outputDir = dirname4(params.outputPath);
|
|
1710
|
+
if (!existsSync8(outputDir)) {
|
|
1711
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
1712
|
+
}
|
|
1713
|
+
writeFileSync6(params.outputPath, response);
|
|
1714
|
+
}
|
|
1715
|
+
return response;
|
|
1376
1716
|
}
|
|
1377
1717
|
async callClaude(prompt) {
|
|
1378
1718
|
return new Promise((resolve, reject) => {
|
|
@@ -1417,22 +1757,38 @@ var GitHubIOAdapter = class {
|
|
|
1417
1757
|
this.taskId = taskId;
|
|
1418
1758
|
}
|
|
1419
1759
|
async ask(question) {
|
|
1420
|
-
|
|
1760
|
+
console.log(`\u{1F4DD} Question queued: ${question.question}`);
|
|
1761
|
+
return "";
|
|
1421
1762
|
}
|
|
1422
1763
|
async askMultiple(questions) {
|
|
1423
1764
|
const comment = this.formatQuestionsComment(questions);
|
|
1424
1765
|
await this.postComment(comment);
|
|
1425
|
-
console.log("\n\u23F8\uFE0F Waiting for answers
|
|
1426
|
-
|
|
1766
|
+
console.log("\n\u23F8\uFE0F Questions posted to GitHub. Waiting for answers...");
|
|
1767
|
+
console.log(" Answer with /answer command on the Issue/PR.");
|
|
1768
|
+
this.pendingQuestions = questions;
|
|
1769
|
+
return [];
|
|
1427
1770
|
}
|
|
1428
1771
|
async approve(specPath, stepName, stepIndex, totalSteps) {
|
|
1429
|
-
const doc =
|
|
1772
|
+
const doc = existsSync8(specPath) ? readFileSync8(specPath, "utf-8") : "";
|
|
1430
1773
|
const comment = this.formatApprovalComment(doc, stepName, stepIndex, totalSteps, specPath);
|
|
1431
1774
|
await this.postComment(comment);
|
|
1432
|
-
console.log("\n\u23F8\uFE0F
|
|
1775
|
+
console.log("\n\u23F8\uFE0F Approval request posted to GitHub.");
|
|
1433
1776
|
console.log(" Comment /approve, /revise <feedback>, or /reject on the Issue/PR.");
|
|
1434
|
-
return { action: "stop" };
|
|
1777
|
+
return { action: "stop", pending: true };
|
|
1435
1778
|
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Check if there are pending questions awaiting external input
|
|
1781
|
+
*/
|
|
1782
|
+
hasPendingQuestions() {
|
|
1783
|
+
return this.pendingQuestions.length > 0;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Get pending questions for serialization
|
|
1787
|
+
*/
|
|
1788
|
+
getPendingQuestions() {
|
|
1789
|
+
return this.pendingQuestions;
|
|
1790
|
+
}
|
|
1791
|
+
pendingQuestions = [];
|
|
1436
1792
|
notify(message, type) {
|
|
1437
1793
|
const icons = {
|
|
1438
1794
|
info: "\u2139\uFE0F",
|
|
@@ -1527,17 +1883,17 @@ var GitHubIOAdapter = class {
|
|
|
1527
1883
|
};
|
|
1528
1884
|
var GitHubSystemAdapter = class {
|
|
1529
1885
|
readFile(path) {
|
|
1530
|
-
return
|
|
1886
|
+
return readFileSync8(path, "utf-8");
|
|
1531
1887
|
}
|
|
1532
1888
|
writeFile(path, content) {
|
|
1533
|
-
const dir =
|
|
1534
|
-
if (!
|
|
1889
|
+
const dir = dirname4(path);
|
|
1890
|
+
if (!existsSync8(dir)) {
|
|
1535
1891
|
mkdirSync5(dir, { recursive: true });
|
|
1536
1892
|
}
|
|
1537
1893
|
writeFileSync6(path, content);
|
|
1538
1894
|
}
|
|
1539
1895
|
fileExists(path) {
|
|
1540
|
-
return
|
|
1896
|
+
return existsSync8(path);
|
|
1541
1897
|
}
|
|
1542
1898
|
exec(command) {
|
|
1543
1899
|
try {
|
|
@@ -1573,7 +1929,7 @@ function getGitHubInfo(cwd) {
|
|
|
1573
1929
|
return { owner: config.owner, repo: config.repo };
|
|
1574
1930
|
}
|
|
1575
1931
|
try {
|
|
1576
|
-
const remoteUrl =
|
|
1932
|
+
const remoteUrl = execSync2("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
|
|
1577
1933
|
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
1578
1934
|
if (sshMatch) {
|
|
1579
1935
|
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
@@ -1588,7 +1944,7 @@ function getGitHubInfo(cwd) {
|
|
|
1588
1944
|
}
|
|
1589
1945
|
function getCurrentBranch() {
|
|
1590
1946
|
try {
|
|
1591
|
-
return
|
|
1947
|
+
return execSync2("git branch --show-current", { encoding: "utf-8" }).trim() || void 0;
|
|
1592
1948
|
} catch {
|
|
1593
1949
|
return void 0;
|
|
1594
1950
|
}
|
|
@@ -1643,7 +1999,7 @@ async function startCommand(query, options) {
|
|
|
1643
1999
|
console.log(`
|
|
1644
2000
|
\u{1F680} Starting workflow: ${query}
|
|
1645
2001
|
`);
|
|
1646
|
-
if (response.type === "
|
|
2002
|
+
if (response.type === "phase" && response.phase === "context") {
|
|
1647
2003
|
taskId = response.taskId;
|
|
1648
2004
|
if (isGitHubMode && "io" in adapter && "setTaskId" in adapter.io) {
|
|
1649
2005
|
adapter.io.setTaskId(taskId);
|
|
@@ -1651,69 +2007,87 @@ async function startCommand(query, options) {
|
|
|
1651
2007
|
}
|
|
1652
2008
|
try {
|
|
1653
2009
|
while (response.type !== "complete" && response.type !== "error") {
|
|
1654
|
-
if (response.type === "
|
|
1655
|
-
const
|
|
1656
|
-
taskId =
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
`
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2010
|
+
if (response.type === "phase") {
|
|
2011
|
+
const phaseResponse = response;
|
|
2012
|
+
taskId = phaseResponse.taskId;
|
|
2013
|
+
if (phaseResponse.phase === "context") {
|
|
2014
|
+
const contextResp = phaseResponse;
|
|
2015
|
+
console.log(`
|
|
2016
|
+
--- Step ${contextResp.stepIndex}/${contextResp.totalSteps}: ${contextResp.step} ---`);
|
|
2017
|
+
console.log("Phase 1/4: Gathering context...\n");
|
|
2018
|
+
const stepContext = {
|
|
2019
|
+
taskId: contextResp.taskId,
|
|
2020
|
+
description: contextResp.description,
|
|
2021
|
+
stepIndex: contextResp.stepIndex,
|
|
2022
|
+
totalSteps: contextResp.totalSteps,
|
|
2023
|
+
previousOutput: contextResp.context.previousOutput
|
|
2024
|
+
};
|
|
2025
|
+
const result = await stepExecutor.executeContextPhase(contextResp.step, stepContext);
|
|
2026
|
+
response = orchestrator.cmdContextDone(taskId, result.context || "");
|
|
2027
|
+
} else if (phaseResponse.phase === "clarify") {
|
|
2028
|
+
const clarifyResp = phaseResponse;
|
|
2029
|
+
console.log("Phase 2/4: Generating questions...\n");
|
|
2030
|
+
const stepContext = {
|
|
2031
|
+
taskId: clarifyResp.taskId,
|
|
2032
|
+
description: clarifyResp.description,
|
|
2033
|
+
stepIndex: 0,
|
|
2034
|
+
// Not needed for clarify
|
|
2035
|
+
totalSteps: 0,
|
|
2036
|
+
gatheredContext: clarifyResp.gatheredContext
|
|
2037
|
+
};
|
|
2038
|
+
const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
|
|
2039
|
+
response = orchestrator.cmdClarifyDone(taskId, result.questions || []);
|
|
2040
|
+
} else if (phaseResponse.phase === "generate") {
|
|
2041
|
+
const generateResp = phaseResponse;
|
|
2042
|
+
console.log("Phase 3/4: Generating document...\n");
|
|
2043
|
+
const stepContext = {
|
|
2044
|
+
taskId: generateResp.taskId,
|
|
2045
|
+
description: generateResp.description,
|
|
2046
|
+
stepIndex: generateResp.stepIndex,
|
|
2047
|
+
totalSteps: generateResp.totalSteps,
|
|
2048
|
+
gatheredContext: generateResp.gatheredContext,
|
|
2049
|
+
answers: generateResp.answers,
|
|
2050
|
+
revisionFeedback: generateResp.context.revisionFeedback
|
|
2051
|
+
};
|
|
2052
|
+
await stepExecutor.executeGeneratePhase(generateResp.step, stepContext);
|
|
2053
|
+
response = orchestrator.cmdGenerateDone(taskId);
|
|
2054
|
+
}
|
|
2055
|
+
} else if (response.type === "checkpoint") {
|
|
2056
|
+
taskId = response.taskId;
|
|
2057
|
+
if (response.checkpoint === "clarify") {
|
|
2058
|
+
const clarifyCheckpoint = response;
|
|
2059
|
+
console.log("Phase 2/4: Questions need answers\n");
|
|
2060
|
+
const answers = await adapter.io.askMultiple(clarifyCheckpoint.questions);
|
|
2061
|
+
if (answers.length === 0) {
|
|
1680
2062
|
console.log(`
|
|
1681
2063
|
\u23F8\uFE0F Workflow paused. Task ID: ${taskId}`);
|
|
1682
2064
|
console.log(" Resume with: spets resume --task", taskId);
|
|
1683
2065
|
return;
|
|
1684
2066
|
}
|
|
1685
|
-
|
|
1686
|
-
}
|
|
1687
|
-
} else if (response.type === "checkpoint") {
|
|
1688
|
-
taskId = response.taskId;
|
|
1689
|
-
if (response.checkpoint === "clarify") {
|
|
1690
|
-
adapter.io.notify("Resuming from clarify checkpoint - regenerating step", "warning");
|
|
1691
|
-
response = orchestrator.cmdStatus(taskId);
|
|
2067
|
+
response = orchestrator.cmdClarified(taskId, answers);
|
|
1692
2068
|
} else if (response.checkpoint === "approve") {
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
} else if (approval.action === "revise") {
|
|
1704
|
-
response = orchestrator.cmdRevise(taskId, approval.feedback || "");
|
|
1705
|
-
} else if (approval.action === "reject") {
|
|
1706
|
-
response = orchestrator.cmdReject(taskId);
|
|
1707
|
-
} else {
|
|
1708
|
-
response = orchestrator.cmdStop(taskId);
|
|
1709
|
-
}
|
|
1710
|
-
} catch (error) {
|
|
1711
|
-
if (error instanceof PauseForInputError) {
|
|
1712
|
-
console.log(`
|
|
2069
|
+
const approveCheckpoint = response;
|
|
2070
|
+
console.log("Phase 4/4: Review document\n");
|
|
2071
|
+
const approval = await adapter.io.approve(
|
|
2072
|
+
approveCheckpoint.specPath,
|
|
2073
|
+
approveCheckpoint.step,
|
|
2074
|
+
approveCheckpoint.stepIndex,
|
|
2075
|
+
approveCheckpoint.totalSteps
|
|
2076
|
+
);
|
|
2077
|
+
if (approval.pending) {
|
|
2078
|
+
console.log(`
|
|
1713
2079
|
\u23F8\uFE0F Workflow paused. Task ID: ${taskId}`);
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2080
|
+
console.log(" Resume with: spets resume --task", taskId);
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
if (approval.action === "approve") {
|
|
2084
|
+
response = orchestrator.cmdApprove(taskId);
|
|
2085
|
+
} else if (approval.action === "revise") {
|
|
2086
|
+
response = orchestrator.cmdRevise(taskId, approval.feedback || "");
|
|
2087
|
+
} else if (approval.action === "reject") {
|
|
2088
|
+
response = orchestrator.cmdReject(taskId);
|
|
2089
|
+
} else {
|
|
2090
|
+
response = orchestrator.cmdStop(taskId);
|
|
1717
2091
|
}
|
|
1718
2092
|
}
|
|
1719
2093
|
} else {
|
|
@@ -1752,19 +2126,19 @@ Resume with: spets resume --task ${taskId}`);
|
|
|
1752
2126
|
|
|
1753
2127
|
// src/commands/resume.ts
|
|
1754
2128
|
import { select as select2 } from "@inquirer/prompts";
|
|
1755
|
-
import { existsSync as
|
|
1756
|
-
import { join as
|
|
2129
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2 } from "fs";
|
|
2130
|
+
import { join as join7 } from "path";
|
|
1757
2131
|
function listResumableTasks(cwd) {
|
|
1758
2132
|
const outputsDir = getOutputsDir(cwd);
|
|
1759
|
-
if (!
|
|
2133
|
+
if (!existsSync9(outputsDir)) {
|
|
1760
2134
|
return [];
|
|
1761
2135
|
}
|
|
1762
2136
|
const tasks = [];
|
|
1763
2137
|
const taskDirs = readdirSync2(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1764
2138
|
const orchestrator = new Orchestrator(cwd);
|
|
1765
2139
|
for (const taskId of taskDirs) {
|
|
1766
|
-
const stateFile =
|
|
1767
|
-
if (!
|
|
2140
|
+
const stateFile = join7(outputsDir, taskId, ".state.json");
|
|
2141
|
+
if (!existsSync9(stateFile)) continue;
|
|
1768
2142
|
try {
|
|
1769
2143
|
const response = orchestrator.cmdStatus(taskId);
|
|
1770
2144
|
if (response.type === "error") continue;
|
|
@@ -1932,8 +2306,8 @@ Resume with: spets resume --task ${taskId}`);
|
|
|
1932
2306
|
}
|
|
1933
2307
|
|
|
1934
2308
|
// src/commands/plugin.ts
|
|
1935
|
-
import { existsSync as
|
|
1936
|
-
import { join as
|
|
2309
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, rmSync } from "fs";
|
|
2310
|
+
import { join as join8 } from "path";
|
|
1937
2311
|
import { homedir } from "os";
|
|
1938
2312
|
async function pluginCommand(action, name) {
|
|
1939
2313
|
switch (action) {
|
|
@@ -1975,10 +2349,10 @@ async function installPlugin(name) {
|
|
|
1975
2349
|
installer();
|
|
1976
2350
|
}
|
|
1977
2351
|
function installClaudePlugin() {
|
|
1978
|
-
const claudeDir =
|
|
1979
|
-
const commandsDir =
|
|
2352
|
+
const claudeDir = join8(homedir(), ".claude");
|
|
2353
|
+
const commandsDir = join8(claudeDir, "commands");
|
|
1980
2354
|
mkdirSync6(commandsDir, { recursive: true });
|
|
1981
|
-
const skillPath =
|
|
2355
|
+
const skillPath = join8(commandsDir, "spets.md");
|
|
1982
2356
|
writeFileSync7(skillPath, getClaudeSkillContent());
|
|
1983
2357
|
console.log("Installed Claude Code plugin.");
|
|
1984
2358
|
console.log(`Location: ${skillPath}`);
|
|
@@ -1991,14 +2365,14 @@ function installClaudePlugin() {
|
|
|
1991
2365
|
}
|
|
1992
2366
|
async function uninstallPlugin(name) {
|
|
1993
2367
|
if (name === "claude") {
|
|
1994
|
-
const skillPath =
|
|
1995
|
-
const legacySkillPath =
|
|
2368
|
+
const skillPath = join8(homedir(), ".claude", "commands", "spets.md");
|
|
2369
|
+
const legacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
|
|
1996
2370
|
let uninstalled = false;
|
|
1997
|
-
if (
|
|
2371
|
+
if (existsSync10(skillPath)) {
|
|
1998
2372
|
rmSync(skillPath);
|
|
1999
2373
|
uninstalled = true;
|
|
2000
2374
|
}
|
|
2001
|
-
if (
|
|
2375
|
+
if (existsSync10(legacySkillPath)) {
|
|
2002
2376
|
rmSync(legacySkillPath);
|
|
2003
2377
|
uninstalled = true;
|
|
2004
2378
|
}
|
|
@@ -2017,9 +2391,9 @@ async function listPlugins() {
|
|
|
2017
2391
|
console.log("");
|
|
2018
2392
|
console.log(" claude - Claude Code /spets skill");
|
|
2019
2393
|
console.log("");
|
|
2020
|
-
const skillPath =
|
|
2021
|
-
const legacySkillPath =
|
|
2022
|
-
const claudeInstalled =
|
|
2394
|
+
const skillPath = join8(homedir(), ".claude", "commands", "spets.md");
|
|
2395
|
+
const legacySkillPath = join8(homedir(), ".claude", "commands", "sdd-do.md");
|
|
2396
|
+
const claudeInstalled = existsSync10(skillPath) || existsSync10(legacySkillPath);
|
|
2023
2397
|
console.log("Installed:");
|
|
2024
2398
|
if (claudeInstalled) {
|
|
2025
2399
|
console.log(" - claude");
|
|
@@ -2189,9 +2563,9 @@ request: The user's task description for the workflow
|
|
|
2189
2563
|
}
|
|
2190
2564
|
|
|
2191
2565
|
// src/commands/github.ts
|
|
2192
|
-
import { execSync as
|
|
2193
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
2194
|
-
import { join as
|
|
2566
|
+
import { execSync as execSync3, spawn as spawn3, spawnSync as spawnSync3 } from "child_process";
|
|
2567
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
|
|
2568
|
+
import { join as join9 } from "path";
|
|
2195
2569
|
function parseGitHubCommand(comment) {
|
|
2196
2570
|
const trimmed = comment.trim();
|
|
2197
2571
|
if (trimmed === "/approve") {
|
|
@@ -2231,7 +2605,7 @@ function getGitHubInfo2(cwd) {
|
|
|
2231
2605
|
return { owner: config.owner, repo: config.repo };
|
|
2232
2606
|
}
|
|
2233
2607
|
try {
|
|
2234
|
-
const remoteUrl =
|
|
2608
|
+
const remoteUrl = execSync3("git remote get-url origin", { encoding: "utf-8", cwd }).trim();
|
|
2235
2609
|
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
2236
2610
|
if (sshMatch) {
|
|
2237
2611
|
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
@@ -2246,7 +2620,7 @@ function getGitHubInfo2(cwd) {
|
|
|
2246
2620
|
}
|
|
2247
2621
|
function getCurrentBranch2(cwd) {
|
|
2248
2622
|
try {
|
|
2249
|
-
return
|
|
2623
|
+
return execSync3("git branch --show-current", { encoding: "utf-8", cwd }).trim();
|
|
2250
2624
|
} catch {
|
|
2251
2625
|
return void 0;
|
|
2252
2626
|
}
|
|
@@ -2254,7 +2628,7 @@ function getCurrentBranch2(cwd) {
|
|
|
2254
2628
|
function findTaskId(cwd, owner, repo, issueOrPr) {
|
|
2255
2629
|
if (issueOrPr) {
|
|
2256
2630
|
try {
|
|
2257
|
-
const commentsJson =
|
|
2631
|
+
const commentsJson = execSync3(
|
|
2258
2632
|
`gh api repos/${owner}/${repo}/issues/${issueOrPr}/comments --jq '.[].body'`,
|
|
2259
2633
|
{ encoding: "utf-8" }
|
|
2260
2634
|
);
|
|
@@ -2266,7 +2640,7 @@ function findTaskId(cwd, owner, repo, issueOrPr) {
|
|
|
2266
2640
|
}
|
|
2267
2641
|
}
|
|
2268
2642
|
const outputsDir = getOutputsDir(cwd);
|
|
2269
|
-
if (
|
|
2643
|
+
if (existsSync11(outputsDir)) {
|
|
2270
2644
|
const taskDirs = readdirSync4(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
2271
2645
|
if (taskDirs.length > 0) {
|
|
2272
2646
|
return taskDirs[0];
|
|
@@ -2323,15 +2697,15 @@ async function githubCommand(options) {
|
|
|
2323
2697
|
case "approve": {
|
|
2324
2698
|
console.log("Approving current step...");
|
|
2325
2699
|
response = orchestrator.cmdApprove(taskId);
|
|
2326
|
-
if (parsed.createPR && response.type
|
|
2700
|
+
if (parsed.createPR && response.type === "complete") {
|
|
2327
2701
|
console.log("Creating PR with AI-generated content...");
|
|
2328
|
-
const
|
|
2329
|
-
await createPullRequestWithAI(githubConfig, taskId,
|
|
2702
|
+
const completeResp = response;
|
|
2703
|
+
await createPullRequestWithAI(githubConfig, taskId, completeResp.message || "", cwd);
|
|
2330
2704
|
}
|
|
2331
|
-
if (parsed.createIssue && response.type
|
|
2705
|
+
if (parsed.createIssue && response.type === "phase") {
|
|
2332
2706
|
console.log("Creating/Updating Issue...");
|
|
2333
|
-
const
|
|
2334
|
-
await createOrUpdateIssue(githubConfig, taskId,
|
|
2707
|
+
const phaseResp = response;
|
|
2708
|
+
await createOrUpdateIssue(githubConfig, taskId, phaseResp.description || "", phaseResp.step);
|
|
2335
2709
|
}
|
|
2336
2710
|
break;
|
|
2337
2711
|
}
|
|
@@ -2356,37 +2730,78 @@ async function githubCommand(options) {
|
|
|
2356
2730
|
break;
|
|
2357
2731
|
}
|
|
2358
2732
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2733
|
+
const stepExecutor = new StepExecutor(adapter, config, cwd);
|
|
2734
|
+
while (response.type !== "complete" && response.type !== "error") {
|
|
2735
|
+
if (response.type === "phase") {
|
|
2736
|
+
const phaseResponse = response;
|
|
2737
|
+
if (phaseResponse.phase === "context") {
|
|
2738
|
+
const contextResp = phaseResponse;
|
|
2739
|
+
console.log(`
|
|
2740
|
+
--- Step ${contextResp.stepIndex}/${contextResp.totalSteps}: ${contextResp.step} ---`);
|
|
2741
|
+
console.log("Phase 1/4: Gathering context...\n");
|
|
2742
|
+
const stepContext = {
|
|
2743
|
+
taskId: contextResp.taskId,
|
|
2744
|
+
description: contextResp.description,
|
|
2745
|
+
stepIndex: contextResp.stepIndex,
|
|
2746
|
+
totalSteps: contextResp.totalSteps,
|
|
2747
|
+
previousOutput: contextResp.context.previousOutput
|
|
2748
|
+
};
|
|
2749
|
+
const result = await stepExecutor.executeContextPhase(contextResp.step, stepContext);
|
|
2750
|
+
response = orchestrator.cmdContextDone(taskId, result.context || "");
|
|
2751
|
+
} else if (phaseResponse.phase === "clarify") {
|
|
2752
|
+
const clarifyResp = phaseResponse;
|
|
2753
|
+
console.log("Phase 2/4: Generating questions...\n");
|
|
2754
|
+
const stepContext = {
|
|
2755
|
+
taskId: clarifyResp.taskId,
|
|
2756
|
+
description: clarifyResp.description,
|
|
2757
|
+
stepIndex: 0,
|
|
2758
|
+
totalSteps: 0,
|
|
2759
|
+
gatheredContext: clarifyResp.gatheredContext
|
|
2760
|
+
};
|
|
2761
|
+
const result = await stepExecutor.executeClarifyPhase(clarifyResp.step, stepContext);
|
|
2762
|
+
response = orchestrator.cmdClarifyDone(taskId, result.questions || []);
|
|
2763
|
+
} else if (phaseResponse.phase === "generate") {
|
|
2764
|
+
const generateResp = phaseResponse;
|
|
2765
|
+
console.log("Phase 3/4: Generating document...\n");
|
|
2766
|
+
const stepContext = {
|
|
2767
|
+
taskId: generateResp.taskId,
|
|
2768
|
+
description: generateResp.description,
|
|
2769
|
+
stepIndex: generateResp.stepIndex,
|
|
2770
|
+
totalSteps: generateResp.totalSteps,
|
|
2771
|
+
gatheredContext: generateResp.gatheredContext,
|
|
2772
|
+
answers: generateResp.answers,
|
|
2773
|
+
revisionFeedback: generateResp.context.revisionFeedback
|
|
2774
|
+
};
|
|
2775
|
+
await stepExecutor.executeGeneratePhase(generateResp.step, stepContext);
|
|
2776
|
+
response = orchestrator.cmdGenerateDone(taskId);
|
|
2380
2777
|
}
|
|
2381
|
-
}
|
|
2382
|
-
if (
|
|
2778
|
+
} else if (response.type === "checkpoint") {
|
|
2779
|
+
if (response.checkpoint === "clarify") {
|
|
2780
|
+
const clarifyCheckpoint = response;
|
|
2781
|
+
console.log("Phase 2/4: Questions need answers\n");
|
|
2782
|
+
await adapter.io.askMultiple(clarifyCheckpoint.questions);
|
|
2783
|
+
console.log(`
|
|
2784
|
+
\u23F8\uFE0F Workflow paused. Waiting for /answer on GitHub.`);
|
|
2785
|
+
return;
|
|
2786
|
+
} else if (response.checkpoint === "approve") {
|
|
2787
|
+
const approveCheckpoint = response;
|
|
2788
|
+
console.log("Phase 4/4: Document ready for review\n");
|
|
2789
|
+
await adapter.io.approve(
|
|
2790
|
+
approveCheckpoint.specPath,
|
|
2791
|
+
approveCheckpoint.step,
|
|
2792
|
+
approveCheckpoint.stepIndex,
|
|
2793
|
+
approveCheckpoint.totalSteps
|
|
2794
|
+
);
|
|
2383
2795
|
console.log(`
|
|
2384
|
-
\u23F8\uFE0F Workflow paused. Waiting for
|
|
2796
|
+
\u23F8\uFE0F Workflow paused. Waiting for /approve or /revise on GitHub.`);
|
|
2385
2797
|
return;
|
|
2386
2798
|
}
|
|
2387
|
-
|
|
2799
|
+
} else {
|
|
2800
|
+
console.error(`Unknown response type: ${response.type}`);
|
|
2801
|
+
process.exit(1);
|
|
2388
2802
|
}
|
|
2389
|
-
}
|
|
2803
|
+
}
|
|
2804
|
+
if (response.type === "complete") {
|
|
2390
2805
|
const completeResponse = response;
|
|
2391
2806
|
if (completeResponse.status === "completed") {
|
|
2392
2807
|
console.log("\u2705 Workflow completed!");
|
|
@@ -2431,12 +2846,12 @@ async function postComment(config, body) {
|
|
|
2431
2846
|
}
|
|
2432
2847
|
async function createPullRequestWithAI(config, taskId, userQuery, cwd) {
|
|
2433
2848
|
const { owner, repo, issueNumber } = config;
|
|
2434
|
-
const outputsDir =
|
|
2849
|
+
const outputsDir = join9(getOutputsDir(cwd), taskId);
|
|
2435
2850
|
const outputs = [];
|
|
2436
|
-
if (
|
|
2851
|
+
if (existsSync11(outputsDir)) {
|
|
2437
2852
|
const files = readdirSync4(outputsDir).filter((f) => f.endsWith(".md"));
|
|
2438
2853
|
for (const file of files) {
|
|
2439
|
-
const content =
|
|
2854
|
+
const content = readFileSync9(join9(outputsDir, file), "utf-8");
|
|
2440
2855
|
outputs.push({ step: file.replace(".md", ""), content });
|
|
2441
2856
|
}
|
|
2442
2857
|
}
|
|
@@ -2597,6 +3012,47 @@ async function orchestrateCommand(action, args) {
|
|
|
2597
3012
|
outputJSON(result);
|
|
2598
3013
|
break;
|
|
2599
3014
|
}
|
|
3015
|
+
case "context-done": {
|
|
3016
|
+
const taskId = args[0];
|
|
3017
|
+
const contextJson = args[1];
|
|
3018
|
+
if (!taskId) {
|
|
3019
|
+
outputError("Task ID is required for context-done");
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
const result = orchestrator.cmdContextDone(taskId, contextJson || "");
|
|
3023
|
+
outputJSON(result);
|
|
3024
|
+
break;
|
|
3025
|
+
}
|
|
3026
|
+
case "clarify-done": {
|
|
3027
|
+
const taskId = args[0];
|
|
3028
|
+
const questionsJson = args[1];
|
|
3029
|
+
if (!taskId) {
|
|
3030
|
+
outputError("Task ID is required for clarify-done");
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
let questions = [];
|
|
3034
|
+
if (questionsJson) {
|
|
3035
|
+
try {
|
|
3036
|
+
questions = JSON.parse(questionsJson);
|
|
3037
|
+
} catch {
|
|
3038
|
+
outputError("Invalid JSON for questions");
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
const result = orchestrator.cmdClarifyDone(taskId, questions);
|
|
3043
|
+
outputJSON(result);
|
|
3044
|
+
break;
|
|
3045
|
+
}
|
|
3046
|
+
case "generate-done": {
|
|
3047
|
+
const taskId = args[0];
|
|
3048
|
+
if (!taskId) {
|
|
3049
|
+
outputError("Task ID is required for generate-done");
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
const result = orchestrator.cmdGenerateDone(taskId);
|
|
3053
|
+
outputJSON(result);
|
|
3054
|
+
break;
|
|
3055
|
+
}
|
|
2600
3056
|
case "done": {
|
|
2601
3057
|
const taskId = args[0];
|
|
2602
3058
|
if (!taskId) {
|
|
@@ -2685,8 +3141,8 @@ async function orchestrateCommand(action, args) {
|
|
|
2685
3141
|
}
|
|
2686
3142
|
|
|
2687
3143
|
// src/index.ts
|
|
2688
|
-
var __dirname2 =
|
|
2689
|
-
var pkg = JSON.parse(
|
|
3144
|
+
var __dirname2 = dirname5(fileURLToPath2(import.meta.url));
|
|
3145
|
+
var pkg = JSON.parse(readFileSync10(join10(__dirname2, "..", "package.json"), "utf-8"));
|
|
2690
3146
|
var program = new Command();
|
|
2691
3147
|
program.name("spets").description("Spec Driven Development Execution Framework").version(pkg.version);
|
|
2692
3148
|
program.command("init").description("Initialize spets in current directory").option("-f, --force", "Overwrite existing config").option("--github", "Add GitHub Actions workflow for PR/Issue integration").action(initCommand);
|