spets 0.1.8 → 0.1.10
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.
|
@@ -10,6 +10,9 @@ import { parse as parseYaml } from "yaml";
|
|
|
10
10
|
var SPETS_DIR = ".spets";
|
|
11
11
|
var CONFIG_FILE = "config.yml";
|
|
12
12
|
var STEPS_DIR = "steps";
|
|
13
|
+
var configCache = null;
|
|
14
|
+
var configCachePath = null;
|
|
15
|
+
var stepDefinitionCache = /* @__PURE__ */ new Map();
|
|
13
16
|
function getSpetsDir(cwd = process.cwd()) {
|
|
14
17
|
return join(cwd, SPETS_DIR);
|
|
15
18
|
}
|
|
@@ -27,6 +30,9 @@ function spetsExists(cwd = process.cwd()) {
|
|
|
27
30
|
}
|
|
28
31
|
function loadConfig(cwd = process.cwd()) {
|
|
29
32
|
const configPath = getConfigPath(cwd);
|
|
33
|
+
if (configCache && configCachePath === configPath) {
|
|
34
|
+
return configCache;
|
|
35
|
+
}
|
|
30
36
|
if (!existsSync(configPath)) {
|
|
31
37
|
throw new Error(`Spets config not found. Run 'spets init' first.`);
|
|
32
38
|
}
|
|
@@ -35,9 +41,15 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
35
41
|
if (!config.steps || config.steps.length === 0) {
|
|
36
42
|
throw new Error("Config must define at least one step");
|
|
37
43
|
}
|
|
44
|
+
configCache = config;
|
|
45
|
+
configCachePath = configPath;
|
|
38
46
|
return config;
|
|
39
47
|
}
|
|
40
48
|
function loadStepDefinition(stepName, cwd = process.cwd()) {
|
|
49
|
+
const cacheKey = `${cwd}:${stepName}`;
|
|
50
|
+
if (stepDefinitionCache.has(cacheKey)) {
|
|
51
|
+
return stepDefinitionCache.get(cacheKey);
|
|
52
|
+
}
|
|
41
53
|
const stepDir = join(getStepsDir(cwd), stepName);
|
|
42
54
|
const instructionPath = join(stepDir, "instruction.md");
|
|
43
55
|
const templatePath = join(stepDir, "template.md");
|
|
@@ -46,19 +58,58 @@ function loadStepDefinition(stepName, cwd = process.cwd()) {
|
|
|
46
58
|
}
|
|
47
59
|
const instruction = readFileSync(instructionPath, "utf-8");
|
|
48
60
|
const template = existsSync(templatePath) ? readFileSync(templatePath, "utf-8") : void 0;
|
|
49
|
-
|
|
61
|
+
const stepDef = {
|
|
50
62
|
name: stepName,
|
|
51
63
|
instruction,
|
|
52
64
|
template
|
|
53
65
|
};
|
|
66
|
+
stepDefinitionCache.set(cacheKey, stepDef);
|
|
67
|
+
return stepDef;
|
|
54
68
|
}
|
|
55
69
|
function getGitHubConfig(cwd = process.cwd()) {
|
|
56
70
|
const config = loadConfig(cwd);
|
|
57
71
|
return config.github;
|
|
58
72
|
}
|
|
59
73
|
|
|
74
|
+
// src/core/slug.ts
|
|
75
|
+
function generateRandomSuffix() {
|
|
76
|
+
return Math.random().toString(36).substring(2, 6);
|
|
77
|
+
}
|
|
78
|
+
function generateDatePrefix() {
|
|
79
|
+
const now = /* @__PURE__ */ new Date();
|
|
80
|
+
const year = now.getFullYear();
|
|
81
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
82
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
83
|
+
return `${year}-${month}-${day}`;
|
|
84
|
+
}
|
|
85
|
+
function sanitizeDescription(description) {
|
|
86
|
+
let sanitized = description.toLowerCase();
|
|
87
|
+
sanitized = sanitized.replace(/[^\x00-\x7F]/g, "");
|
|
88
|
+
sanitized = sanitized.replace(/[^a-z0-9\s]/g, "");
|
|
89
|
+
sanitized = sanitized.trim();
|
|
90
|
+
sanitized = sanitized.replace(/\s+/g, " ");
|
|
91
|
+
sanitized = sanitized.replace(/\s+/g, "-");
|
|
92
|
+
if (sanitized.length > 10) {
|
|
93
|
+
sanitized = sanitized.substring(0, 10);
|
|
94
|
+
}
|
|
95
|
+
sanitized = sanitized.replace(/-+$/, "");
|
|
96
|
+
if (sanitized === "") {
|
|
97
|
+
sanitized = "task";
|
|
98
|
+
}
|
|
99
|
+
return sanitized;
|
|
100
|
+
}
|
|
101
|
+
function generateSlug(description) {
|
|
102
|
+
const datePrefix = generateDatePrefix();
|
|
103
|
+
const meaningfulName = sanitizeDescription(description);
|
|
104
|
+
const randomSuffix = generateRandomSuffix();
|
|
105
|
+
return `${datePrefix}-${meaningfulName}-${randomSuffix}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
60
108
|
// src/core/state.ts
|
|
61
|
-
function generateTaskId() {
|
|
109
|
+
function generateTaskId(description) {
|
|
110
|
+
if (description) {
|
|
111
|
+
return generateSlug(description);
|
|
112
|
+
}
|
|
62
113
|
const timestamp = Date.now().toString(36);
|
|
63
114
|
const random = Math.random().toString(36).substring(2, 6);
|
|
64
115
|
return `${timestamp}-${random}`;
|
|
@@ -132,11 +183,68 @@ function listTasks(cwd = process.cwd()) {
|
|
|
132
183
|
}
|
|
133
184
|
return readdirSync(outputsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
134
185
|
}
|
|
186
|
+
function getStateCachePath(taskId, cwd = process.cwd()) {
|
|
187
|
+
return join2(getTaskDir(taskId, cwd), ".state-cache.json");
|
|
188
|
+
}
|
|
189
|
+
function loadStateCache(taskId, cwd = process.cwd()) {
|
|
190
|
+
const cachePath = getStateCachePath(taskId, cwd);
|
|
191
|
+
if (!existsSync2(cachePath)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const cached = JSON.parse(readFileSync2(cachePath, "utf-8"));
|
|
196
|
+
return cached;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function saveStateCache(taskId, state, cwd = process.cwd()) {
|
|
202
|
+
const cachePath = getStateCachePath(taskId, cwd);
|
|
203
|
+
const stepStatuses = {};
|
|
204
|
+
for (const [stepName, output] of state.outputs.entries()) {
|
|
205
|
+
stepStatuses[stepName] = output.status;
|
|
206
|
+
}
|
|
207
|
+
const cached = {
|
|
208
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
209
|
+
taskId: state.taskId,
|
|
210
|
+
userQuery: state.userQuery,
|
|
211
|
+
currentStepIndex: state.currentStepIndex,
|
|
212
|
+
currentStepName: state.currentStepName,
|
|
213
|
+
status: state.status,
|
|
214
|
+
stepStatuses
|
|
215
|
+
};
|
|
216
|
+
try {
|
|
217
|
+
writeFileSync(cachePath, JSON.stringify(cached, null, 2));
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function isStateCacheValid(cached, maxAgeMs = 5e3) {
|
|
222
|
+
const age = Date.now() - new Date(cached.lastUpdated).getTime();
|
|
223
|
+
return age < maxAgeMs;
|
|
224
|
+
}
|
|
135
225
|
function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
136
226
|
const taskDir = getTaskDir(taskId, cwd);
|
|
137
227
|
if (!existsSync2(taskDir)) {
|
|
138
228
|
return null;
|
|
139
229
|
}
|
|
230
|
+
const cached = loadStateCache(taskId, cwd);
|
|
231
|
+
if (cached && isStateCacheValid(cached)) {
|
|
232
|
+
const outputs2 = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const [stepName, status] of Object.entries(cached.stepStatuses)) {
|
|
234
|
+
outputs2.set(stepName, {
|
|
235
|
+
path: getOutputPath(taskId, stepName, cwd),
|
|
236
|
+
status
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
taskId: cached.taskId,
|
|
241
|
+
userQuery: cached.userQuery,
|
|
242
|
+
currentStepIndex: cached.currentStepIndex,
|
|
243
|
+
currentStepName: cached.currentStepName,
|
|
244
|
+
status: cached.status,
|
|
245
|
+
outputs: outputs2
|
|
246
|
+
};
|
|
247
|
+
}
|
|
140
248
|
const outputs = /* @__PURE__ */ new Map();
|
|
141
249
|
let lastApprovedIndex = -1;
|
|
142
250
|
let currentStatus = "in_progress";
|
|
@@ -171,7 +279,7 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
|
171
279
|
if (currentDoc?.frontmatter.status === "draft" && currentDoc.frontmatter.open_questions?.length) {
|
|
172
280
|
currentStatus = "paused";
|
|
173
281
|
}
|
|
174
|
-
|
|
282
|
+
const state = {
|
|
175
283
|
taskId,
|
|
176
284
|
userQuery,
|
|
177
285
|
currentStepIndex,
|
|
@@ -179,6 +287,8 @@ function getWorkflowState(taskId, config, cwd = process.cwd()) {
|
|
|
179
287
|
status: currentStatus,
|
|
180
288
|
outputs
|
|
181
289
|
};
|
|
290
|
+
saveStateCache(taskId, state, cwd);
|
|
291
|
+
return state;
|
|
182
292
|
}
|
|
183
293
|
function saveTaskMetadata(taskId, userQuery, cwd = process.cwd()) {
|
|
184
294
|
const taskDir = ensureTaskDir(taskId, cwd);
|
|
@@ -192,6 +302,21 @@ function loadTaskMetadata(taskId, cwd = process.cwd()) {
|
|
|
192
302
|
}
|
|
193
303
|
return JSON.parse(readFileSync2(metaPath, "utf-8"));
|
|
194
304
|
}
|
|
305
|
+
function extractPlanSummary(taskId, cwd = process.cwd()) {
|
|
306
|
+
const doc = loadDocument(taskId, "01-plan", cwd);
|
|
307
|
+
if (!doc) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
const summaryMatch = doc.content.match(/## Summary\n\n(.+?)(?=\n\n##|\n\n$|$)/s);
|
|
311
|
+
if (summaryMatch) {
|
|
312
|
+
return summaryMatch[1].trim();
|
|
313
|
+
}
|
|
314
|
+
const goalMatch = doc.content.match(/## Goal\n\n(.+?)(?=\n\n##|\n\n$|$)/s);
|
|
315
|
+
if (goalMatch) {
|
|
316
|
+
return goalMatch[1].trim();
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
195
320
|
|
|
196
321
|
export {
|
|
197
322
|
getSpetsDir,
|
|
@@ -213,5 +338,6 @@ export {
|
|
|
213
338
|
listTasks,
|
|
214
339
|
getWorkflowState,
|
|
215
340
|
saveTaskMetadata,
|
|
216
|
-
loadTaskMetadata
|
|
341
|
+
loadTaskMetadata,
|
|
342
|
+
extractPlanSummary
|
|
217
343
|
};
|
package/dist/index.js
CHANGED
|
@@ -16,13 +16,13 @@ import {
|
|
|
16
16
|
saveTaskMetadata,
|
|
17
17
|
spetsExists,
|
|
18
18
|
updateDocumentStatus
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-NOS3N4GT.js";
|
|
20
20
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
import { Command } from "commander";
|
|
23
23
|
|
|
24
24
|
// src/commands/init.ts
|
|
25
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
25
|
+
import { mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
26
26
|
import { join, dirname } from "path";
|
|
27
27
|
import { fileURLToPath } from "url";
|
|
28
28
|
import { execSync } from "child_process";
|
|
@@ -121,155 +121,20 @@ function createDefaultSteps(spetsDir) {
|
|
|
121
121
|
writeFileSync(join(implementDir, "template.md"), getImplementTemplate());
|
|
122
122
|
}
|
|
123
123
|
function getPlanInstruction() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
You are creating a technical plan for the given task.
|
|
127
|
-
|
|
128
|
-
## Your Goal
|
|
129
|
-
|
|
130
|
-
Analyze the user's request and create a detailed implementation plan.
|
|
131
|
-
|
|
132
|
-
## Process
|
|
133
|
-
|
|
134
|
-
1. **Understand the Request**
|
|
135
|
-
- Parse the user's query to identify the core requirements
|
|
136
|
-
- Identify any ambiguities or missing information
|
|
137
|
-
|
|
138
|
-
2. **Ask Clarifying Questions** (if needed)
|
|
139
|
-
- If requirements are unclear, list questions in the \`open_questions\` section
|
|
140
|
-
- Questions should be specific and actionable
|
|
141
|
-
|
|
142
|
-
3. **Create the Plan**
|
|
143
|
-
- Break down the task into concrete steps
|
|
144
|
-
- Identify files to create/modify
|
|
145
|
-
- Consider edge cases and potential issues
|
|
146
|
-
|
|
147
|
-
## Output Format
|
|
148
|
-
|
|
149
|
-
Follow the template provided. Include:
|
|
150
|
-
- Summary of what will be built
|
|
151
|
-
- Step-by-step implementation plan
|
|
152
|
-
- Files to be created/modified
|
|
153
|
-
- Any open questions (if requirements are unclear)
|
|
154
|
-
`;
|
|
124
|
+
const fullTemplate = readFileSync(join(__dirname, "..", ".spets", "steps", "01-plan", "instruction.md"), "utf-8");
|
|
125
|
+
return fullTemplate;
|
|
155
126
|
}
|
|
156
127
|
function getPlanTemplate() {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
## Summary
|
|
160
|
-
|
|
161
|
-
Brief description of what will be implemented.
|
|
162
|
-
|
|
163
|
-
## Requirements
|
|
164
|
-
|
|
165
|
-
- Requirement 1
|
|
166
|
-
- Requirement 2
|
|
167
|
-
|
|
168
|
-
## Implementation Steps
|
|
169
|
-
|
|
170
|
-
### Step 1: [Description]
|
|
171
|
-
|
|
172
|
-
Details...
|
|
173
|
-
|
|
174
|
-
### Step 2: [Description]
|
|
175
|
-
|
|
176
|
-
Details...
|
|
177
|
-
|
|
178
|
-
## Files to Modify
|
|
179
|
-
|
|
180
|
-
| File | Action | Description |
|
|
181
|
-
|------|--------|-------------|
|
|
182
|
-
| path/to/file | Create/Modify | What changes |
|
|
183
|
-
|
|
184
|
-
## Open Questions
|
|
185
|
-
|
|
186
|
-
<!-- Remove this section if no questions -->
|
|
187
|
-
|
|
188
|
-
- Question 1?
|
|
189
|
-
- Question 2?
|
|
190
|
-
|
|
191
|
-
## Risks & Considerations
|
|
192
|
-
|
|
193
|
-
- Risk 1
|
|
194
|
-
- Risk 2
|
|
195
|
-
`;
|
|
128
|
+
const fullTemplate = readFileSync(join(__dirname, "..", ".spets", "steps", "01-plan", "template.md"), "utf-8");
|
|
129
|
+
return fullTemplate;
|
|
196
130
|
}
|
|
197
131
|
function getImplementInstruction() {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
You are implementing the plan from the previous step.
|
|
201
|
-
|
|
202
|
-
## Your Goal
|
|
203
|
-
|
|
204
|
-
Write the actual code based on the approved plan.
|
|
205
|
-
|
|
206
|
-
## Process
|
|
207
|
-
|
|
208
|
-
1. **Review the Plan**
|
|
209
|
-
- Read the approved plan document carefully
|
|
210
|
-
- Understand all requirements and steps
|
|
211
|
-
|
|
212
|
-
2. **Implement**
|
|
213
|
-
- Follow the plan step by step
|
|
214
|
-
- Write clean, well-documented code
|
|
215
|
-
- Handle edge cases identified in the plan
|
|
216
|
-
|
|
217
|
-
3. **Document Changes**
|
|
218
|
-
- List all files created/modified
|
|
219
|
-
- Explain key decisions made during implementation
|
|
220
|
-
|
|
221
|
-
## Output Format
|
|
222
|
-
|
|
223
|
-
Follow the template provided. Include:
|
|
224
|
-
- Summary of implementation
|
|
225
|
-
- List of all changes made
|
|
226
|
-
- Any deviations from the plan (with justification)
|
|
227
|
-
- Testing notes
|
|
228
|
-
`;
|
|
132
|
+
const fullTemplate = readFileSync(join(__dirname, "..", ".spets", "steps", "02-implement", "instruction.md"), "utf-8");
|
|
133
|
+
return fullTemplate;
|
|
229
134
|
}
|
|
230
135
|
function getImplementTemplate() {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
## Summary
|
|
234
|
-
|
|
235
|
-
Brief description of what was implemented.
|
|
236
|
-
|
|
237
|
-
## Changes Made
|
|
238
|
-
|
|
239
|
-
### New Files
|
|
240
|
-
|
|
241
|
-
| File | Description |
|
|
242
|
-
|------|-------------|
|
|
243
|
-
| path/to/file | What it does |
|
|
244
|
-
|
|
245
|
-
### Modified Files
|
|
246
|
-
|
|
247
|
-
| File | Changes |
|
|
248
|
-
|------|---------|
|
|
249
|
-
| path/to/file | What changed |
|
|
250
|
-
|
|
251
|
-
## Key Decisions
|
|
252
|
-
|
|
253
|
-
- Decision 1: Explanation
|
|
254
|
-
- Decision 2: Explanation
|
|
255
|
-
|
|
256
|
-
## Deviations from Plan
|
|
257
|
-
|
|
258
|
-
<!-- Remove if no deviations -->
|
|
259
|
-
|
|
260
|
-
None / List any deviations with justification.
|
|
261
|
-
|
|
262
|
-
## Testing
|
|
263
|
-
|
|
264
|
-
- [ ] Manual testing completed
|
|
265
|
-
- [ ] Unit tests added
|
|
266
|
-
- [ ] Integration tests pass
|
|
267
|
-
|
|
268
|
-
## Next Steps
|
|
269
|
-
|
|
270
|
-
- Follow-up task 1
|
|
271
|
-
- Follow-up task 2
|
|
272
|
-
`;
|
|
136
|
+
const fullTemplate = readFileSync(join(__dirname, "..", ".spets", "steps", "02-implement", "template.md"), "utf-8");
|
|
137
|
+
return fullTemplate;
|
|
273
138
|
}
|
|
274
139
|
function createClaudeCommand(cwd) {
|
|
275
140
|
const commandDir = join(cwd, ".claude", "commands");
|
|
@@ -458,7 +323,13 @@ jobs:
|
|
|
458
323
|
- name: Checkout
|
|
459
324
|
uses: actions/checkout@v4
|
|
460
325
|
with:
|
|
461
|
-
fetch-depth:
|
|
326
|
+
fetch-depth: 1
|
|
327
|
+
persist-credentials: false
|
|
328
|
+
|
|
329
|
+
- name: Setup Git
|
|
330
|
+
run: |
|
|
331
|
+
git config user.name "github-actions[bot]"
|
|
332
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
462
333
|
|
|
463
334
|
- name: Parse Issue body
|
|
464
335
|
id: parse
|
|
@@ -479,17 +350,33 @@ jobs:
|
|
|
479
350
|
|
|
480
351
|
- name: Create and checkout branch
|
|
481
352
|
run: |
|
|
353
|
+
git remote set-url origin https://x-access-token:${gh("secrets.PAT_TOKEN")}@github.com/${gh("github.repository")}.git
|
|
482
354
|
git checkout -b ${gh("steps.parse.outputs.branch")}
|
|
483
355
|
git push -u origin ${gh("steps.parse.outputs.branch")}
|
|
484
|
-
env:
|
|
485
|
-
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
486
356
|
|
|
487
357
|
- name: Setup Node.js
|
|
488
358
|
uses: actions/setup-node@v4
|
|
489
359
|
with:
|
|
490
360
|
node-version: '20'
|
|
361
|
+
cache: 'npm'
|
|
362
|
+
|
|
363
|
+
- name: Cache global npm packages
|
|
364
|
+
uses: actions/cache@v4
|
|
365
|
+
with:
|
|
366
|
+
path: ~/.npm
|
|
367
|
+
key: ${gh("runner.os")}-npm-global-${gh("hashFiles('package-lock.json')")}
|
|
368
|
+
restore-keys: |
|
|
369
|
+
${gh("runner.os")}-npm-global-
|
|
370
|
+
|
|
371
|
+
- name: Cache Claude Code
|
|
372
|
+
id: cache-claude
|
|
373
|
+
uses: actions/cache@v4
|
|
374
|
+
with:
|
|
375
|
+
path: /usr/local/lib/node_modules/@anthropic-ai/claude-code
|
|
376
|
+
key: claude-code-${gh("runner.os")}-v1
|
|
491
377
|
|
|
492
378
|
- name: Install Claude Code
|
|
379
|
+
if: steps.cache-claude.outputs.cache-hit != 'true'
|
|
493
380
|
run: npm install -g @anthropic-ai/claude-code
|
|
494
381
|
|
|
495
382
|
- name: Install dependencies
|
|
@@ -505,13 +392,10 @@ jobs:
|
|
|
505
392
|
|
|
506
393
|
- name: Push changes
|
|
507
394
|
run: |
|
|
508
|
-
git
|
|
509
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
395
|
+
git remote set-url origin https://x-access-token:${gh("secrets.PAT_TOKEN")}@github.com/${gh("github.repository")}.git
|
|
510
396
|
git add -A
|
|
511
397
|
git diff --staged --quiet || git commit -m "Spets: Start workflow for #${gh("github.event.issue.number")}"
|
|
512
398
|
git push
|
|
513
|
-
env:
|
|
514
|
-
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
515
399
|
|
|
516
400
|
# Handle commands from Issue/PR comments
|
|
517
401
|
handle-command:
|
|
@@ -524,38 +408,108 @@ jobs:
|
|
|
524
408
|
runs-on: ubuntu-latest
|
|
525
409
|
|
|
526
410
|
steps:
|
|
527
|
-
- name: Find
|
|
411
|
+
- name: Find branch from Issue or PR
|
|
528
412
|
id: branch
|
|
413
|
+
env:
|
|
414
|
+
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
529
415
|
run: |
|
|
530
|
-
#
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
416
|
+
# Check if this is a PR (has pull_request field)
|
|
417
|
+
PR_BRANCH=$(gh api repos/${gh("github.repository")}/issues/${gh("github.event.issue.number")} --jq '.pull_request.url // empty' 2>/dev/null)
|
|
418
|
+
|
|
419
|
+
if [ -n "$PR_BRANCH" ]; then
|
|
420
|
+
# It's a PR - get head branch directly
|
|
421
|
+
BRANCH=$(gh api repos/${gh("github.repository")}/pulls/${gh("github.event.issue.number")} --jq '.head.ref')
|
|
422
|
+
echo "Found PR head branch: $BRANCH"
|
|
423
|
+
else
|
|
424
|
+
# It's an Issue - try to parse branch name from body
|
|
425
|
+
ISSUE_BODY=$(gh api repos/${gh("github.repository")}/issues/${gh("github.event.issue.number")} --jq '.body')
|
|
426
|
+
CUSTOM_BRANCH=$(echo "$ISSUE_BODY" | sed -n '/### Branch Name/,/###/{/###/!p;}' | sed '/^$/d' | head -1)
|
|
427
|
+
|
|
428
|
+
if [ -n "$CUSTOM_BRANCH" ]; then
|
|
429
|
+
BRANCH="$CUSTOM_BRANCH"
|
|
430
|
+
else
|
|
431
|
+
BRANCH="spets/${gh("github.event.issue.number")}"
|
|
432
|
+
fi
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
echo "Checking for branch: $BRANCH"
|
|
436
|
+
|
|
437
|
+
# Check if branch exists on remote using gh api
|
|
438
|
+
if gh api "repos/${gh("github.repository")}/branches/$BRANCH" --silent 2>/dev/null; then
|
|
439
|
+
echo "name=$BRANCH" >> $GITHUB_OUTPUT
|
|
440
|
+
echo "exists=true" >> $GITHUB_OUTPUT
|
|
441
|
+
echo "Branch $BRANCH found!"
|
|
442
|
+
else
|
|
443
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
444
|
+
echo "expected=$BRANCH" >> $GITHUB_OUTPUT
|
|
445
|
+
echo "::error::Branch $BRANCH not found. Start workflow first by creating an Issue with 'spets' label."
|
|
534
446
|
fi
|
|
535
|
-
|
|
447
|
+
|
|
448
|
+
- name: Post error comment
|
|
449
|
+
if: steps.branch.outputs.exists == 'false'
|
|
450
|
+
run: |
|
|
451
|
+
gh issue comment ${gh("github.event.issue.number")} \\
|
|
452
|
+
-R "${gh("github.repository")}" \\
|
|
453
|
+
--body "\u274C **Spets Error**: Branch \\\`${gh("steps.branch.outputs.expected")}\\\` not found.
|
|
454
|
+
|
|
455
|
+
Please make sure the workflow was started properly. You can:
|
|
456
|
+
1. Add the \\\`spets\\\` label to this issue to trigger the start workflow
|
|
457
|
+
2. Or manually create the branch and run \\\`spets start\\\`"
|
|
536
458
|
env:
|
|
537
459
|
GH_TOKEN: ${gh("secrets.GITHUB_TOKEN")}
|
|
538
460
|
|
|
461
|
+
- name: Exit if branch not found
|
|
462
|
+
if: steps.branch.outputs.exists == 'false'
|
|
463
|
+
run: exit 1
|
|
464
|
+
|
|
539
465
|
- name: Checkout
|
|
540
466
|
uses: actions/checkout@v4
|
|
541
467
|
with:
|
|
542
468
|
ref: ${gh("steps.branch.outputs.name")}
|
|
543
|
-
fetch-depth:
|
|
469
|
+
fetch-depth: 1
|
|
470
|
+
persist-credentials: false
|
|
471
|
+
|
|
472
|
+
- name: Setup Git
|
|
473
|
+
run: |
|
|
474
|
+
git config user.name "github-actions[bot]"
|
|
475
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
544
476
|
|
|
545
477
|
- name: Setup Node.js
|
|
546
478
|
uses: actions/setup-node@v4
|
|
547
479
|
with:
|
|
548
480
|
node-version: '20'
|
|
481
|
+
cache: 'npm'
|
|
482
|
+
|
|
483
|
+
- name: Cache global npm packages
|
|
484
|
+
uses: actions/cache@v4
|
|
485
|
+
with:
|
|
486
|
+
path: ~/.npm
|
|
487
|
+
key: ${gh("runner.os")}-npm-global-${gh("hashFiles('package-lock.json')")}
|
|
488
|
+
restore-keys: |
|
|
489
|
+
${gh("runner.os")}-npm-global-
|
|
490
|
+
|
|
491
|
+
- name: Cache Claude Code
|
|
492
|
+
id: cache-claude
|
|
493
|
+
uses: actions/cache@v4
|
|
494
|
+
with:
|
|
495
|
+
path: /usr/local/lib/node_modules/@anthropic-ai/claude-code
|
|
496
|
+
key: claude-code-${gh("runner.os")}-v1
|
|
549
497
|
|
|
550
498
|
- name: Install Claude Code
|
|
499
|
+
if: steps.cache-claude.outputs.cache-hit != 'true'
|
|
551
500
|
run: npm install -g @anthropic-ai/claude-code
|
|
552
501
|
|
|
553
502
|
- name: Install dependencies
|
|
554
503
|
run: npm ci
|
|
555
504
|
|
|
556
505
|
- name: Run Spets command
|
|
506
|
+
id: spets
|
|
557
507
|
run: |
|
|
558
508
|
npx spets github --issue ${gh("github.event.issue.number")} --comment "$COMMENT"
|
|
509
|
+
# Check if PR should be created
|
|
510
|
+
if [[ "$COMMENT" == "/approve --pr"* ]]; then
|
|
511
|
+
echo "create_pr=true" >> $GITHUB_OUTPUT
|
|
512
|
+
fi
|
|
559
513
|
env:
|
|
560
514
|
COMMENT: ${gh("github.event.comment.body")}
|
|
561
515
|
CLAUDE_CODE_OAUTH_TOKEN: ${gh("secrets.CLAUDE_CODE_OAUTH_TOKEN")}
|
|
@@ -563,13 +517,33 @@ jobs:
|
|
|
563
517
|
|
|
564
518
|
- name: Push changes
|
|
565
519
|
run: |
|
|
566
|
-
git
|
|
567
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
520
|
+
git remote set-url origin https://x-access-token:${gh("secrets.PAT_TOKEN")}@github.com/${gh("github.repository")}.git
|
|
568
521
|
git add -A
|
|
569
522
|
git diff --staged --quiet || git commit -m "Spets: Update from #${gh("github.event.issue.number")}"
|
|
570
523
|
git push
|
|
524
|
+
|
|
525
|
+
- name: Create PR
|
|
526
|
+
if: steps.spets.outputs.create_pr == 'true'
|
|
527
|
+
run: |
|
|
528
|
+
PR_BODY="Closes #${gh("github.event.issue.number")}
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Spets Commands
|
|
533
|
+
|
|
534
|
+
| Command | Description |
|
|
535
|
+
|---------|-------------|
|
|
536
|
+
| \\\`/approve\\\` | Approve current step and continue |
|
|
537
|
+
| \\\`/approve --pr\\\` | Approve and create PR |
|
|
538
|
+
| \\\`/revise <feedback>\\\` | Request changes with feedback |
|
|
539
|
+
| \\\`/reject\\\` | Reject and stop workflow |"
|
|
540
|
+
|
|
541
|
+
gh pr create \\
|
|
542
|
+
--title "Spets: Issue #${gh("github.event.issue.number")}" \\
|
|
543
|
+
--body "$PR_BODY" \\
|
|
544
|
+
--repo ${gh("github.repository")}
|
|
571
545
|
env:
|
|
572
|
-
GH_TOKEN: ${gh("secrets.
|
|
546
|
+
GH_TOKEN: ${gh("secrets.PAT_TOKEN")}
|
|
573
547
|
`;
|
|
574
548
|
}
|
|
575
549
|
|
|
@@ -702,6 +676,16 @@ var Executor = class {
|
|
|
702
676
|
this.config = options.config;
|
|
703
677
|
this.cwd = options.cwd || process.cwd();
|
|
704
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* Preload all step definitions into cache for better performance
|
|
681
|
+
*/
|
|
682
|
+
async preloadSteps() {
|
|
683
|
+
await Promise.all(
|
|
684
|
+
this.config.steps.map(
|
|
685
|
+
(stepName) => Promise.resolve(loadStepDefinition(stepName, this.cwd))
|
|
686
|
+
)
|
|
687
|
+
);
|
|
688
|
+
}
|
|
705
689
|
async executeWorkflow(taskId, userQuery, startIndex = 0, feedback) {
|
|
706
690
|
let previousOutput;
|
|
707
691
|
if (startIndex > 0) {
|
|
@@ -954,7 +938,6 @@ var CliPlatform = class extends BasePlatform {
|
|
|
954
938
|
async callClaude(prompt) {
|
|
955
939
|
return new Promise((resolve, reject) => {
|
|
956
940
|
const proc = spawn2(this.claudeCommand, [
|
|
957
|
-
"--print",
|
|
958
941
|
"--permission-mode",
|
|
959
942
|
"bypassPermissions"
|
|
960
943
|
], {
|
|
@@ -1024,6 +1007,21 @@ var CliPlatform = class extends BasePlatform {
|
|
|
1024
1007
|
|
|
1025
1008
|
// src/platform/github.ts
|
|
1026
1009
|
import { spawn as spawn3 } from "child_process";
|
|
1010
|
+
async function retryWithBackoff(fn, maxRetries = 3, initialDelayMs = 1e3) {
|
|
1011
|
+
let lastError = null;
|
|
1012
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
1013
|
+
try {
|
|
1014
|
+
return await fn();
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
lastError = error;
|
|
1017
|
+
if (i === maxRetries - 1) break;
|
|
1018
|
+
const delay = initialDelayMs * Math.pow(2, i);
|
|
1019
|
+
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms...`);
|
|
1020
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
throw lastError || new Error("Retry failed");
|
|
1024
|
+
}
|
|
1027
1025
|
var PauseForInputError = class extends Error {
|
|
1028
1026
|
constructor(inputType, data) {
|
|
1029
1027
|
super(`Paused waiting for ${inputType}`);
|
|
@@ -1211,26 +1209,39 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1211
1209
|
const { owner, repo, issueNumber } = this.config;
|
|
1212
1210
|
await this.runGh(["issue", "comment", String(issueNumber), "--body", body, "-R", `${owner}/${repo}`]);
|
|
1213
1211
|
}
|
|
1214
|
-
async runGh(args) {
|
|
1215
|
-
return
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1212
|
+
async runGh(args, timeoutMs = 3e4) {
|
|
1213
|
+
return retryWithBackoff(async () => {
|
|
1214
|
+
return new Promise((resolve, reject) => {
|
|
1215
|
+
const proc = spawn3("gh", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
1216
|
+
let stdout = "";
|
|
1217
|
+
let stderr = "";
|
|
1218
|
+
let isTimedOut = false;
|
|
1219
|
+
const timeoutId = setTimeout(() => {
|
|
1220
|
+
isTimedOut = true;
|
|
1221
|
+
proc.kill();
|
|
1222
|
+
reject(new Error(`gh command timed out after ${timeoutMs}ms`));
|
|
1223
|
+
}, timeoutMs);
|
|
1224
|
+
proc.stdout.on("data", (data) => {
|
|
1225
|
+
stdout += data.toString();
|
|
1226
|
+
});
|
|
1227
|
+
proc.stderr.on("data", (data) => {
|
|
1228
|
+
stderr += data.toString();
|
|
1229
|
+
});
|
|
1230
|
+
proc.on("close", (code) => {
|
|
1231
|
+
clearTimeout(timeoutId);
|
|
1232
|
+
if (isTimedOut) return;
|
|
1233
|
+
if (code !== 0) {
|
|
1234
|
+
reject(new Error(`gh command failed: ${stderr}`));
|
|
1235
|
+
} else {
|
|
1236
|
+
resolve(stdout);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
proc.on("error", (err) => {
|
|
1240
|
+
clearTimeout(timeoutId);
|
|
1241
|
+
if (!isTimedOut) {
|
|
1242
|
+
reject(new Error(`Failed to run gh: ${err.message}`));
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1234
1245
|
});
|
|
1235
1246
|
});
|
|
1236
1247
|
}
|
|
@@ -1264,11 +1275,9 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1264
1275
|
async callClaude(prompt) {
|
|
1265
1276
|
return new Promise((resolve, reject) => {
|
|
1266
1277
|
const proc = spawn3("claude", [
|
|
1267
|
-
"-p",
|
|
1268
|
-
prompt,
|
|
1269
1278
|
"--permission-mode",
|
|
1270
1279
|
"bypassPermissions"
|
|
1271
|
-
], { stdio: ["
|
|
1280
|
+
], { stdio: ["pipe", "pipe", "pipe"] });
|
|
1272
1281
|
let stdout = "";
|
|
1273
1282
|
let stderr = "";
|
|
1274
1283
|
proc.stdout.on("data", (data) => {
|
|
@@ -1277,6 +1286,8 @@ var GitHubPlatform = class extends BasePlatform {
|
|
|
1277
1286
|
proc.stderr.on("data", (data) => {
|
|
1278
1287
|
stderr += data.toString();
|
|
1279
1288
|
});
|
|
1289
|
+
proc.stdin.write(prompt);
|
|
1290
|
+
proc.stdin.end();
|
|
1280
1291
|
proc.on("close", (code) => {
|
|
1281
1292
|
if (code !== 0) {
|
|
1282
1293
|
reject(new Error(`Claude CLI failed (code ${code}): stderr=${stderr}, stdout=${stdout}`));
|
|
@@ -1402,7 +1413,7 @@ async function startCommand(query, options) {
|
|
|
1402
1413
|
process.exit(1);
|
|
1403
1414
|
}
|
|
1404
1415
|
const config = loadConfig(cwd);
|
|
1405
|
-
const taskId = generateTaskId();
|
|
1416
|
+
const taskId = generateTaskId(query);
|
|
1406
1417
|
saveTaskMetadata(taskId, query, cwd);
|
|
1407
1418
|
let platform;
|
|
1408
1419
|
if (options.github || options.issue !== void 0 || options.pr !== void 0) {
|
|
@@ -1612,28 +1623,28 @@ function installClaudePlugin() {
|
|
|
1612
1623
|
const claudeDir = join3(homedir(), ".claude");
|
|
1613
1624
|
const commandsDir = join3(claudeDir, "commands");
|
|
1614
1625
|
mkdirSync2(commandsDir, { recursive: true });
|
|
1615
|
-
const skillPath = join3(commandsDir, "
|
|
1626
|
+
const skillPath = join3(commandsDir, "spets.md");
|
|
1616
1627
|
writeFileSync2(skillPath, getClaudeSkillContent());
|
|
1617
1628
|
console.log("Installed Claude Code plugin.");
|
|
1618
1629
|
console.log(`Location: ${skillPath}`);
|
|
1619
1630
|
console.log("");
|
|
1620
1631
|
console.log("Usage in Claude Code:");
|
|
1621
|
-
console.log(' /
|
|
1632
|
+
console.log(' /spets "your task description"');
|
|
1622
1633
|
console.log("");
|
|
1623
1634
|
console.log("This skill runs deterministically within your Claude Code session.");
|
|
1624
1635
|
console.log("No additional Claude processes are spawned.");
|
|
1625
1636
|
}
|
|
1626
1637
|
async function uninstallPlugin(name) {
|
|
1627
1638
|
if (name === "claude") {
|
|
1628
|
-
const
|
|
1629
|
-
const
|
|
1639
|
+
const skillPath = join3(homedir(), ".claude", "commands", "spets.md");
|
|
1640
|
+
const legacySkillPath = join3(homedir(), ".claude", "commands", "sdd-do.md");
|
|
1630
1641
|
let uninstalled = false;
|
|
1631
|
-
if (existsSync3(
|
|
1632
|
-
rmSync(
|
|
1642
|
+
if (existsSync3(skillPath)) {
|
|
1643
|
+
rmSync(skillPath);
|
|
1633
1644
|
uninstalled = true;
|
|
1634
1645
|
}
|
|
1635
|
-
if (existsSync3(
|
|
1636
|
-
rmSync(
|
|
1646
|
+
if (existsSync3(legacySkillPath)) {
|
|
1647
|
+
rmSync(legacySkillPath);
|
|
1637
1648
|
uninstalled = true;
|
|
1638
1649
|
}
|
|
1639
1650
|
if (uninstalled) {
|
|
@@ -1649,11 +1660,11 @@ async function uninstallPlugin(name) {
|
|
|
1649
1660
|
async function listPlugins() {
|
|
1650
1661
|
console.log("Available plugins:");
|
|
1651
1662
|
console.log("");
|
|
1652
|
-
console.log(" claude - Claude Code /
|
|
1663
|
+
console.log(" claude - Claude Code /spets skill");
|
|
1653
1664
|
console.log("");
|
|
1654
|
-
const
|
|
1655
|
-
const
|
|
1656
|
-
const claudeInstalled = existsSync3(
|
|
1665
|
+
const skillPath = join3(homedir(), ".claude", "commands", "spets.md");
|
|
1666
|
+
const legacySkillPath = join3(homedir(), ".claude", "commands", "sdd-do.md");
|
|
1667
|
+
const claudeInstalled = existsSync3(skillPath) || existsSync3(legacySkillPath);
|
|
1657
1668
|
console.log("Installed:");
|
|
1658
1669
|
if (claudeInstalled) {
|
|
1659
1670
|
console.log(" - claude");
|
|
@@ -1662,7 +1673,7 @@ async function listPlugins() {
|
|
|
1662
1673
|
}
|
|
1663
1674
|
}
|
|
1664
1675
|
function getClaudeSkillContent() {
|
|
1665
|
-
return `#
|
|
1676
|
+
return `# Spets - Spec Driven Development
|
|
1666
1677
|
|
|
1667
1678
|
Spec-Driven Development workflow execution skill for Claude Code.
|
|
1668
1679
|
|
|
@@ -1671,7 +1682,7 @@ Spec-Driven Development workflow execution skill for Claude Code.
|
|
|
1671
1682
|
## When to Use This Skill
|
|
1672
1683
|
|
|
1673
1684
|
Automatically invoked when user uses:
|
|
1674
|
-
- \`/
|
|
1685
|
+
- \`/spets\` - Run Spets workflow
|
|
1675
1686
|
|
|
1676
1687
|
---
|
|
1677
1688
|
|
|
@@ -1893,7 +1904,7 @@ async function githubCommand(options) {
|
|
|
1893
1904
|
}
|
|
1894
1905
|
if (!taskId) {
|
|
1895
1906
|
const config2 = loadConfig(cwd);
|
|
1896
|
-
const { listTasks: listTasks2 } = await import("./state-
|
|
1907
|
+
const { listTasks: listTasks2 } = await import("./state-54IS3PZH.js");
|
|
1897
1908
|
const tasks = listTasks2(cwd);
|
|
1898
1909
|
for (const tid of tasks) {
|
|
1899
1910
|
const state2 = getWorkflowState(tid, config2, cwd);
|
|
@@ -2072,7 +2083,7 @@ _Updated by [Spets](https://github.com/eatnug/spets)_`;
|
|
|
2072
2083
|
}
|
|
2073
2084
|
|
|
2074
2085
|
// src/orchestrator/index.ts
|
|
2075
|
-
import { readFileSync, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
2086
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
2076
2087
|
import { join as join4, dirname as dirname2 } from "path";
|
|
2077
2088
|
import matter from "gray-matter";
|
|
2078
2089
|
var Orchestrator = class {
|
|
@@ -2110,7 +2121,7 @@ var Orchestrator = class {
|
|
|
2110
2121
|
if (!existsSync4(statePath)) {
|
|
2111
2122
|
return null;
|
|
2112
2123
|
}
|
|
2113
|
-
const data = JSON.parse(
|
|
2124
|
+
const data = JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
2114
2125
|
return data;
|
|
2115
2126
|
}
|
|
2116
2127
|
saveState(state) {
|
|
@@ -2129,7 +2140,7 @@ var Orchestrator = class {
|
|
|
2129
2140
|
if (!existsSync4(specPath)) {
|
|
2130
2141
|
return [];
|
|
2131
2142
|
}
|
|
2132
|
-
const content =
|
|
2143
|
+
const content = readFileSync2(specPath, "utf-8");
|
|
2133
2144
|
const { data } = matter(content);
|
|
2134
2145
|
const questions = [];
|
|
2135
2146
|
if (data.open_questions && Array.isArray(data.open_questions)) {
|
|
@@ -2148,11 +2159,6 @@ var Orchestrator = class {
|
|
|
2148
2159
|
}
|
|
2149
2160
|
return questions;
|
|
2150
2161
|
}
|
|
2151
|
-
generateTaskId(description) {
|
|
2152
|
-
const timestamp = Date.now().toString(36);
|
|
2153
|
-
const random = Math.random().toString(36).substring(2, 6);
|
|
2154
|
-
return `${timestamp}-${random}`;
|
|
2155
|
-
}
|
|
2156
2162
|
// ===========================================================================
|
|
2157
2163
|
// Protocol Response Builders
|
|
2158
2164
|
// ===========================================================================
|
|
@@ -2258,7 +2264,7 @@ var Orchestrator = class {
|
|
|
2258
2264
|
cmdInit(description) {
|
|
2259
2265
|
try {
|
|
2260
2266
|
const steps = this.getSteps();
|
|
2261
|
-
const taskId =
|
|
2267
|
+
const taskId = generateTaskId(description);
|
|
2262
2268
|
const state = {
|
|
2263
2269
|
taskId,
|
|
2264
2270
|
description,
|
|
@@ -2306,7 +2312,7 @@ var Orchestrator = class {
|
|
|
2306
2312
|
}
|
|
2307
2313
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2308
2314
|
if (existsSync4(specPath)) {
|
|
2309
|
-
const content =
|
|
2315
|
+
const content = readFileSync2(specPath, "utf-8");
|
|
2310
2316
|
const { content: body, data } = matter(content);
|
|
2311
2317
|
if (data.open_questions && Array.isArray(data.open_questions)) {
|
|
2312
2318
|
data.open_questions = data.open_questions.map((q, i) => ({
|
|
@@ -2333,7 +2339,7 @@ var Orchestrator = class {
|
|
|
2333
2339
|
const steps = this.getSteps();
|
|
2334
2340
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2335
2341
|
if (existsSync4(specPath)) {
|
|
2336
|
-
const content =
|
|
2342
|
+
const content = readFileSync2(specPath, "utf-8");
|
|
2337
2343
|
const { content: body, data } = matter(content);
|
|
2338
2344
|
data.status = "approved";
|
|
2339
2345
|
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2375,7 +2381,7 @@ var Orchestrator = class {
|
|
|
2375
2381
|
}
|
|
2376
2382
|
const specPath = this.getSpecPath(taskId, state.currentStep);
|
|
2377
2383
|
if (existsSync4(specPath)) {
|
|
2378
|
-
const content =
|
|
2384
|
+
const content = readFileSync2(specPath, "utf-8");
|
|
2379
2385
|
const { content: body, data } = matter(content);
|
|
2380
2386
|
data.status = "rejected";
|
|
2381
2387
|
data.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createDocument,
|
|
3
3
|
ensureTaskDir,
|
|
4
|
+
extractPlanSummary,
|
|
4
5
|
generateTaskId,
|
|
5
6
|
getOutputPath,
|
|
6
7
|
getTaskDir,
|
|
@@ -12,10 +13,11 @@ import {
|
|
|
12
13
|
saveDocument,
|
|
13
14
|
saveTaskMetadata,
|
|
14
15
|
updateDocumentStatus
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-NOS3N4GT.js";
|
|
16
17
|
export {
|
|
17
18
|
createDocument,
|
|
18
19
|
ensureTaskDir,
|
|
20
|
+
extractPlanSummary,
|
|
19
21
|
generateTaskId,
|
|
20
22
|
getOutputPath,
|
|
21
23
|
getTaskDir,
|