@papi-ai/server 0.7.2 → 0.7.4-alpha.1
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 +198 -40
- package/dist/prompts.js +32 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1427,8 +1427,8 @@ var init_dist2 = __esm({
|
|
|
1427
1427
|
bug: 1,
|
|
1428
1428
|
task: 1,
|
|
1429
1429
|
research: 2,
|
|
1430
|
-
|
|
1431
|
-
|
|
1430
|
+
spike: 2,
|
|
1431
|
+
idea: 3
|
|
1432
1432
|
};
|
|
1433
1433
|
VALID_EFFORT_SIZES = /* @__PURE__ */ new Set(["XS", "S", "M", "L", "XL"]);
|
|
1434
1434
|
SECTION_HEADERS = [
|
|
@@ -4467,6 +4467,7 @@ function rowToTask(row) {
|
|
|
4467
4467
|
if (row.maturity != null) task.maturity = row.maturity;
|
|
4468
4468
|
if (row.stage_id != null) task.stageId = row.stage_id;
|
|
4469
4469
|
if (row.doc_ref != null) task.docRef = row.doc_ref;
|
|
4470
|
+
if (row.source != null) task.source = row.source;
|
|
4470
4471
|
return task;
|
|
4471
4472
|
}
|
|
4472
4473
|
function rowToBuildReport(row) {
|
|
@@ -6710,7 +6711,7 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6710
6711
|
project_id, display_id, title, status, priority, complexity,
|
|
6711
6712
|
module, epic, phase, owner, reviewed, cycle, created_cycle,
|
|
6712
6713
|
why, depends_on, notes, closure_reason, state_history,
|
|
6713
|
-
build_handoff, build_report, task_type, maturity, stage_id, doc_ref
|
|
6714
|
+
build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source
|
|
6714
6715
|
) VALUES (
|
|
6715
6716
|
${this.projectId}, ${displayId}, ${task.title}, ${task.status}, ${task.priority},
|
|
6716
6717
|
${normaliseComplexity(task.complexity)}, ${task.module}, ${task.epic ?? null}, ${task.phase}, ${task.owner},
|
|
@@ -6723,7 +6724,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6723
6724
|
${task.taskType ?? null},
|
|
6724
6725
|
${task.maturity ?? null},
|
|
6725
6726
|
${task.stageId ?? null},
|
|
6726
|
-
${task.docRef ?? null}
|
|
6727
|
+
${task.docRef ?? null},
|
|
6728
|
+
${task.source ?? null}
|
|
6727
6729
|
)
|
|
6728
6730
|
RETURNING *
|
|
6729
6731
|
`;
|
|
@@ -6754,6 +6756,7 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6754
6756
|
if (updates.maturity !== void 0) columnMap["maturity"] = updates.maturity;
|
|
6755
6757
|
if (updates.stageId !== void 0) columnMap["stage_id"] = updates.stageId;
|
|
6756
6758
|
if (updates.docRef !== void 0) columnMap["doc_ref"] = updates.docRef;
|
|
6759
|
+
if (updates.source !== void 0) columnMap["source"] = updates.source;
|
|
6757
6760
|
const keys = Object.keys(columnMap);
|
|
6758
6761
|
if (keys.length === 0) return;
|
|
6759
6762
|
await this.sql`
|
|
@@ -8518,8 +8521,10 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
8518
8521
|
return this.invoke("submitBugReport", [report]);
|
|
8519
8522
|
}
|
|
8520
8523
|
// --- Atomic plan write-back ---
|
|
8521
|
-
planWriteBack(payload) {
|
|
8522
|
-
|
|
8524
|
+
async planWriteBack(payload) {
|
|
8525
|
+
const raw = await this.invoke("planWriteBack", [payload]);
|
|
8526
|
+
const map = new Map(Object.entries(raw.newTaskIdMap ?? {}));
|
|
8527
|
+
return { ...raw, newTaskIdMap: map };
|
|
8523
8528
|
}
|
|
8524
8529
|
};
|
|
8525
8530
|
}
|
|
@@ -9998,10 +10003,12 @@ This is Cycle 0 \u2014 the first planning cycle for a brand-new project.
|
|
|
9998
10003
|
|
|
9999
10004
|
2. **North Star** \u2014 Propose a one-sentence North Star statement, a success metric, and a key metric.
|
|
10000
10005
|
|
|
10001
|
-
3. **Initial Board** \u2014 Generate 3-5 tasks:
|
|
10002
|
-
-
|
|
10003
|
-
- Task
|
|
10004
|
-
-
|
|
10006
|
+
3. **Initial Board** \u2014 Generate 3-5 tasks based on the project's actual tech stack and goals:
|
|
10007
|
+
- Infer the project type from the brief/description (CLI, web app, mobile app, API, library, game, data pipeline, etc.)
|
|
10008
|
+
- Task 1: Project-appropriate setup (toolchain, dependencies, config \u2014 NOT "scaffolding" if the project already has code)
|
|
10009
|
+
- Task 2: Core functionality that proves the concept works (data model, main loop, core algorithm \u2014 whatever the project needs first)
|
|
10010
|
+
- Tasks 3-5: First deliverables that demonstrate value, broken into small steps appropriate for the project type
|
|
10011
|
+
- Do NOT assume web-app patterns (routes, pages, components) unless the brief explicitly describes a web application
|
|
10005
10012
|
- All tasks: status Backlog, priority P1-P2, reviewed true, phase "Phase 1"
|
|
10006
10013
|
|
|
10007
10014
|
4. **First Active Decision** \u2014 If the description implies a clear architectural choice, create AD-1 with Confidence: MEDIUM. If no clear choice, skip this.
|
|
@@ -10161,11 +10168,25 @@ var PLAN_FRAGMENT_RESEARCH = `
|
|
|
10161
10168
|
var PLAN_FRAGMENT_BUG = `
|
|
10162
10169
|
**Bug task detection:** When a task's task type is "bug" or the title starts with "Bug:" or "Fix:", apply these rules:
|
|
10163
10170
|
- **Auto-P1:** If the task's current priority is P2 or lower, upgrade it to "P1 High" via a boardCorrections entry in Part 2. Note the upgrade in Part 1 analysis.
|
|
10164
|
-
-
|
|
10165
|
-
|
|
10171
|
+
- Replace the standard SCOPE (DO THIS) section with bug-specific sections:
|
|
10172
|
+
- **REPRODUCE:** Exact steps to reproduce the bug before touching any code. If the task notes describe the symptoms, include them. If not, the first build step is "confirm the bug reproduces."
|
|
10173
|
+
- **ROOT CAUSE:** One-sentence hypothesis for the root cause (what is wrong, not what the user sees). The builder must confirm or correct this before implementing a fix.
|
|
10174
|
+
- **MINIMAL FIX:** The smallest code change that resolves the root cause. "Bug fix \u2014 minimal blast radius. Change only what is necessary. Do not refactor surrounding code or expand scope."
|
|
10175
|
+
- **REGRESSION TEST:** How to verify the bug is fixed and won't silently recur. Describe the test (manual or automated) \u2014 the builder must confirm this passes.
|
|
10176
|
+
- Add to ACCEPTANCE CRITERIA: "[ ] Fix is targeted \u2014 no unrelated code changed" and "[ ] Regression test confirms the bug no longer reproduces"`;
|
|
10166
10177
|
var PLAN_FRAGMENT_IDEA = `
|
|
10167
10178
|
**Idea task detection:** When a task's task type is "idea", add a scope clarification note to the BUILD HANDOFF:
|
|
10168
10179
|
- Add to SCOPE (DO THIS): "This task originated as an idea. Confirm the exact deliverable before implementing \u2014 check task notes and any referenced docs for intent. If scope is unclear, flag it in the build report surprises."`;
|
|
10180
|
+
var PLAN_FRAGMENT_SPIKE = `
|
|
10181
|
+
**Spike task detection:** When a task's task type is "spike" or the title starts with "Spike:", apply these rules:
|
|
10182
|
+
- Spikes are time-boxed investigations, not implementation tasks. The deliverable is a FINDING, not code.
|
|
10183
|
+
- Replace the standard BUILD HANDOFF sections with spike-specific sections:
|
|
10184
|
+
- **TIME-BOX:** Maximum effort for this spike (e.g. "Stop after S effort / ~2 hours"). If the question isn't answered by then, the spike is done \u2014 report what you found.
|
|
10185
|
+
- **GOAL:** The specific question this spike answers (one sentence, phrased as a question).
|
|
10186
|
+
- **OUTPUT:** What the spike produces: a written finding (doc in docs/research/ or notes in the build report), optionally a proof-of-concept if code is needed.
|
|
10187
|
+
- **DONE CONDITION:** "Question answered OR time-box hit, whichever comes first."
|
|
10188
|
+
- Keep SCOPE BOUNDARY, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
10189
|
+
- Spikes should be estimated conservatively: XS or S. If a spike needs M+ effort, it's not a spike \u2014 reclassify as a research task.`;
|
|
10169
10190
|
var PLAN_FRAGMENT_UI = `
|
|
10170
10191
|
**UI/visual task detection:** When a task's title or notes contain keywords suggesting frontend visual work (e.g. "visual", "design", "UI", "styling", "refresh", "frontend", "landing page", "hero", "carousel", "theme", "layout", "cockpit", "dashboard", "page"), apply these handoff additions:
|
|
10171
10192
|
- Add to SCOPE: "Read \`.impeccable.md\` for brand palette, design principles, and audience context before writing any code. Use the \`frontend-design\` skill for implementation."
|
|
@@ -10274,6 +10295,7 @@ Standard planning cycle with full board review.
|
|
|
10274
10295
|
if (flags.hasResearchTasks) parts.push(PLAN_FRAGMENT_RESEARCH);
|
|
10275
10296
|
if (flags.hasBugTasks) parts.push(PLAN_FRAGMENT_BUG);
|
|
10276
10297
|
if (flags.hasIdeaTasks) parts.push(PLAN_FRAGMENT_IDEA);
|
|
10298
|
+
if (flags.hasSpikeTasks) parts.push(PLAN_FRAGMENT_SPIKE);
|
|
10277
10299
|
if (flags.hasUITasks) parts.push(PLAN_FRAGMENT_UI);
|
|
10278
10300
|
parts.push(`
|
|
10279
10301
|
11. **New Tasks (max 3 per cycle)** \u2014 Actively mine the Recent Build Reports for task candidates. For each report, check:
|
|
@@ -10592,6 +10614,15 @@ You MUST cover these 5 sections. Each is mandatory.
|
|
|
10592
10614
|
${compressionJob}
|
|
10593
10615
|
Note: Hierarchy assessment and structural drift detection are handled within section 5 (AD & Hierarchy Housekeeping). They do not need their own sections.
|
|
10594
10616
|
|
|
10617
|
+
## DETECT STRATEGIC DECISIONS
|
|
10618
|
+
|
|
10619
|
+
Watch for direction changes, architecture shifts, deprioritisation with reasoning, new principles, or competitive positioning decisions in the project data.
|
|
10620
|
+
|
|
10621
|
+
When detected:
|
|
10622
|
+
1. Flag it in the review: "Strategic direction change detected \u2014 [description]."
|
|
10623
|
+
2. Propose an AD update or new AD in the structured output (Part 2 \`activeDecisions\` array).
|
|
10624
|
+
3. Recommend running \`strategy_change\` if the shift requires immediate action before the next plan.
|
|
10625
|
+
|
|
10595
10626
|
## OUTPUT FORMAT
|
|
10596
10627
|
|
|
10597
10628
|
Your output has TWO parts:
|
|
@@ -11451,21 +11482,24 @@ function detectBoardFlags(tasks) {
|
|
|
11451
11482
|
let hasBugTasks = false;
|
|
11452
11483
|
let hasResearchTasks = false;
|
|
11453
11484
|
let hasIdeaTasks = false;
|
|
11485
|
+
let hasSpikeTasks = false;
|
|
11454
11486
|
let hasUITasks = false;
|
|
11455
11487
|
const uiKeywords = /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i;
|
|
11456
11488
|
for (const t of tasks) {
|
|
11457
11489
|
if (t.taskType === "bug" || /^(Bug:|Fix:)/i.test(t.title)) hasBugTasks = true;
|
|
11458
11490
|
if (t.taskType === "research" || /^Research:/i.test(t.title)) hasResearchTasks = true;
|
|
11459
11491
|
if (t.taskType === "idea") hasIdeaTasks = true;
|
|
11492
|
+
if (t.taskType === "spike" || /^Spike:/i.test(t.title)) hasSpikeTasks = true;
|
|
11460
11493
|
if (uiKeywords.test(t.title) || uiKeywords.test(t.notes ?? "")) hasUITasks = true;
|
|
11461
11494
|
}
|
|
11462
|
-
return { hasBugTasks, hasResearchTasks, hasIdeaTasks, hasUITasks };
|
|
11495
|
+
return { hasBugTasks, hasResearchTasks, hasIdeaTasks, hasSpikeTasks, hasUITasks };
|
|
11463
11496
|
}
|
|
11464
11497
|
function detectBoardFlagsFromText(boardText) {
|
|
11465
11498
|
return {
|
|
11466
11499
|
hasBugTasks: /\b(bug|Bug:|Fix:)\b/i.test(boardText),
|
|
11467
11500
|
hasResearchTasks: /\b(research|Research:)\b/i.test(boardText),
|
|
11468
11501
|
hasIdeaTasks: /\bidea\b/i.test(boardText),
|
|
11502
|
+
hasSpikeTasks: /\b(spike|Spike:)\b/i.test(boardText),
|
|
11469
11503
|
hasUITasks: /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i.test(boardText)
|
|
11470
11504
|
};
|
|
11471
11505
|
}
|
|
@@ -11942,13 +11976,24 @@ ${cleanContent}`;
|
|
|
11942
11976
|
console.error(`[plan-perf] transactionalWriteBack: total=${writeBackMs}ms`);
|
|
11943
11977
|
const verifyWarnings = [];
|
|
11944
11978
|
try {
|
|
11945
|
-
const cycles = await
|
|
11979
|
+
const [cycles, boardTasks] = await Promise.all([
|
|
11980
|
+
adapter2.readCycles(),
|
|
11981
|
+
adapter2.queryBoard({ status: ["In Cycle", "Backlog", "In Progress", "In Review"] })
|
|
11982
|
+
]);
|
|
11946
11983
|
const newCycle = cycles.find((s) => s.number === newCycleNumber);
|
|
11947
11984
|
if (!newCycle) {
|
|
11948
|
-
verifyWarnings.push(`Post-write verification: cycle ${newCycleNumber} entity not found after commit`);
|
|
11985
|
+
verifyWarnings.push(`Post-write verification FAILED: cycle ${newCycleNumber} entity not found after commit \u2014 data may not have persisted`);
|
|
11986
|
+
} else {
|
|
11987
|
+
const expectedHandoffs = data.cycleHandoffs?.length ?? 0;
|
|
11988
|
+
const actualCycleTasks = boardTasks.filter((t) => t.cycle === newCycleNumber).length;
|
|
11989
|
+
if (expectedHandoffs > 0 && actualCycleTasks === 0) {
|
|
11990
|
+
verifyWarnings.push(`Post-write verification FAILED: cycle ${newCycleNumber} exists but has 0 tasks assigned (expected ${expectedHandoffs}) \u2014 task cycle assignment may have failed`);
|
|
11991
|
+
} else if (expectedHandoffs > 0 && actualCycleTasks < expectedHandoffs) {
|
|
11992
|
+
verifyWarnings.push(`Post-write verification WARNING: cycle ${newCycleNumber} has ${actualCycleTasks} tasks but expected ${expectedHandoffs} \u2014 some task assignments may have failed`);
|
|
11993
|
+
}
|
|
11949
11994
|
}
|
|
11950
11995
|
} catch {
|
|
11951
|
-
verifyWarnings.push("Post-write verification: could not read cycles
|
|
11996
|
+
verifyWarnings.push("Post-write verification: could not read cycles/tasks tables");
|
|
11952
11997
|
}
|
|
11953
11998
|
const allWarnings = [...result.warnings, ...verifyWarnings];
|
|
11954
11999
|
const handoffCount = data.cycleHandoffs?.length ?? 0;
|
|
@@ -12035,7 +12080,8 @@ ${cleanContent}`;
|
|
|
12035
12080
|
cycle: newCycleNumber,
|
|
12036
12081
|
createdCycle: newCycleNumber,
|
|
12037
12082
|
why: task.why || "",
|
|
12038
|
-
notes: task.notes || ""
|
|
12083
|
+
notes: task.notes || "",
|
|
12084
|
+
source: "llm"
|
|
12039
12085
|
});
|
|
12040
12086
|
newTaskIdMap.set(`new-${i + 1}`, created.id);
|
|
12041
12087
|
if (adapter2.updateCycleLearningActionRef && task.notes) {
|
|
@@ -14432,6 +14478,18 @@ ${result.userMessage}
|
|
|
14432
14478
|
// src/services/board.ts
|
|
14433
14479
|
var ACTIVE_STATUSES = ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked"];
|
|
14434
14480
|
var PRIORITY_ORDER = ["P0 Critical", "P1 High", "P2 Medium", "P3 Low"];
|
|
14481
|
+
var STATUS_TIER = {
|
|
14482
|
+
"In Progress": 0,
|
|
14483
|
+
"In Review": 1,
|
|
14484
|
+
"Backlog": 2,
|
|
14485
|
+
"In Cycle": 2,
|
|
14486
|
+
"Ready": 2,
|
|
14487
|
+
"Blocked": 3,
|
|
14488
|
+
"Deferred": 4,
|
|
14489
|
+
"Done": 5,
|
|
14490
|
+
"Cancelled": 6,
|
|
14491
|
+
"Archived": 7
|
|
14492
|
+
};
|
|
14435
14493
|
async function viewBoard(adapter2, phaseFilter, options) {
|
|
14436
14494
|
const queryOptions = {};
|
|
14437
14495
|
const phase = options?.phase ?? phaseFilter;
|
|
@@ -14446,9 +14504,16 @@ async function viewBoard(adapter2, phaseFilter, options) {
|
|
|
14446
14504
|
Object.keys(queryOptions).length > 0 ? queryOptions : void 0
|
|
14447
14505
|
);
|
|
14448
14506
|
allTasks.sort((a, b2) => {
|
|
14507
|
+
const aTier = STATUS_TIER[a.status] ?? 4;
|
|
14508
|
+
const bTier = STATUS_TIER[b2.status] ?? 4;
|
|
14509
|
+
if (aTier !== bTier) return aTier - bTier;
|
|
14449
14510
|
const ai = PRIORITY_ORDER.indexOf(a.priority);
|
|
14450
14511
|
const bi = PRIORITY_ORDER.indexOf(b2.priority);
|
|
14451
|
-
|
|
14512
|
+
const priorityDiff = (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
|
|
14513
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
14514
|
+
const aDate = a.createdAt ?? "";
|
|
14515
|
+
const bDate = b2.createdAt ?? "";
|
|
14516
|
+
return bDate.localeCompare(aDate);
|
|
14452
14517
|
});
|
|
14453
14518
|
const total = allTasks.length;
|
|
14454
14519
|
const offset = options?.offset ?? 0;
|
|
@@ -14652,7 +14717,7 @@ function formatBoard(result) {
|
|
|
14652
14717
|
if (result.tasks.length === 0) {
|
|
14653
14718
|
return "No tasks found.";
|
|
14654
14719
|
}
|
|
14655
|
-
const headers = ["Priority", "Task", "Summary", "Status", "Cycle", "Phase", "Module", "Epic", "Effort", "Created"];
|
|
14720
|
+
const headers = ["Priority", "Task", "Summary", "Status", "Cycle", "Phase", "Module", "Epic", "Effort", "Created", "Source"];
|
|
14656
14721
|
const rows = result.tasks.map((t) => [
|
|
14657
14722
|
t.priority,
|
|
14658
14723
|
t.id,
|
|
@@ -14663,7 +14728,8 @@ function formatBoard(result) {
|
|
|
14663
14728
|
t.module ?? "-",
|
|
14664
14729
|
t.epic ?? "-",
|
|
14665
14730
|
t.complexity ?? "-",
|
|
14666
|
-
t.createdAt ?? "-"
|
|
14731
|
+
t.createdAt ?? "-",
|
|
14732
|
+
t.source ?? "-"
|
|
14667
14733
|
]);
|
|
14668
14734
|
const widths = headers.map(
|
|
14669
14735
|
(h, i) => Math.max(h.length, ...rows.map((r) => r[i].length))
|
|
@@ -15941,7 +16007,8 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
15941
16007
|
phase: task.phase || "Phase 1",
|
|
15942
16008
|
owner: "TBD",
|
|
15943
16009
|
reviewed: false,
|
|
15944
|
-
notes: task.notes
|
|
16010
|
+
notes: task.notes,
|
|
16011
|
+
source: "llm"
|
|
15945
16012
|
});
|
|
15946
16013
|
createdTasks++;
|
|
15947
16014
|
}
|
|
@@ -17520,19 +17587,23 @@ ${lines.join("\n")}
|
|
|
17520
17587
|
const VALID_COMPLEXITIES2 = /* @__PURE__ */ new Set(["XS", "Small", "Medium", "Large", "XL"]);
|
|
17521
17588
|
const priority = input.priority && VALID_PRIORITIES2.has(input.priority) ? input.priority : "P2 Medium";
|
|
17522
17589
|
const complexity = input.complexity && VALID_COMPLEXITIES2.has(input.complexity) ? input.complexity : "Small";
|
|
17523
|
-
const
|
|
17524
|
-
bug: "bug",
|
|
17525
|
-
research: "research",
|
|
17526
|
-
feedback: "feedback"
|
|
17527
|
-
};
|
|
17590
|
+
const VALID_TYPES = /* @__PURE__ */ new Set(["task", "bug", "research", "idea", "spike"]);
|
|
17528
17591
|
let taskTitle = input.text;
|
|
17529
17592
|
let taskType = "idea";
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17593
|
+
if (input.type && VALID_TYPES.has(input.type)) {
|
|
17594
|
+
taskType = input.type;
|
|
17595
|
+
} else {
|
|
17596
|
+
const PREFIX_MAP = {
|
|
17597
|
+
bug: "bug",
|
|
17598
|
+
research: "research"
|
|
17599
|
+
};
|
|
17600
|
+
const prefixMatch = input.text.match(/^\[([a-zA-Z]+)\]\s*/);
|
|
17601
|
+
if (prefixMatch) {
|
|
17602
|
+
const key = prefixMatch[1].toLowerCase();
|
|
17603
|
+
if (key in PREFIX_MAP) {
|
|
17604
|
+
taskType = PREFIX_MAP[key];
|
|
17605
|
+
taskTitle = input.text.slice(prefixMatch[0].length);
|
|
17606
|
+
}
|
|
17536
17607
|
}
|
|
17537
17608
|
}
|
|
17538
17609
|
const task = await adapter2.createTask({
|
|
@@ -17551,7 +17622,8 @@ ${lines.join("\n")}
|
|
|
17551
17622
|
notes: input.notes || "",
|
|
17552
17623
|
taskType,
|
|
17553
17624
|
maturity: "raw",
|
|
17554
|
-
docRef: input.docRef
|
|
17625
|
+
docRef: input.docRef,
|
|
17626
|
+
source: "llm"
|
|
17555
17627
|
});
|
|
17556
17628
|
if (input.notes && adapter2.updateCycleLearningActionRef) {
|
|
17557
17629
|
const learningRefs = input.notes.match(/learning:([a-f0-9-]+)/gi);
|
|
@@ -17659,6 +17731,11 @@ var ideaTool = {
|
|
|
17659
17731
|
type: "boolean",
|
|
17660
17732
|
description: "Force creation even if a high-overlap duplicate or already-done task is detected. Default: false."
|
|
17661
17733
|
},
|
|
17734
|
+
type: {
|
|
17735
|
+
type: "string",
|
|
17736
|
+
enum: ["task", "bug", "research", "spike"],
|
|
17737
|
+
description: 'Task type. Defaults to "task". Use "bug" for defects, "research" for investigation tasks, "spike" for time-boxed experiments. The planner uses this to generate type-specific BUILD HANDOFFs.'
|
|
17738
|
+
},
|
|
17662
17739
|
doc_ref: {
|
|
17663
17740
|
type: "string",
|
|
17664
17741
|
description: 'Path to a reference document (e.g. "docs/research/foo.md"). Stored as a structured field \u2014 replaces the fragile "Reference:" line in notes.'
|
|
@@ -17689,7 +17766,8 @@ async function handleIdea(adapter2, config2, args) {
|
|
|
17689
17766
|
notes: rawNotes,
|
|
17690
17767
|
discovery: args.discovery === true,
|
|
17691
17768
|
force: args.force === true,
|
|
17692
|
-
docRef: args.doc_ref?.trim()
|
|
17769
|
+
docRef: args.doc_ref?.trim(),
|
|
17770
|
+
type: args.type
|
|
17693
17771
|
};
|
|
17694
17772
|
const useGit = isGitAvailable() && isGitRepo(config2.projectRoot);
|
|
17695
17773
|
const currentBranch = useGit ? getCurrentBranch(config2.projectRoot) : null;
|
|
@@ -17776,7 +17854,8 @@ async function captureBug(adapter2, input) {
|
|
|
17776
17854
|
createdCycle: health.totalCycles,
|
|
17777
17855
|
notes: input.notes || "",
|
|
17778
17856
|
taskType: "bug",
|
|
17779
|
-
maturity: "investigated"
|
|
17857
|
+
maturity: "investigated",
|
|
17858
|
+
source: "llm"
|
|
17780
17859
|
});
|
|
17781
17860
|
}
|
|
17782
17861
|
|
|
@@ -17947,7 +18026,8 @@ async function recordAdHoc(adapter2, input) {
|
|
|
17947
18026
|
reviewed: true,
|
|
17948
18027
|
createdCycle: cycle,
|
|
17949
18028
|
notes: input.notes ? `[ad-hoc] ${input.notes}` : "[ad-hoc]",
|
|
17950
|
-
taskType: "task"
|
|
18029
|
+
taskType: "task",
|
|
18030
|
+
source: "owner"
|
|
17951
18031
|
});
|
|
17952
18032
|
}
|
|
17953
18033
|
const report = {
|
|
@@ -18461,11 +18541,88 @@ When done, call \`board_reconcile\` again with:
|
|
|
18461
18541
|
- \`mode\`: "retriage-apply"
|
|
18462
18542
|
- \`llm_response\`: your complete output (both parts)
|
|
18463
18543
|
`;
|
|
18544
|
+
async function checkCycleIntegrity(adapter2) {
|
|
18545
|
+
const report = {
|
|
18546
|
+
multiActiveCycles: [],
|
|
18547
|
+
ghostCycles: [],
|
|
18548
|
+
orphanedAssignments: [],
|
|
18549
|
+
autoFixed: []
|
|
18550
|
+
};
|
|
18551
|
+
try {
|
|
18552
|
+
const cycles = await adapter2.readCycles();
|
|
18553
|
+
const allTasks = await adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Done", "Blocked", "Cancelled", "Deferred"] });
|
|
18554
|
+
const activeCycles = cycles.filter((c) => c.status === "active");
|
|
18555
|
+
if (activeCycles.length > 1) {
|
|
18556
|
+
for (const cycle of activeCycles) {
|
|
18557
|
+
const taskCount = allTasks.filter((t) => t.cycle === cycle.number).length;
|
|
18558
|
+
report.multiActiveCycles.push({ cycleNumber: cycle.number, id: cycle.id, taskCount });
|
|
18559
|
+
}
|
|
18560
|
+
}
|
|
18561
|
+
const validCycleNumbers = new Set(cycles.map((c) => c.number));
|
|
18562
|
+
for (const cycle of cycles) {
|
|
18563
|
+
if (cycle.status === "completed") continue;
|
|
18564
|
+
const taskCount = allTasks.filter((t) => t.cycle === cycle.number).length;
|
|
18565
|
+
if (taskCount === 0 && cycle.status === "active") {
|
|
18566
|
+
report.ghostCycles.push({ cycleNumber: cycle.number, id: cycle.id });
|
|
18567
|
+
}
|
|
18568
|
+
}
|
|
18569
|
+
for (const task of allTasks) {
|
|
18570
|
+
if (task.cycle && !validCycleNumbers.has(task.cycle)) {
|
|
18571
|
+
report.orphanedAssignments.push({
|
|
18572
|
+
taskId: task.id,
|
|
18573
|
+
title: task.title,
|
|
18574
|
+
assignedCycle: task.cycle
|
|
18575
|
+
});
|
|
18576
|
+
}
|
|
18577
|
+
}
|
|
18578
|
+
} catch {
|
|
18579
|
+
}
|
|
18580
|
+
return report;
|
|
18581
|
+
}
|
|
18582
|
+
function formatCycleIntegrityReport(report) {
|
|
18583
|
+
const lines = [];
|
|
18584
|
+
const hasIssues = report.multiActiveCycles.length > 1 || report.ghostCycles.length > 0 || report.orphanedAssignments.length > 0;
|
|
18585
|
+
if (!hasIssues) return "";
|
|
18586
|
+
lines.push("### Cycle Integrity Check");
|
|
18587
|
+
lines.push("");
|
|
18588
|
+
if (report.multiActiveCycles.length > 1) {
|
|
18589
|
+
lines.push(`**Multi-active-cycle detected** (${report.multiActiveCycles.length} active cycles \u2014 needs manual fix):`);
|
|
18590
|
+
const sorted = [...report.multiActiveCycles].sort((a, b2) => b2.taskCount - a.taskCount || b2.cycleNumber - a.cycleNumber);
|
|
18591
|
+
for (const c of sorted) {
|
|
18592
|
+
const tag = c === sorted[0] ? " \u2190 likely current" : " \u2190 candidate for completion";
|
|
18593
|
+
lines.push(`- Cycle ${c.cycleNumber} (${c.taskCount} tasks)${tag}`);
|
|
18594
|
+
}
|
|
18595
|
+
lines.push("Fix: run `UPDATE cycles SET status = 'completed' WHERE number = <N>` for the stale cycle(s).");
|
|
18596
|
+
lines.push("");
|
|
18597
|
+
}
|
|
18598
|
+
if (report.ghostCycles.length > 0) {
|
|
18599
|
+
lines.push("**Ghost cycles** (active but no tasks \u2014 needs manual review):");
|
|
18600
|
+
for (const g of report.ghostCycles) {
|
|
18601
|
+
lines.push(`- Cycle ${g.cycleNumber} (id: ${g.id}) \u2014 active with 0 tasks`);
|
|
18602
|
+
}
|
|
18603
|
+
lines.push("");
|
|
18604
|
+
}
|
|
18605
|
+
if (report.orphanedAssignments.length > 0) {
|
|
18606
|
+
lines.push("**Orphaned cycle assignments** (tasks pointing to non-existent cycles):");
|
|
18607
|
+
for (const o of report.orphanedAssignments) {
|
|
18608
|
+
lines.push(`- **${o.taskId}**: "${o.title}" \u2192 cycle ${o.assignedCycle} (does not exist)`);
|
|
18609
|
+
}
|
|
18610
|
+
lines.push("");
|
|
18611
|
+
}
|
|
18612
|
+
return lines.join("\n") + "\n";
|
|
18613
|
+
}
|
|
18464
18614
|
async function handleBoardReconcile(adapter2, config2, args) {
|
|
18465
18615
|
const mode = args.mode ?? "prepare";
|
|
18466
18616
|
if (mode === "prepare") {
|
|
18467
18617
|
const context = await prepareReconcile(adapter2);
|
|
18618
|
+
const cycleReport = await checkCycleIntegrity(adapter2);
|
|
18619
|
+
const cycleSection = formatCycleIntegrityReport(cycleReport);
|
|
18468
18620
|
if (context === "No backlog tasks to reconcile.") {
|
|
18621
|
+
if (cycleSection) {
|
|
18622
|
+
return textResponse(`No backlog tasks to reconcile.
|
|
18623
|
+
|
|
18624
|
+
${cycleSection}`);
|
|
18625
|
+
}
|
|
18469
18626
|
return textResponse(context);
|
|
18470
18627
|
}
|
|
18471
18628
|
let mismatchSection = "";
|
|
@@ -18496,7 +18653,7 @@ async function handleBoardReconcile(adapter2, config2, args) {
|
|
|
18496
18653
|
`${RECONCILE_PROMPT}
|
|
18497
18654
|
---
|
|
18498
18655
|
|
|
18499
|
-
### Backlog Context
|
|
18656
|
+
${cycleSection}### Backlog Context
|
|
18500
18657
|
|
|
18501
18658
|
${mismatchSection}${context}
|
|
18502
18659
|
---
|
|
@@ -19866,9 +20023,10 @@ async function getHierarchyPosition(adapter2) {
|
|
|
19866
20023
|
adapter2.queryBoard()
|
|
19867
20024
|
]);
|
|
19868
20025
|
if (horizons.length === 0) return void 0;
|
|
19869
|
-
const
|
|
19870
|
-
const
|
|
19871
|
-
const
|
|
20026
|
+
const isActive = (s) => s.status.toLowerCase() === "active";
|
|
20027
|
+
const activeHorizon = horizons.find(isActive) || horizons[0];
|
|
20028
|
+
const horizonStages = stages.filter((s) => s.horizonId === activeHorizon.id).sort((a, b2) => (a.sortOrder ?? 0) - (b2.sortOrder ?? 0));
|
|
20029
|
+
const activeStage = horizonStages.find(isActive) || horizonStages[0];
|
|
19872
20030
|
if (!activeStage) return void 0;
|
|
19873
20031
|
const stagePhases = phases.filter((p) => p.stageId === activeStage.id);
|
|
19874
20032
|
const activePhases = stagePhases.filter((p) => p.status === "In Progress");
|
package/dist/prompts.js
CHANGED
|
@@ -133,10 +133,12 @@ This is Cycle 0 \u2014 the first planning cycle for a brand-new project.
|
|
|
133
133
|
|
|
134
134
|
2. **North Star** \u2014 Propose a one-sentence North Star statement, a success metric, and a key metric.
|
|
135
135
|
|
|
136
|
-
3. **Initial Board** \u2014 Generate 3-5 tasks:
|
|
137
|
-
-
|
|
138
|
-
- Task
|
|
139
|
-
-
|
|
136
|
+
3. **Initial Board** \u2014 Generate 3-5 tasks based on the project's actual tech stack and goals:
|
|
137
|
+
- Infer the project type from the brief/description (CLI, web app, mobile app, API, library, game, data pipeline, etc.)
|
|
138
|
+
- Task 1: Project-appropriate setup (toolchain, dependencies, config \u2014 NOT "scaffolding" if the project already has code)
|
|
139
|
+
- Task 2: Core functionality that proves the concept works (data model, main loop, core algorithm \u2014 whatever the project needs first)
|
|
140
|
+
- Tasks 3-5: First deliverables that demonstrate value, broken into small steps appropriate for the project type
|
|
141
|
+
- Do NOT assume web-app patterns (routes, pages, components) unless the brief explicitly describes a web application
|
|
140
142
|
- All tasks: status Backlog, priority P1-P2, reviewed true, phase "Phase 1"
|
|
141
143
|
|
|
142
144
|
4. **First Active Decision** \u2014 If the description implies a clear architectural choice, create AD-1 with Confidence: MEDIUM. If no clear choice, skip this.
|
|
@@ -296,11 +298,25 @@ var PLAN_FRAGMENT_RESEARCH = `
|
|
|
296
298
|
var PLAN_FRAGMENT_BUG = `
|
|
297
299
|
**Bug task detection:** When a task's task type is "bug" or the title starts with "Bug:" or "Fix:", apply these rules:
|
|
298
300
|
- **Auto-P1:** If the task's current priority is P2 or lower, upgrade it to "P1 High" via a boardCorrections entry in Part 2. Note the upgrade in Part 1 analysis.
|
|
299
|
-
-
|
|
300
|
-
|
|
301
|
+
- Replace the standard SCOPE (DO THIS) section with bug-specific sections:
|
|
302
|
+
- **REPRODUCE:** Exact steps to reproduce the bug before touching any code. If the task notes describe the symptoms, include them. If not, the first build step is "confirm the bug reproduces."
|
|
303
|
+
- **ROOT CAUSE:** One-sentence hypothesis for the root cause (what is wrong, not what the user sees). The builder must confirm or correct this before implementing a fix.
|
|
304
|
+
- **MINIMAL FIX:** The smallest code change that resolves the root cause. "Bug fix \u2014 minimal blast radius. Change only what is necessary. Do not refactor surrounding code or expand scope."
|
|
305
|
+
- **REGRESSION TEST:** How to verify the bug is fixed and won't silently recur. Describe the test (manual or automated) \u2014 the builder must confirm this passes.
|
|
306
|
+
- Add to ACCEPTANCE CRITERIA: "[ ] Fix is targeted \u2014 no unrelated code changed" and "[ ] Regression test confirms the bug no longer reproduces"`;
|
|
301
307
|
var PLAN_FRAGMENT_IDEA = `
|
|
302
308
|
**Idea task detection:** When a task's task type is "idea", add a scope clarification note to the BUILD HANDOFF:
|
|
303
309
|
- Add to SCOPE (DO THIS): "This task originated as an idea. Confirm the exact deliverable before implementing \u2014 check task notes and any referenced docs for intent. If scope is unclear, flag it in the build report surprises."`;
|
|
310
|
+
var PLAN_FRAGMENT_SPIKE = `
|
|
311
|
+
**Spike task detection:** When a task's task type is "spike" or the title starts with "Spike:", apply these rules:
|
|
312
|
+
- Spikes are time-boxed investigations, not implementation tasks. The deliverable is a FINDING, not code.
|
|
313
|
+
- Replace the standard BUILD HANDOFF sections with spike-specific sections:
|
|
314
|
+
- **TIME-BOX:** Maximum effort for this spike (e.g. "Stop after S effort / ~2 hours"). If the question isn't answered by then, the spike is done \u2014 report what you found.
|
|
315
|
+
- **GOAL:** The specific question this spike answers (one sentence, phrased as a question).
|
|
316
|
+
- **OUTPUT:** What the spike produces: a written finding (doc in docs/research/ or notes in the build report), optionally a proof-of-concept if code is needed.
|
|
317
|
+
- **DONE CONDITION:** "Question answered OR time-box hit, whichever comes first."
|
|
318
|
+
- Keep SCOPE BOUNDARY, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
319
|
+
- Spikes should be estimated conservatively: XS or S. If a spike needs M+ effort, it's not a spike \u2014 reclassify as a research task.`;
|
|
304
320
|
var PLAN_FRAGMENT_UI = `
|
|
305
321
|
**UI/visual task detection:** When a task's title or notes contain keywords suggesting frontend visual work (e.g. "visual", "design", "UI", "styling", "refresh", "frontend", "landing page", "hero", "carousel", "theme", "layout", "cockpit", "dashboard", "page"), apply these handoff additions:
|
|
306
322
|
- Add to SCOPE: "Read \`.impeccable.md\` for brand palette, design principles, and audience context before writing any code. Use the \`frontend-design\` skill for implementation."
|
|
@@ -409,6 +425,7 @@ Standard planning cycle with full board review.
|
|
|
409
425
|
if (flags.hasResearchTasks) parts.push(PLAN_FRAGMENT_RESEARCH);
|
|
410
426
|
if (flags.hasBugTasks) parts.push(PLAN_FRAGMENT_BUG);
|
|
411
427
|
if (flags.hasIdeaTasks) parts.push(PLAN_FRAGMENT_IDEA);
|
|
428
|
+
if (flags.hasSpikeTasks) parts.push(PLAN_FRAGMENT_SPIKE);
|
|
412
429
|
if (flags.hasUITasks) parts.push(PLAN_FRAGMENT_UI);
|
|
413
430
|
parts.push(`
|
|
414
431
|
11. **New Tasks (max 3 per cycle)** \u2014 Actively mine the Recent Build Reports for task candidates. For each report, check:
|
|
@@ -727,6 +744,15 @@ You MUST cover these 5 sections. Each is mandatory.
|
|
|
727
744
|
${compressionJob}
|
|
728
745
|
Note: Hierarchy assessment and structural drift detection are handled within section 5 (AD & Hierarchy Housekeeping). They do not need their own sections.
|
|
729
746
|
|
|
747
|
+
## DETECT STRATEGIC DECISIONS
|
|
748
|
+
|
|
749
|
+
Watch for direction changes, architecture shifts, deprioritisation with reasoning, new principles, or competitive positioning decisions in the project data.
|
|
750
|
+
|
|
751
|
+
When detected:
|
|
752
|
+
1. Flag it in the review: "Strategic direction change detected \u2014 [description]."
|
|
753
|
+
2. Propose an AD update or new AD in the structured output (Part 2 \`activeDecisions\` array).
|
|
754
|
+
3. Recommend running \`strategy_change\` if the shift requires immediate action before the next plan.
|
|
755
|
+
|
|
730
756
|
## OUTPUT FORMAT
|
|
731
757
|
|
|
732
758
|
Your output has TWO parts:
|
package/package.json
CHANGED