@papi-ai/server 0.7.3-alpha.1 → 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 +194 -38
- 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`
|
|
@@ -10000,10 +10003,12 @@ This is Cycle 0 \u2014 the first planning cycle for a brand-new project.
|
|
|
10000
10003
|
|
|
10001
10004
|
2. **North Star** \u2014 Propose a one-sentence North Star statement, a success metric, and a key metric.
|
|
10002
10005
|
|
|
10003
|
-
3. **Initial Board** \u2014 Generate 3-5 tasks:
|
|
10004
|
-
-
|
|
10005
|
-
- Task
|
|
10006
|
-
-
|
|
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
|
|
10007
10012
|
- All tasks: status Backlog, priority P1-P2, reviewed true, phase "Phase 1"
|
|
10008
10013
|
|
|
10009
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.
|
|
@@ -10163,11 +10168,25 @@ var PLAN_FRAGMENT_RESEARCH = `
|
|
|
10163
10168
|
var PLAN_FRAGMENT_BUG = `
|
|
10164
10169
|
**Bug task detection:** When a task's task type is "bug" or the title starts with "Bug:" or "Fix:", apply these rules:
|
|
10165
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.
|
|
10166
|
-
-
|
|
10167
|
-
|
|
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"`;
|
|
10168
10177
|
var PLAN_FRAGMENT_IDEA = `
|
|
10169
10178
|
**Idea task detection:** When a task's task type is "idea", add a scope clarification note to the BUILD HANDOFF:
|
|
10170
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.`;
|
|
10171
10190
|
var PLAN_FRAGMENT_UI = `
|
|
10172
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:
|
|
10173
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."
|
|
@@ -10276,6 +10295,7 @@ Standard planning cycle with full board review.
|
|
|
10276
10295
|
if (flags.hasResearchTasks) parts.push(PLAN_FRAGMENT_RESEARCH);
|
|
10277
10296
|
if (flags.hasBugTasks) parts.push(PLAN_FRAGMENT_BUG);
|
|
10278
10297
|
if (flags.hasIdeaTasks) parts.push(PLAN_FRAGMENT_IDEA);
|
|
10298
|
+
if (flags.hasSpikeTasks) parts.push(PLAN_FRAGMENT_SPIKE);
|
|
10279
10299
|
if (flags.hasUITasks) parts.push(PLAN_FRAGMENT_UI);
|
|
10280
10300
|
parts.push(`
|
|
10281
10301
|
11. **New Tasks (max 3 per cycle)** \u2014 Actively mine the Recent Build Reports for task candidates. For each report, check:
|
|
@@ -10594,6 +10614,15 @@ You MUST cover these 5 sections. Each is mandatory.
|
|
|
10594
10614
|
${compressionJob}
|
|
10595
10615
|
Note: Hierarchy assessment and structural drift detection are handled within section 5 (AD & Hierarchy Housekeeping). They do not need their own sections.
|
|
10596
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
|
+
|
|
10597
10626
|
## OUTPUT FORMAT
|
|
10598
10627
|
|
|
10599
10628
|
Your output has TWO parts:
|
|
@@ -11453,21 +11482,24 @@ function detectBoardFlags(tasks) {
|
|
|
11453
11482
|
let hasBugTasks = false;
|
|
11454
11483
|
let hasResearchTasks = false;
|
|
11455
11484
|
let hasIdeaTasks = false;
|
|
11485
|
+
let hasSpikeTasks = false;
|
|
11456
11486
|
let hasUITasks = false;
|
|
11457
11487
|
const uiKeywords = /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i;
|
|
11458
11488
|
for (const t of tasks) {
|
|
11459
11489
|
if (t.taskType === "bug" || /^(Bug:|Fix:)/i.test(t.title)) hasBugTasks = true;
|
|
11460
11490
|
if (t.taskType === "research" || /^Research:/i.test(t.title)) hasResearchTasks = true;
|
|
11461
11491
|
if (t.taskType === "idea") hasIdeaTasks = true;
|
|
11492
|
+
if (t.taskType === "spike" || /^Spike:/i.test(t.title)) hasSpikeTasks = true;
|
|
11462
11493
|
if (uiKeywords.test(t.title) || uiKeywords.test(t.notes ?? "")) hasUITasks = true;
|
|
11463
11494
|
}
|
|
11464
|
-
return { hasBugTasks, hasResearchTasks, hasIdeaTasks, hasUITasks };
|
|
11495
|
+
return { hasBugTasks, hasResearchTasks, hasIdeaTasks, hasSpikeTasks, hasUITasks };
|
|
11465
11496
|
}
|
|
11466
11497
|
function detectBoardFlagsFromText(boardText) {
|
|
11467
11498
|
return {
|
|
11468
11499
|
hasBugTasks: /\b(bug|Bug:|Fix:)\b/i.test(boardText),
|
|
11469
11500
|
hasResearchTasks: /\b(research|Research:)\b/i.test(boardText),
|
|
11470
11501
|
hasIdeaTasks: /\bidea\b/i.test(boardText),
|
|
11502
|
+
hasSpikeTasks: /\b(spike|Spike:)\b/i.test(boardText),
|
|
11471
11503
|
hasUITasks: /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i.test(boardText)
|
|
11472
11504
|
};
|
|
11473
11505
|
}
|
|
@@ -11944,13 +11976,24 @@ ${cleanContent}`;
|
|
|
11944
11976
|
console.error(`[plan-perf] transactionalWriteBack: total=${writeBackMs}ms`);
|
|
11945
11977
|
const verifyWarnings = [];
|
|
11946
11978
|
try {
|
|
11947
|
-
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
|
+
]);
|
|
11948
11983
|
const newCycle = cycles.find((s) => s.number === newCycleNumber);
|
|
11949
11984
|
if (!newCycle) {
|
|
11950
|
-
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
|
+
}
|
|
11951
11994
|
}
|
|
11952
11995
|
} catch {
|
|
11953
|
-
verifyWarnings.push("Post-write verification: could not read cycles
|
|
11996
|
+
verifyWarnings.push("Post-write verification: could not read cycles/tasks tables");
|
|
11954
11997
|
}
|
|
11955
11998
|
const allWarnings = [...result.warnings, ...verifyWarnings];
|
|
11956
11999
|
const handoffCount = data.cycleHandoffs?.length ?? 0;
|
|
@@ -12037,7 +12080,8 @@ ${cleanContent}`;
|
|
|
12037
12080
|
cycle: newCycleNumber,
|
|
12038
12081
|
createdCycle: newCycleNumber,
|
|
12039
12082
|
why: task.why || "",
|
|
12040
|
-
notes: task.notes || ""
|
|
12083
|
+
notes: task.notes || "",
|
|
12084
|
+
source: "llm"
|
|
12041
12085
|
});
|
|
12042
12086
|
newTaskIdMap.set(`new-${i + 1}`, created.id);
|
|
12043
12087
|
if (adapter2.updateCycleLearningActionRef && task.notes) {
|
|
@@ -14434,6 +14478,18 @@ ${result.userMessage}
|
|
|
14434
14478
|
// src/services/board.ts
|
|
14435
14479
|
var ACTIVE_STATUSES = ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked"];
|
|
14436
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
|
+
};
|
|
14437
14493
|
async function viewBoard(adapter2, phaseFilter, options) {
|
|
14438
14494
|
const queryOptions = {};
|
|
14439
14495
|
const phase = options?.phase ?? phaseFilter;
|
|
@@ -14448,9 +14504,16 @@ async function viewBoard(adapter2, phaseFilter, options) {
|
|
|
14448
14504
|
Object.keys(queryOptions).length > 0 ? queryOptions : void 0
|
|
14449
14505
|
);
|
|
14450
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;
|
|
14451
14510
|
const ai = PRIORITY_ORDER.indexOf(a.priority);
|
|
14452
14511
|
const bi = PRIORITY_ORDER.indexOf(b2.priority);
|
|
14453
|
-
|
|
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);
|
|
14454
14517
|
});
|
|
14455
14518
|
const total = allTasks.length;
|
|
14456
14519
|
const offset = options?.offset ?? 0;
|
|
@@ -14654,7 +14717,7 @@ function formatBoard(result) {
|
|
|
14654
14717
|
if (result.tasks.length === 0) {
|
|
14655
14718
|
return "No tasks found.";
|
|
14656
14719
|
}
|
|
14657
|
-
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"];
|
|
14658
14721
|
const rows = result.tasks.map((t) => [
|
|
14659
14722
|
t.priority,
|
|
14660
14723
|
t.id,
|
|
@@ -14665,7 +14728,8 @@ function formatBoard(result) {
|
|
|
14665
14728
|
t.module ?? "-",
|
|
14666
14729
|
t.epic ?? "-",
|
|
14667
14730
|
t.complexity ?? "-",
|
|
14668
|
-
t.createdAt ?? "-"
|
|
14731
|
+
t.createdAt ?? "-",
|
|
14732
|
+
t.source ?? "-"
|
|
14669
14733
|
]);
|
|
14670
14734
|
const widths = headers.map(
|
|
14671
14735
|
(h, i) => Math.max(h.length, ...rows.map((r) => r[i].length))
|
|
@@ -15943,7 +16007,8 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
15943
16007
|
phase: task.phase || "Phase 1",
|
|
15944
16008
|
owner: "TBD",
|
|
15945
16009
|
reviewed: false,
|
|
15946
|
-
notes: task.notes
|
|
16010
|
+
notes: task.notes,
|
|
16011
|
+
source: "llm"
|
|
15947
16012
|
});
|
|
15948
16013
|
createdTasks++;
|
|
15949
16014
|
}
|
|
@@ -17522,19 +17587,23 @@ ${lines.join("\n")}
|
|
|
17522
17587
|
const VALID_COMPLEXITIES2 = /* @__PURE__ */ new Set(["XS", "Small", "Medium", "Large", "XL"]);
|
|
17523
17588
|
const priority = input.priority && VALID_PRIORITIES2.has(input.priority) ? input.priority : "P2 Medium";
|
|
17524
17589
|
const complexity = input.complexity && VALID_COMPLEXITIES2.has(input.complexity) ? input.complexity : "Small";
|
|
17525
|
-
const
|
|
17526
|
-
bug: "bug",
|
|
17527
|
-
research: "research",
|
|
17528
|
-
feedback: "feedback"
|
|
17529
|
-
};
|
|
17590
|
+
const VALID_TYPES = /* @__PURE__ */ new Set(["task", "bug", "research", "idea", "spike"]);
|
|
17530
17591
|
let taskTitle = input.text;
|
|
17531
17592
|
let taskType = "idea";
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
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
|
+
}
|
|
17538
17607
|
}
|
|
17539
17608
|
}
|
|
17540
17609
|
const task = await adapter2.createTask({
|
|
@@ -17553,7 +17622,8 @@ ${lines.join("\n")}
|
|
|
17553
17622
|
notes: input.notes || "",
|
|
17554
17623
|
taskType,
|
|
17555
17624
|
maturity: "raw",
|
|
17556
|
-
docRef: input.docRef
|
|
17625
|
+
docRef: input.docRef,
|
|
17626
|
+
source: "llm"
|
|
17557
17627
|
});
|
|
17558
17628
|
if (input.notes && adapter2.updateCycleLearningActionRef) {
|
|
17559
17629
|
const learningRefs = input.notes.match(/learning:([a-f0-9-]+)/gi);
|
|
@@ -17661,6 +17731,11 @@ var ideaTool = {
|
|
|
17661
17731
|
type: "boolean",
|
|
17662
17732
|
description: "Force creation even if a high-overlap duplicate or already-done task is detected. Default: false."
|
|
17663
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
|
+
},
|
|
17664
17739
|
doc_ref: {
|
|
17665
17740
|
type: "string",
|
|
17666
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.'
|
|
@@ -17691,7 +17766,8 @@ async function handleIdea(adapter2, config2, args) {
|
|
|
17691
17766
|
notes: rawNotes,
|
|
17692
17767
|
discovery: args.discovery === true,
|
|
17693
17768
|
force: args.force === true,
|
|
17694
|
-
docRef: args.doc_ref?.trim()
|
|
17769
|
+
docRef: args.doc_ref?.trim(),
|
|
17770
|
+
type: args.type
|
|
17695
17771
|
};
|
|
17696
17772
|
const useGit = isGitAvailable() && isGitRepo(config2.projectRoot);
|
|
17697
17773
|
const currentBranch = useGit ? getCurrentBranch(config2.projectRoot) : null;
|
|
@@ -17778,7 +17854,8 @@ async function captureBug(adapter2, input) {
|
|
|
17778
17854
|
createdCycle: health.totalCycles,
|
|
17779
17855
|
notes: input.notes || "",
|
|
17780
17856
|
taskType: "bug",
|
|
17781
|
-
maturity: "investigated"
|
|
17857
|
+
maturity: "investigated",
|
|
17858
|
+
source: "llm"
|
|
17782
17859
|
});
|
|
17783
17860
|
}
|
|
17784
17861
|
|
|
@@ -17949,7 +18026,8 @@ async function recordAdHoc(adapter2, input) {
|
|
|
17949
18026
|
reviewed: true,
|
|
17950
18027
|
createdCycle: cycle,
|
|
17951
18028
|
notes: input.notes ? `[ad-hoc] ${input.notes}` : "[ad-hoc]",
|
|
17952
|
-
taskType: "task"
|
|
18029
|
+
taskType: "task",
|
|
18030
|
+
source: "owner"
|
|
17953
18031
|
});
|
|
17954
18032
|
}
|
|
17955
18033
|
const report = {
|
|
@@ -18463,11 +18541,88 @@ When done, call \`board_reconcile\` again with:
|
|
|
18463
18541
|
- \`mode\`: "retriage-apply"
|
|
18464
18542
|
- \`llm_response\`: your complete output (both parts)
|
|
18465
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
|
+
}
|
|
18466
18614
|
async function handleBoardReconcile(adapter2, config2, args) {
|
|
18467
18615
|
const mode = args.mode ?? "prepare";
|
|
18468
18616
|
if (mode === "prepare") {
|
|
18469
18617
|
const context = await prepareReconcile(adapter2);
|
|
18618
|
+
const cycleReport = await checkCycleIntegrity(adapter2);
|
|
18619
|
+
const cycleSection = formatCycleIntegrityReport(cycleReport);
|
|
18470
18620
|
if (context === "No backlog tasks to reconcile.") {
|
|
18621
|
+
if (cycleSection) {
|
|
18622
|
+
return textResponse(`No backlog tasks to reconcile.
|
|
18623
|
+
|
|
18624
|
+
${cycleSection}`);
|
|
18625
|
+
}
|
|
18471
18626
|
return textResponse(context);
|
|
18472
18627
|
}
|
|
18473
18628
|
let mismatchSection = "";
|
|
@@ -18498,7 +18653,7 @@ async function handleBoardReconcile(adapter2, config2, args) {
|
|
|
18498
18653
|
`${RECONCILE_PROMPT}
|
|
18499
18654
|
---
|
|
18500
18655
|
|
|
18501
|
-
### Backlog Context
|
|
18656
|
+
${cycleSection}### Backlog Context
|
|
18502
18657
|
|
|
18503
18658
|
${mismatchSection}${context}
|
|
18504
18659
|
---
|
|
@@ -19868,9 +20023,10 @@ async function getHierarchyPosition(adapter2) {
|
|
|
19868
20023
|
adapter2.queryBoard()
|
|
19869
20024
|
]);
|
|
19870
20025
|
if (horizons.length === 0) return void 0;
|
|
19871
|
-
const
|
|
19872
|
-
const
|
|
19873
|
-
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];
|
|
19874
20030
|
if (!activeStage) return void 0;
|
|
19875
20031
|
const stagePhases = phases.filter((p) => p.stageId === activeStage.id);
|
|
19876
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