@agentic-coding-framework/orchestrator-core 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto.d.ts +8 -0
- package/dist/auto.d.ts.map +1 -1
- package/dist/auto.js +56 -0
- package/dist/auto.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -1
- package/dist/dispatch.d.ts +67 -0
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +425 -2
- package/dist/dispatch.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/state.d.ts +6 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +25 -0
- package/dist/state.js.map +1 -1
- package/package.json +1 -1
package/dist/dispatch.js
CHANGED
|
@@ -25,12 +25,18 @@ exports.queryProjectStatus = queryProjectStatus;
|
|
|
25
25
|
exports.listProjects = listProjects;
|
|
26
26
|
exports.startCustom = startCustom;
|
|
27
27
|
exports.rollback = rollback;
|
|
28
|
+
exports.reopen = reopen;
|
|
29
|
+
exports.review = review;
|
|
30
|
+
exports.triage = triage;
|
|
28
31
|
exports.checkPrerequisites = checkPrerequisites;
|
|
29
32
|
exports.generateChecklist = generateChecklist;
|
|
30
33
|
const fs_1 = require("fs");
|
|
31
34
|
const path_1 = require("path");
|
|
32
35
|
const state_1 = require("./state");
|
|
33
36
|
const rules_1 = require("./rules");
|
|
37
|
+
// ─── Config Constants ────────────────────────────────────────────────────────
|
|
38
|
+
/** After N stories reach "done", automatically suggest/trigger a review session */
|
|
39
|
+
const REVIEW_TRIGGER_THRESHOLD = 3;
|
|
34
40
|
// ─── Main Dispatch Function ──────────────────────────────────────────────────
|
|
35
41
|
/**
|
|
36
42
|
* Core dispatch logic — direct translation of Protocol's dispatch(project).
|
|
@@ -167,13 +173,25 @@ function _dispatchInner(projectRoot, state, dryRun) {
|
|
|
167
173
|
state.files_changed = [];
|
|
168
174
|
// Check if we just reached "done"
|
|
169
175
|
if (state.step === "done") {
|
|
176
|
+
// Clear reopened_from when story completes
|
|
177
|
+
state.reopened_from = null;
|
|
178
|
+
// Feature 3: Check if review should be triggered
|
|
179
|
+
let review_suggested = false;
|
|
180
|
+
const completedCount = countCompletedStories(projectRoot);
|
|
181
|
+
if (completedCount >= REVIEW_TRIGGER_THRESHOLD) {
|
|
182
|
+
review_suggested = true;
|
|
183
|
+
}
|
|
170
184
|
if (!dryRun)
|
|
171
185
|
(0, state_1.writeState)(projectRoot, state);
|
|
172
|
-
|
|
186
|
+
const result = {
|
|
173
187
|
type: "done",
|
|
174
188
|
story: state.story ?? "(no story)",
|
|
175
189
|
summary: `Story ${state.story} completed. All steps passed.`,
|
|
176
190
|
};
|
|
191
|
+
if (review_suggested) {
|
|
192
|
+
result.review_suggested = true;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
177
195
|
}
|
|
178
196
|
// Recurse: the new step might also require human
|
|
179
197
|
const newRule = (0, rules_1.getRule)(state.step);
|
|
@@ -208,7 +226,25 @@ function _dispatchInner(projectRoot, state, dryRun) {
|
|
|
208
226
|
: "No specific reason."),
|
|
209
227
|
};
|
|
210
228
|
}
|
|
211
|
-
|
|
229
|
+
// Feature 1: Escalation logic for post-reopen verify failures
|
|
230
|
+
// If verify fails after reopen with no explicit reason, escalate by rolling back one step deeper
|
|
231
|
+
let target = (0, rules_1.getFailTarget)(state.step, state.reason);
|
|
232
|
+
if (state.step === "verify" &&
|
|
233
|
+
state.reopened_from !== null &&
|
|
234
|
+
state.reason === null // only escalate for pure RED failure (no specific reason)
|
|
235
|
+
) {
|
|
236
|
+
// Find the step before reopened_from
|
|
237
|
+
const earlierStep = getEarlierStep(state.reopened_from);
|
|
238
|
+
if (earlierStep !== null) {
|
|
239
|
+
// Escalate to the earlier step (overriding normal routing)
|
|
240
|
+
target = earlierStep;
|
|
241
|
+
if (!dryRun) {
|
|
242
|
+
(0, state_1.appendLog)(projectRoot, "INFO", "dispatch", `Post-reopen verify failure escalation: ${state.step} → ${earlierStep} (was reopened at ${state.reopened_from})`);
|
|
243
|
+
}
|
|
244
|
+
// Clear reopened_from so we only escalate once
|
|
245
|
+
state.reopened_from = null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
212
248
|
if (target !== state.step) {
|
|
213
249
|
// Route to different step
|
|
214
250
|
state.step = target;
|
|
@@ -1103,6 +1139,393 @@ function rollback(projectRoot, targetStep, options = {}) {
|
|
|
1103
1139
|
message: `Rolled back to "${targetStep}" (attempt 1, status: pending)`,
|
|
1104
1140
|
};
|
|
1105
1141
|
}
|
|
1142
|
+
// ─── Helper Functions for Features ────────────────────────────────────────────
|
|
1143
|
+
/**
|
|
1144
|
+
* Count completed stories since last review by reading .ai/history.md
|
|
1145
|
+
* and matching entries with reopen pattern.
|
|
1146
|
+
*/
|
|
1147
|
+
function countCompletedStories(projectRoot) {
|
|
1148
|
+
try {
|
|
1149
|
+
const historyPath = (0, path_1.join)(projectRoot, ".ai", "history.md");
|
|
1150
|
+
if (!(0, fs_1.existsSync)(historyPath))
|
|
1151
|
+
return 0;
|
|
1152
|
+
const content = (0, fs_1.readFileSync)(historyPath, "utf-8");
|
|
1153
|
+
// Count lines that match "### Reopen" pattern (each represents a completed story that was reopened)
|
|
1154
|
+
// Plus we need to count stories that completed naturally
|
|
1155
|
+
// For now, count any story-related lines in history
|
|
1156
|
+
const matches = content.match(/### Reopen/g) || [];
|
|
1157
|
+
return matches.length;
|
|
1158
|
+
}
|
|
1159
|
+
catch {
|
|
1160
|
+
return 0;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Find the step that comes before a given step in the sequence.
|
|
1165
|
+
* Returns null if the step is the first one (no earlier step).
|
|
1166
|
+
*/
|
|
1167
|
+
function getEarlierStep(step) {
|
|
1168
|
+
const sequence = (0, rules_1.getStepSequence)();
|
|
1169
|
+
const index = sequence.indexOf(step);
|
|
1170
|
+
if (index <= 0)
|
|
1171
|
+
return null;
|
|
1172
|
+
return sequence[index - 1];
|
|
1173
|
+
}
|
|
1174
|
+
// ─── Reopen ──────────────────────────────────────────────────────────────────
|
|
1175
|
+
/**
|
|
1176
|
+
* Reopen a completed story at a specified step.
|
|
1177
|
+
*
|
|
1178
|
+
* Unlike rollback() which moves backwards within an active story,
|
|
1179
|
+
* reopen() specifically targets stories that have reached step "done".
|
|
1180
|
+
* It resets state to the target step so the pipeline can re-execute from there.
|
|
1181
|
+
*
|
|
1182
|
+
* Use case: Review Session or triage found issues in a completed US —
|
|
1183
|
+
* human decides to reopen it at a specific step (e.g., "impl", "verify").
|
|
1184
|
+
*
|
|
1185
|
+
* Guards:
|
|
1186
|
+
* - Story must be in "done" state (use rollback for active stories)
|
|
1187
|
+
* - Target step must be a valid pipeline step
|
|
1188
|
+
* - Cannot reopen to "done" (that's a no-op)
|
|
1189
|
+
*
|
|
1190
|
+
* [v0.8.0] FB-009: Review → Triage → Re-entry
|
|
1191
|
+
*/
|
|
1192
|
+
function reopen(projectRoot, targetStep, options = {}) {
|
|
1193
|
+
let state;
|
|
1194
|
+
try {
|
|
1195
|
+
state = (0, state_1.readState)(projectRoot);
|
|
1196
|
+
}
|
|
1197
|
+
catch (err) {
|
|
1198
|
+
return { type: "error", code: "STATE_NOT_FOUND", message: err.message, recoverable: false };
|
|
1199
|
+
}
|
|
1200
|
+
// Guard: story must be completed
|
|
1201
|
+
if (state.step !== "done") {
|
|
1202
|
+
(0, state_1.appendLog)(projectRoot, "ERROR", "reopen", `NOT_DONE: current step is "${state.step}", not "done". Use rollback instead.`);
|
|
1203
|
+
return {
|
|
1204
|
+
type: "error",
|
|
1205
|
+
code: "NOT_DONE",
|
|
1206
|
+
message: `Cannot reopen: story is at step "${state.step}", not "done". Use "rollback" for active stories.`,
|
|
1207
|
+
recoverable: false,
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
// Cannot reopen to "done" (check before sequence validation since "done" isn't in sequence)
|
|
1211
|
+
if (targetStep === "done") {
|
|
1212
|
+
return {
|
|
1213
|
+
type: "error",
|
|
1214
|
+
code: "REOPEN_TO_DONE",
|
|
1215
|
+
message: `Cannot reopen to "done" — story is already done.`,
|
|
1216
|
+
recoverable: false,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
// Validate target step
|
|
1220
|
+
const sequence = (0, rules_1.getStepSequence)();
|
|
1221
|
+
const targetIndex = targetStep === "bootstrap" ? -1 : sequence.indexOf(targetStep);
|
|
1222
|
+
if (targetStep !== "bootstrap" && targetIndex === -1) {
|
|
1223
|
+
(0, state_1.appendLog)(projectRoot, "ERROR", "reopen", `INVALID_TARGET: "${targetStep}"`);
|
|
1224
|
+
return {
|
|
1225
|
+
type: "error",
|
|
1226
|
+
code: "INVALID_TARGET",
|
|
1227
|
+
message: `Invalid reopen target "${targetStep}". Valid steps: bootstrap, ${sequence.join(", ")}`,
|
|
1228
|
+
recoverable: false,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
// Reset state to target step
|
|
1232
|
+
const previousStep = state.step; // "done"
|
|
1233
|
+
const rule = targetStep === "bootstrap"
|
|
1234
|
+
? (0, rules_1.getRule)("bootstrap")
|
|
1235
|
+
: (0, rules_1.getRule)(targetStep);
|
|
1236
|
+
state.step = targetStep;
|
|
1237
|
+
state.status = "pending";
|
|
1238
|
+
state.attempt = 1;
|
|
1239
|
+
state.max_attempts = rule.max_attempts;
|
|
1240
|
+
state.timeout_min = rule.timeout_min;
|
|
1241
|
+
state.reason = null;
|
|
1242
|
+
state.last_error = null;
|
|
1243
|
+
state.files_changed = [];
|
|
1244
|
+
state.dispatched_at = null;
|
|
1245
|
+
state.completed_at = null;
|
|
1246
|
+
state.tests = null;
|
|
1247
|
+
state.failing_tests = [];
|
|
1248
|
+
state.lint_pass = null;
|
|
1249
|
+
state.human_note = options.humanNote ?? null;
|
|
1250
|
+
state.reopened_from = targetStep; // Feature 1: Track reopen target for escalation
|
|
1251
|
+
(0, state_1.writeState)(projectRoot, state);
|
|
1252
|
+
(0, state_1.appendLog)(projectRoot, "INFO", "reopen", `Reopened story "${state.story}" from "${previousStep}" to "${targetStep}"${options.humanNote ? ` note: "${options.humanNote}"` : ""}`);
|
|
1253
|
+
// Feature 2: Append entry to .ai/history.md
|
|
1254
|
+
const isoTimestamp = new Date().toISOString();
|
|
1255
|
+
const historyEntry = `### Reopen — ${state.story} → ${targetStep}
|
|
1256
|
+
- **Date**: ${isoTimestamp}
|
|
1257
|
+
- **From**: ${previousStep} → ${targetStep}
|
|
1258
|
+
- **Note**: ${options.humanNote ?? "N/A"}`;
|
|
1259
|
+
(0, state_1.appendHistory)(projectRoot, historyEntry);
|
|
1260
|
+
return {
|
|
1261
|
+
type: "ok",
|
|
1262
|
+
state,
|
|
1263
|
+
message: `Reopened story "${state.story}" at step "${targetStep}" (attempt 1, status: pending)`,
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
// ─── Review (On-Demand Review Session) ───────────────────────────────────────
|
|
1267
|
+
/**
|
|
1268
|
+
* Generate a Review Session prompt for the current project.
|
|
1269
|
+
*
|
|
1270
|
+
* This is a STATELESS operation — it reads project state but does NOT
|
|
1271
|
+
* mutate STATE.json. The caller (CC executor) receives the prompt and
|
|
1272
|
+
* runs the review; results are acted on by the human (reopen, new US, etc.).
|
|
1273
|
+
*
|
|
1274
|
+
* Works on non-ACF projects too (detectFramework level 0): generates a
|
|
1275
|
+
* lighter review prompt based on whatever files exist.
|
|
1276
|
+
*
|
|
1277
|
+
* Review Session checks (from Lifecycle v0.9):
|
|
1278
|
+
* 1. Code Review — diff quality, naming, duplication
|
|
1279
|
+
* 2. Spec-Code Coherence — BDD ↔ tests ↔ impl alignment
|
|
1280
|
+
* 3. Regression — all existing tests still pass
|
|
1281
|
+
* 4. Security Scan — no hardcoded secrets, .gitignore coverage
|
|
1282
|
+
* 5. Memory Audit — PROJECT_MEMORY accuracy
|
|
1283
|
+
*
|
|
1284
|
+
* [v0.8.0] FB-009: Review → Triage → Re-entry
|
|
1285
|
+
*/
|
|
1286
|
+
function review(projectRoot) {
|
|
1287
|
+
const framework = detectFramework(projectRoot);
|
|
1288
|
+
const prompt = buildReviewPrompt(projectRoot, framework.level);
|
|
1289
|
+
return {
|
|
1290
|
+
type: "review_prompt",
|
|
1291
|
+
prompt,
|
|
1292
|
+
fw_lv: framework.level,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Build the review session prompt based on framework adoption level.
|
|
1297
|
+
*/
|
|
1298
|
+
function buildReviewPrompt(projectRoot, fwLevel) {
|
|
1299
|
+
const lines = [];
|
|
1300
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1301
|
+
lines.push(" ON-DEMAND REVIEW SESSION");
|
|
1302
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1303
|
+
lines.push("");
|
|
1304
|
+
if (fwLevel >= 2) {
|
|
1305
|
+
// Full ACF project — rich review
|
|
1306
|
+
let state = null;
|
|
1307
|
+
try {
|
|
1308
|
+
state = (0, state_1.readState)(projectRoot);
|
|
1309
|
+
}
|
|
1310
|
+
catch { /* no state */ }
|
|
1311
|
+
lines.push(`Project: ${state?.project ?? "(unknown)"}`);
|
|
1312
|
+
lines.push(`Story: ${state?.story ?? "(no active story)"}`);
|
|
1313
|
+
lines.push(`Current Step: ${state?.step ?? "(unknown)"}`);
|
|
1314
|
+
lines.push("");
|
|
1315
|
+
lines.push("## Review Checklist");
|
|
1316
|
+
lines.push("");
|
|
1317
|
+
lines.push("### 1. Code Review");
|
|
1318
|
+
lines.push("- Check recent changes for naming consistency, duplication, dead code");
|
|
1319
|
+
lines.push("- Verify diff-only discipline: only files related to the story should be modified");
|
|
1320
|
+
lines.push("");
|
|
1321
|
+
lines.push("### 2. Spec-Code Coherence");
|
|
1322
|
+
lines.push("- Read BDD scenarios in docs/bdd/ and verify each has a corresponding test");
|
|
1323
|
+
lines.push("- Read SDD Delta in docs/deltas/ and verify implementation matches");
|
|
1324
|
+
lines.push("- Check that API contracts in docs/api/ are synchronized");
|
|
1325
|
+
lines.push("");
|
|
1326
|
+
lines.push("### 3. Regression");
|
|
1327
|
+
lines.push("- Run the project's test suite and confirm all tests pass");
|
|
1328
|
+
lines.push("- If tests fail, record them in ISSUES section of PROJECT_MEMORY.md");
|
|
1329
|
+
lines.push("");
|
|
1330
|
+
lines.push("### 4. Security Scan");
|
|
1331
|
+
lines.push("- grep for common secret patterns: password=, apikey=, token=, BEGIN RSA PRIVATE KEY");
|
|
1332
|
+
lines.push("- Verify .gitignore covers: .env, *.key, credentials.json, *.pem");
|
|
1333
|
+
lines.push("- Confirm test fixtures use mock/fake values, not real credentials");
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
lines.push("### 5. Memory Audit");
|
|
1336
|
+
lines.push("- Read PROJECT_MEMORY.md and verify NOW/TESTS/NEXT/ISSUES sections are accurate");
|
|
1337
|
+
lines.push("- Check .ai/history.md for completeness");
|
|
1338
|
+
lines.push("- Verify HANDOFF.md reflects the latest session state");
|
|
1339
|
+
}
|
|
1340
|
+
else if (fwLevel === 1) {
|
|
1341
|
+
// Partial ACF — moderate review
|
|
1342
|
+
lines.push("## Review Checklist (Partial ACF Project)");
|
|
1343
|
+
lines.push("");
|
|
1344
|
+
lines.push("### 1. Code Review");
|
|
1345
|
+
lines.push("- Check recent changes for quality, naming, duplication");
|
|
1346
|
+
lines.push("");
|
|
1347
|
+
lines.push("### 2. Spec Coherence");
|
|
1348
|
+
lines.push("- If BDD/SDD docs exist, verify alignment with code");
|
|
1349
|
+
lines.push("");
|
|
1350
|
+
lines.push("### 3. Regression");
|
|
1351
|
+
lines.push("- Run available test suite and confirm all tests pass");
|
|
1352
|
+
lines.push("");
|
|
1353
|
+
lines.push("### 4. Security Scan");
|
|
1354
|
+
lines.push("- grep for hardcoded secrets (password=, apikey=, token=)");
|
|
1355
|
+
lines.push("- Verify .gitignore covers sensitive file patterns");
|
|
1356
|
+
lines.push("");
|
|
1357
|
+
lines.push("### 5. Memory Check");
|
|
1358
|
+
lines.push("- If PROJECT_MEMORY.md exists, verify it is up to date");
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
// Non-ACF project — lightweight review
|
|
1362
|
+
lines.push("## Review Checklist (Non-ACF Project)");
|
|
1363
|
+
lines.push("");
|
|
1364
|
+
lines.push("### 1. Code Review");
|
|
1365
|
+
lines.push("- Review recent git changes (git log + git diff) for quality");
|
|
1366
|
+
lines.push("- Check for dead code, duplication, naming issues");
|
|
1367
|
+
lines.push("");
|
|
1368
|
+
lines.push("### 2. Test Check");
|
|
1369
|
+
lines.push("- Locate and run the project's test suite (if any)");
|
|
1370
|
+
lines.push("- Report test results");
|
|
1371
|
+
lines.push("");
|
|
1372
|
+
lines.push("### 3. Security Scan");
|
|
1373
|
+
lines.push("- grep for hardcoded secrets (password=, apikey=, token=)");
|
|
1374
|
+
lines.push("- Check .gitignore for sensitive patterns");
|
|
1375
|
+
}
|
|
1376
|
+
lines.push("");
|
|
1377
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1378
|
+
lines.push(" OUTPUT FORMAT");
|
|
1379
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1380
|
+
lines.push("");
|
|
1381
|
+
lines.push("For each check, report:");
|
|
1382
|
+
lines.push(" PASS — <brief note>");
|
|
1383
|
+
lines.push(" WARN — <issue description>");
|
|
1384
|
+
lines.push(" FAIL — <issue description + suggested action>");
|
|
1385
|
+
lines.push("");
|
|
1386
|
+
lines.push("At the end, provide a summary with recommended actions:");
|
|
1387
|
+
lines.push(" - REOPEN <US-XXX> at <step> — if existing story needs rework");
|
|
1388
|
+
lines.push(" - NEW US — <brief description> — if a new story is needed");
|
|
1389
|
+
lines.push(" - ISSUE — <description> — record in PROJECT_MEMORY.md ISSUES");
|
|
1390
|
+
lines.push(" - ALL CLEAR — no issues found");
|
|
1391
|
+
lines.push("");
|
|
1392
|
+
return lines.join("\n");
|
|
1393
|
+
}
|
|
1394
|
+
// ─── Triage (ISSUES → Actionable Plan) ──────────────────────────────────────
|
|
1395
|
+
/**
|
|
1396
|
+
* Read unfixed ISSUES from PROJECT_MEMORY.md and generate a triage prompt.
|
|
1397
|
+
*
|
|
1398
|
+
* This is a STATELESS operation — reads files but does NOT mutate STATE.json.
|
|
1399
|
+
* The triage prompt asks the executor (CC) to classify each issue and
|
|
1400
|
+
* recommend actions (reopen US, create new US, or dismiss).
|
|
1401
|
+
*
|
|
1402
|
+
* Requires PROJECT_MEMORY.md to exist with an ISSUES section.
|
|
1403
|
+
*
|
|
1404
|
+
* [v0.8.0] FB-009: Review → Triage → Re-entry
|
|
1405
|
+
*/
|
|
1406
|
+
function triage(projectRoot) {
|
|
1407
|
+
const framework = detectFramework(projectRoot);
|
|
1408
|
+
// Read PROJECT_MEMORY.md
|
|
1409
|
+
const memoryPath = (0, path_1.join)(projectRoot, "PROJECT_MEMORY.md");
|
|
1410
|
+
if (!(0, fs_1.existsSync)(memoryPath)) {
|
|
1411
|
+
return {
|
|
1412
|
+
type: "error",
|
|
1413
|
+
code: "NO_MEMORY",
|
|
1414
|
+
message: "PROJECT_MEMORY.md not found. Cannot triage without ISSUES section.",
|
|
1415
|
+
recoverable: false,
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
const memory = (0, fs_1.readFileSync)(memoryPath, "utf-8");
|
|
1419
|
+
const issues = parseIssuesFromMemory(memory);
|
|
1420
|
+
if (issues.length === 0) {
|
|
1421
|
+
return {
|
|
1422
|
+
type: "error",
|
|
1423
|
+
code: "NO_ISSUES",
|
|
1424
|
+
message: "No unfixed ISSUES found in PROJECT_MEMORY.md. Nothing to triage.",
|
|
1425
|
+
recoverable: false,
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
const prompt = buildTriagePrompt(projectRoot, issues, framework.level);
|
|
1429
|
+
return {
|
|
1430
|
+
type: "triage_prompt",
|
|
1431
|
+
prompt,
|
|
1432
|
+
issues,
|
|
1433
|
+
fw_lv: framework.level,
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Parse ISSUES lines from PROJECT_MEMORY.md.
|
|
1438
|
+
* Looks for lines starting with "- [ ]" under a section containing "ISSUES".
|
|
1439
|
+
* Filters out already-checked items "- [x]".
|
|
1440
|
+
*/
|
|
1441
|
+
function parseIssuesFromMemory(memory) {
|
|
1442
|
+
const lines = memory.split("\n");
|
|
1443
|
+
let inIssuesSection = false;
|
|
1444
|
+
const issues = [];
|
|
1445
|
+
for (const line of lines) {
|
|
1446
|
+
// Detect ISSUES section header (## ISSUES, ### ISSUES, or just ISSUES:)
|
|
1447
|
+
if (/^#{1,4}\s*ISSUES/i.test(line) || /^ISSUES\s*:/i.test(line)) {
|
|
1448
|
+
inIssuesSection = true;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
// Exit ISSUES section on next header
|
|
1452
|
+
if (inIssuesSection && /^#{1,4}\s/.test(line) && !/ISSUES/i.test(line)) {
|
|
1453
|
+
inIssuesSection = false;
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
// Collect unchecked items
|
|
1457
|
+
if (inIssuesSection && /^-\s*\[\s*\]/.test(line)) {
|
|
1458
|
+
issues.push(line.replace(/^-\s*\[\s*\]\s*/, "").trim());
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
return issues;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Build the triage prompt with parsed issues.
|
|
1465
|
+
*/
|
|
1466
|
+
function buildTriagePrompt(projectRoot, issues, fwLevel) {
|
|
1467
|
+
const lines = [];
|
|
1468
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1469
|
+
lines.push(" TRIAGE SESSION");
|
|
1470
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1471
|
+
lines.push("");
|
|
1472
|
+
let state = null;
|
|
1473
|
+
try {
|
|
1474
|
+
state = (0, state_1.readState)(projectRoot);
|
|
1475
|
+
}
|
|
1476
|
+
catch { /* no state */ }
|
|
1477
|
+
if (state) {
|
|
1478
|
+
lines.push(`Project: ${state.project ?? "(unknown)"}`);
|
|
1479
|
+
lines.push(`Current Story: ${state.story ?? "(none)"}`);
|
|
1480
|
+
lines.push(`Current Step: ${state.step}`);
|
|
1481
|
+
lines.push("");
|
|
1482
|
+
}
|
|
1483
|
+
lines.push(`## Unfixed ISSUES (${issues.length})`);
|
|
1484
|
+
lines.push("");
|
|
1485
|
+
for (let i = 0; i < issues.length; i++) {
|
|
1486
|
+
lines.push(` ${i + 1}. ${issues[i]}`);
|
|
1487
|
+
}
|
|
1488
|
+
lines.push("");
|
|
1489
|
+
lines.push("## Triage Instructions");
|
|
1490
|
+
lines.push("");
|
|
1491
|
+
lines.push("For each issue above, analyze and classify:");
|
|
1492
|
+
lines.push("");
|
|
1493
|
+
lines.push(" A. REOPEN <US-XXX> at <step>");
|
|
1494
|
+
lines.push(" → Issue belongs to an existing story; reopen it at the appropriate step.");
|
|
1495
|
+
lines.push(" → The human will run: orchestrator reopen <project> <step>");
|
|
1496
|
+
lines.push("");
|
|
1497
|
+
lines.push(" B. NEW US: <title>");
|
|
1498
|
+
lines.push(" → Issue requires a new User Story. Write a 1-2 sentence description.");
|
|
1499
|
+
lines.push(" → The human will create the US and run: orchestrator start-story <project> <US-XXX>");
|
|
1500
|
+
lines.push("");
|
|
1501
|
+
lines.push(" C. DISMISS: <reason>");
|
|
1502
|
+
lines.push(" → Issue is resolved, duplicate, or no longer relevant.");
|
|
1503
|
+
lines.push(" → The human will mark it [x] in PROJECT_MEMORY.md.");
|
|
1504
|
+
lines.push("");
|
|
1505
|
+
if (fwLevel >= 2) {
|
|
1506
|
+
lines.push("## Context Files");
|
|
1507
|
+
lines.push("Read these files to inform your triage decisions:");
|
|
1508
|
+
lines.push(" - PROJECT_MEMORY.md (full context: NOW, TESTS, NEXT, ISSUES)");
|
|
1509
|
+
lines.push(" - .ai/history.md (completed work log)");
|
|
1510
|
+
lines.push(" - docs/bdd/ (BDD scenarios for existing stories)");
|
|
1511
|
+
lines.push(" - docs/deltas/ (SDD deltas for existing stories)");
|
|
1512
|
+
lines.push("");
|
|
1513
|
+
}
|
|
1514
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1515
|
+
lines.push(" OUTPUT FORMAT");
|
|
1516
|
+
lines.push("═══════════════════════════════════════════════════════════════");
|
|
1517
|
+
lines.push("");
|
|
1518
|
+
lines.push("Produce a triage plan as a numbered list matching the issues above:");
|
|
1519
|
+
lines.push("");
|
|
1520
|
+
lines.push(" 1. [A] REOPEN US-001 at impl — <reason>");
|
|
1521
|
+
lines.push(" 2. [B] NEW US: \"Add input validation for email field\" — <reason>");
|
|
1522
|
+
lines.push(" 3. [C] DISMISS — resolved in commit abc123");
|
|
1523
|
+
lines.push("");
|
|
1524
|
+
lines.push("End with a SUMMARY: X to reopen, Y new US, Z dismissed.");
|
|
1525
|
+
lines.push("This plan is HUMAN-GATED — no actions will be taken automatically.");
|
|
1526
|
+
lines.push("");
|
|
1527
|
+
return lines.join("\n");
|
|
1528
|
+
}
|
|
1106
1529
|
/**
|
|
1107
1530
|
* Check if prerequisite files (claude_reads) exist before dispatching.
|
|
1108
1531
|
* Only checks concrete paths (skips wildcards like *.go, **\/*.ts).
|