@dv.nghiem/flowdeck 0.3.4 → 0.3.5
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/README.md +154 -3
- package/dist/agents/coder.d.ts +3 -1
- package/dist/agents/coder.d.ts.map +1 -1
- package/dist/agents/design.d.ts +3 -0
- package/dist/agents/design.d.ts.map +1 -0
- package/dist/agents/index.d.ts +4 -3
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/reviewer.d.ts.map +1 -1
- package/dist/agents/specialist.d.ts +0 -1
- package/dist/agents/specialist.d.ts.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/schema.d.ts +55 -2
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/dashboard/server.mjs +24 -1
- package/dist/dashboard/types.d.ts +72 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/hooks/guard-rails.d.ts.map +1 -1
- package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
- package/dist/hooks/tool-guard.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +490 -418
- package/dist/services/agent-contract-registry.d.ts +32 -0
- package/dist/services/agent-contract-registry.d.ts.map +1 -0
- package/dist/services/agent-performance.d.ts +1 -1
- package/dist/services/agent-performance.d.ts.map +1 -1
- package/dist/services/agent-trace-graph.d.ts +94 -0
- package/dist/services/agent-trace-graph.d.ts.map +1 -0
- package/dist/services/agent-validator.d.ts +56 -0
- package/dist/services/agent-validator.d.ts.map +1 -0
- package/dist/services/deadlock-detector.d.ts +34 -0
- package/dist/services/deadlock-detector.d.ts.map +1 -0
- package/dist/services/delegation-budget.d.ts +54 -0
- package/dist/services/delegation-budget.d.ts.map +1 -0
- package/dist/services/governance.test.d.ts +11 -0
- package/dist/services/governance.test.d.ts.map +1 -0
- package/dist/services/index.d.ts +6 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/telemetry.d.ts +1 -1
- package/dist/services/telemetry.d.ts.map +1 -1
- package/dist/services/workflow-scorecard.d.ts +76 -0
- package/dist/services/workflow-scorecard.d.ts.map +1 -0
- package/dist/tools/delegate.d.ts.map +1 -1
- package/dist/tools/dispatch-routing.d.ts +4 -1
- package/dist/tools/dispatch-routing.d.ts.map +1 -1
- package/dist/tools/dispatch-routing.test.d.ts +2 -0
- package/dist/tools/dispatch-routing.test.d.ts.map +1 -0
- package/dist/tools/planning-state-lib.d.ts +8 -0
- package/dist/tools/planning-state-lib.d.ts.map +1 -1
- package/dist/tools/planning-state.d.ts.map +1 -1
- package/dist/tools/run-pipeline.d.ts.map +1 -1
- package/docs/agents.md +104 -74
- package/docs/best-practices.md +1 -1
- package/docs/commands/fd-ask.md +2 -2
- package/docs/commands/fd-fix-bug.md +2 -2
- package/docs/commands/fd-new-feature.md +2 -2
- package/docs/commands/fd-quick.md +3 -1
- package/docs/commands.md +37 -7
- package/docs/configuration.md +76 -46
- package/docs/design-first-workflow.md +94 -0
- package/docs/feature-integration-architecture.md +3 -31
- package/docs/index.md +5 -2
- package/docs/intelligence.md +92 -1
- package/docs/multi-repo.md +1 -1
- package/docs/rules.md +1 -1
- package/docs/skills.md +24 -15
- package/docs/workflows.md +11 -6
- package/package.json +1 -1
- package/src/commands/fd-ask.md +1 -0
- package/src/commands/fd-design.md +64 -0
- package/src/commands/fd-discuss.md +2 -0
- package/src/commands/fd-execute.md +7 -3
- package/src/commands/fd-fix-bug.md +2 -2
- package/src/commands/fd-multi-repo.md +3 -3
- package/src/commands/fd-plan.md +2 -0
- package/src/commands/fd-quick.md +4 -1
- package/src/commands/fd-verify.md +6 -0
- package/src/rules/common/agent-orchestration.md +6 -6
- package/src/skills/app-shell-design/SKILL.md +31 -0
- package/src/skills/dashboard-design/SKILL.md +32 -0
- package/src/skills/decision-trace/SKILL.md +1 -1
- package/src/skills/design-audit/SKILL.md +37 -0
- package/src/skills/design-system-definition/SKILL.md +33 -0
- package/src/skills/frontend-handoff/SKILL.md +31 -0
- package/src/skills/landing-page-design/SKILL.md +32 -0
- package/src/skills/multi-repo/SKILL.md +3 -3
- package/src/skills/plan-task/SKILL.md +2 -2
- package/src/skills/responsive-review/SKILL.md +31 -0
- package/src/skills/ui-ux-planning/SKILL.md +32 -0
- package/src/skills/wireframe-planning/SKILL.md +30 -0
- package/dist/services/model-router.d.ts +0 -35
- package/dist/services/model-router.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
3
|
-
import { join as
|
|
2
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync23, existsSync as existsSync27 } from "fs";
|
|
3
|
+
import { join as join26, basename } from "path";
|
|
4
4
|
import { dirname as dirname4 } from "path";
|
|
5
5
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
6
|
|
|
@@ -128,6 +128,8 @@ function parseState(content) {
|
|
|
128
128
|
result[key] = value.replace(/[\[\]]/g, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
129
129
|
} else if (key === "plan_confirmed") {
|
|
130
130
|
result[key] = value === "true";
|
|
131
|
+
} else if (key === "requires_design_first" || key === "design_approved" || key === "design_override") {
|
|
132
|
+
result[key] = value === "true";
|
|
131
133
|
} else if (value !== "" && !isNaN(Number(value)) && key !== "plan_file" && key !== "confirmed_at") {
|
|
132
134
|
result[key] = Number(value);
|
|
133
135
|
} else {
|
|
@@ -155,7 +157,21 @@ ${entry}
|
|
|
155
157
|
function readPlanningState(dir) {
|
|
156
158
|
const sp = statePath(dir);
|
|
157
159
|
if (!existsSync2(sp)) {
|
|
158
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
phase: 0,
|
|
162
|
+
status: "",
|
|
163
|
+
plan_confirmed: false,
|
|
164
|
+
requires_design_first: false,
|
|
165
|
+
design_stage: "pending",
|
|
166
|
+
design_approved: false,
|
|
167
|
+
design_override: false,
|
|
168
|
+
steps_complete: [],
|
|
169
|
+
steps_pending: [],
|
|
170
|
+
last_action: "",
|
|
171
|
+
next_action: "",
|
|
172
|
+
blockers: [],
|
|
173
|
+
tdd: undefined
|
|
174
|
+
};
|
|
159
175
|
}
|
|
160
176
|
const content = readFileSync2(sp, "utf-8");
|
|
161
177
|
const parsed = parseState(content);
|
|
@@ -163,6 +179,13 @@ function readPlanningState(dir) {
|
|
|
163
179
|
phase: parsed.phase || 1,
|
|
164
180
|
status: parsed.status || "",
|
|
165
181
|
plan_confirmed: Boolean(parsed.plan_confirmed),
|
|
182
|
+
task_type: parsed.task_type || undefined,
|
|
183
|
+
requires_design_first: Boolean(parsed.requires_design_first),
|
|
184
|
+
design_stage: parsed.design_stage || "pending",
|
|
185
|
+
design_approved: Boolean(parsed.design_approved),
|
|
186
|
+
design_override: Boolean(parsed.design_override),
|
|
187
|
+
design_override_reason: parsed.design_override_reason || undefined,
|
|
188
|
+
design_artifact: parsed.design_artifact || undefined,
|
|
166
189
|
steps_complete: parsed.steps_complete || [],
|
|
167
190
|
steps_pending: parsed.steps_pending || [],
|
|
168
191
|
last_action: parsed.last_action || "",
|
|
@@ -251,6 +274,13 @@ var planningStateTool = tool2({
|
|
|
251
274
|
plan_file: tool2.schema.string().optional(),
|
|
252
275
|
plan_confirmed: tool2.schema.boolean().optional(),
|
|
253
276
|
confirmed_at: tool2.schema.string().optional(),
|
|
277
|
+
task_type: tool2.schema.string().optional(),
|
|
278
|
+
requires_design_first: tool2.schema.boolean().optional(),
|
|
279
|
+
design_stage: tool2.schema.enum(["pending", "discovery", "ux_planning", "wireframe_layout", "visual_system_definition", "design_approval", "handoff_complete"]).optional(),
|
|
280
|
+
design_approved: tool2.schema.boolean().optional(),
|
|
281
|
+
design_override: tool2.schema.boolean().optional(),
|
|
282
|
+
design_override_reason: tool2.schema.string().optional(),
|
|
283
|
+
design_artifact: tool2.schema.string().optional(),
|
|
254
284
|
steps_complete: tool2.schema.array(tool2.schema.number()).optional(),
|
|
255
285
|
steps_pending: tool2.schema.array(tool2.schema.number()).optional()
|
|
256
286
|
}).optional(),
|
|
@@ -273,16 +303,24 @@ var planningStateTool = tool2({
|
|
|
273
303
|
if (!u)
|
|
274
304
|
return JSON.stringify({ error: "No updates provided" });
|
|
275
305
|
let content = readFileSync3(sp, "utf-8");
|
|
306
|
+
const upsertLine = (current, key, value) => {
|
|
307
|
+
const pattern = new RegExp(`^${key}:\\s*.*$`, "m");
|
|
308
|
+
if (pattern.test(current))
|
|
309
|
+
return current.replace(pattern, `${key}: ${value}`);
|
|
310
|
+
return `${current.trimEnd()}
|
|
311
|
+
${key}: ${value}
|
|
312
|
+
`;
|
|
313
|
+
};
|
|
276
314
|
if (u.phase !== undefined)
|
|
277
|
-
content = content
|
|
315
|
+
content = upsertLine(content, "phase", `${u.phase}`);
|
|
278
316
|
if (u.status !== undefined)
|
|
279
|
-
content = content
|
|
317
|
+
content = upsertLine(content, "status", `${u.status}`);
|
|
280
318
|
if (u.last_action !== undefined) {
|
|
281
|
-
content = content
|
|
319
|
+
content = upsertLine(content, "last_action", `"${u.last_action}"`);
|
|
282
320
|
content = appendHistory(content, u.last_action);
|
|
283
321
|
}
|
|
284
322
|
if (u.next_action !== undefined)
|
|
285
|
-
content = content
|
|
323
|
+
content = upsertLine(content, "next_action", `"${u.next_action}"`);
|
|
286
324
|
if (u.blockers !== undefined) {
|
|
287
325
|
const blockersMd = u.blockers.map((b) => `- ${b}`).join(`
|
|
288
326
|
`);
|
|
@@ -291,15 +329,29 @@ ${blockersMd}
|
|
|
291
329
|
`);
|
|
292
330
|
}
|
|
293
331
|
if (u.plan_file !== undefined)
|
|
294
|
-
content = content
|
|
332
|
+
content = upsertLine(content, "plan_file", `${u.plan_file}`);
|
|
295
333
|
if (u.plan_confirmed !== undefined)
|
|
296
|
-
content = content
|
|
334
|
+
content = upsertLine(content, "plan_confirmed", `${u.plan_confirmed}`);
|
|
297
335
|
if (u.confirmed_at !== undefined)
|
|
298
|
-
content = content
|
|
336
|
+
content = upsertLine(content, "confirmed_at", `${u.confirmed_at}`);
|
|
337
|
+
if (u.task_type !== undefined)
|
|
338
|
+
content = upsertLine(content, "task_type", `"${u.task_type}"`);
|
|
339
|
+
if (u.requires_design_first !== undefined)
|
|
340
|
+
content = upsertLine(content, "requires_design_first", `${u.requires_design_first}`);
|
|
341
|
+
if (u.design_stage !== undefined)
|
|
342
|
+
content = upsertLine(content, "design_stage", `"${u.design_stage}"`);
|
|
343
|
+
if (u.design_approved !== undefined)
|
|
344
|
+
content = upsertLine(content, "design_approved", `${u.design_approved}`);
|
|
345
|
+
if (u.design_override !== undefined)
|
|
346
|
+
content = upsertLine(content, "design_override", `${u.design_override}`);
|
|
347
|
+
if (u.design_override_reason !== undefined)
|
|
348
|
+
content = upsertLine(content, "design_override_reason", `"${u.design_override_reason}"`);
|
|
349
|
+
if (u.design_artifact !== undefined)
|
|
350
|
+
content = upsertLine(content, "design_artifact", `'${u.design_artifact.replace(/'/g, "''")}'`);
|
|
299
351
|
if (u.steps_complete !== undefined)
|
|
300
|
-
content = content
|
|
352
|
+
content = upsertLine(content, "steps_complete", `[${u.steps_complete.join(", ")}]`);
|
|
301
353
|
if (u.steps_pending !== undefined)
|
|
302
|
-
content = content
|
|
354
|
+
content = upsertLine(content, "steps_pending", `[${u.steps_pending.join(", ")}]`);
|
|
303
355
|
writeFileSync3(sp, content, "utf-8");
|
|
304
356
|
return JSON.stringify({ success: true, updated_at: timestamp() });
|
|
305
357
|
}
|
|
@@ -567,51 +619,6 @@ function recordRun(dir, agent, model, task_type, success, duration_ms, cost = 0)
|
|
|
567
619
|
saveStore(dir, store);
|
|
568
620
|
}
|
|
569
621
|
|
|
570
|
-
// src/services/model-router.ts
|
|
571
|
-
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
572
|
-
import { join as join6 } from "path";
|
|
573
|
-
var DEFAULT_ROUTING = {
|
|
574
|
-
planning: { primary: "claude-sonnet-4-5", temperature: 0.3, reasoning_effort: "medium" },
|
|
575
|
-
implementation: { primary: "claude-opus-4-5", fallback: "claude-sonnet-4-5", high_risk_override: "claude-opus-4-5", temperature: 0.2, reasoning_effort: "high" },
|
|
576
|
-
debugging: { primary: "claude-sonnet-4-5", high_risk_override: "claude-opus-4-5", temperature: 0.2, reasoning_effort: "high" },
|
|
577
|
-
review: { primary: "gemini-2.5-flash", fallback: "claude-haiku-4-5", temperature: 0.1, reasoning_effort: "medium" },
|
|
578
|
-
testing: { primary: "claude-haiku-4-5", fallback: "gemini-2.5-flash", temperature: 0.1, reasoning_effort: "low" },
|
|
579
|
-
documentation: { primary: "claude-sonnet-4-5", fallback: "gemini-2.5-flash", temperature: 0.3, reasoning_effort: "low" },
|
|
580
|
-
analysis: { primary: "claude-sonnet-4-5", temperature: 0.3, reasoning_effort: "medium" },
|
|
581
|
-
security: { primary: "claude-opus-4-5", high_risk_override: "claude-opus-4-5", temperature: 0.1, reasoning_effort: "high" },
|
|
582
|
-
orchestration: { primary: "claude-sonnet-4-5", temperature: 0.3, reasoning_effort: "medium" }
|
|
583
|
-
};
|
|
584
|
-
function getRouterConfig(dir) {
|
|
585
|
-
const p = join6(codebaseDir(dir), "MODEL_ROUTER.json");
|
|
586
|
-
if (!existsSync6(p))
|
|
587
|
-
return DEFAULT_ROUTING;
|
|
588
|
-
try {
|
|
589
|
-
const overrides = JSON.parse(readFileSync6(p, "utf-8"));
|
|
590
|
-
return { ...DEFAULT_ROUTING, ...overrides };
|
|
591
|
-
} catch {
|
|
592
|
-
return DEFAULT_ROUTING;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
function routeModel(dir, task_type, risk_score = 100) {
|
|
596
|
-
const config = getRouterConfig(dir);
|
|
597
|
-
const route = config[task_type] ?? DEFAULT_ROUTING.implementation;
|
|
598
|
-
const is_high_risk = risk_score < 40;
|
|
599
|
-
let model = route.primary;
|
|
600
|
-
let is_override = false;
|
|
601
|
-
if (is_high_risk && route.high_risk_override) {
|
|
602
|
-
model = route.high_risk_override;
|
|
603
|
-
is_override = true;
|
|
604
|
-
}
|
|
605
|
-
return {
|
|
606
|
-
model,
|
|
607
|
-
temperature: route.temperature ?? 0.3,
|
|
608
|
-
reasoning_effort: route.reasoning_effort,
|
|
609
|
-
task_type,
|
|
610
|
-
is_high_risk,
|
|
611
|
-
is_override
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
|
|
615
622
|
// src/tools/dispatch-routing.ts
|
|
616
623
|
function shouldRetry(promptRes) {
|
|
617
624
|
if (!promptRes)
|
|
@@ -634,6 +641,8 @@ function normalizeTaskType(taskType, agent) {
|
|
|
634
641
|
if (isTaskType(normalized))
|
|
635
642
|
return normalized;
|
|
636
643
|
const a = agent.toLowerCase();
|
|
644
|
+
if (a.includes("design") || a.includes("ui-ux"))
|
|
645
|
+
return "design";
|
|
637
646
|
if (a.includes("review"))
|
|
638
647
|
return "review";
|
|
639
648
|
if (a.includes("test"))
|
|
@@ -653,7 +662,48 @@ function normalizeTaskType(taskType, agent) {
|
|
|
653
662
|
return "implementation";
|
|
654
663
|
}
|
|
655
664
|
function isTaskType(value) {
|
|
656
|
-
return value === "planning" || value === "implementation" || value === "debugging" || value === "review" || value === "testing" || value === "documentation" || value === "analysis" || value === "security" || value === "orchestration";
|
|
665
|
+
return value === "planning" || value === "design" || value === "implementation" || value === "debugging" || value === "review" || value === "testing" || value === "documentation" || value === "analysis" || value === "security" || value === "orchestration";
|
|
666
|
+
}
|
|
667
|
+
var UI_HEAVY_KEYWORDS = [
|
|
668
|
+
"landing page",
|
|
669
|
+
"marketing site",
|
|
670
|
+
"website",
|
|
671
|
+
"web app",
|
|
672
|
+
"mobile app",
|
|
673
|
+
"app screen",
|
|
674
|
+
"dashboard",
|
|
675
|
+
"admin panel",
|
|
676
|
+
"settings page",
|
|
677
|
+
"onboarding ux",
|
|
678
|
+
"kanban",
|
|
679
|
+
"design system",
|
|
680
|
+
"responsive",
|
|
681
|
+
"ui",
|
|
682
|
+
"ux",
|
|
683
|
+
"cta",
|
|
684
|
+
"conversion flow",
|
|
685
|
+
"saas interface",
|
|
686
|
+
"user-facing"
|
|
687
|
+
];
|
|
688
|
+
var NON_UI_KEYWORDS = [
|
|
689
|
+
"backend",
|
|
690
|
+
"infrastructure",
|
|
691
|
+
"migration",
|
|
692
|
+
"pipeline",
|
|
693
|
+
"api only",
|
|
694
|
+
"database only",
|
|
695
|
+
"cli",
|
|
696
|
+
"worker"
|
|
697
|
+
];
|
|
698
|
+
function isUiHeavyTask(input) {
|
|
699
|
+
const normalized = input.trim().toLowerCase();
|
|
700
|
+
if (!normalized)
|
|
701
|
+
return false;
|
|
702
|
+
const hasUiSignal = UI_HEAVY_KEYWORDS.some((keyword) => normalized.includes(keyword));
|
|
703
|
+
if (!hasUiSignal)
|
|
704
|
+
return false;
|
|
705
|
+
const hasOnlyNonUiSignals = NON_UI_KEYWORDS.some((keyword) => normalized.includes(keyword)) && !normalized.includes("frontend");
|
|
706
|
+
return !hasOnlyNonUiSignals;
|
|
657
707
|
}
|
|
658
708
|
|
|
659
709
|
// src/tools/run-pipeline.ts
|
|
@@ -699,7 +749,6 @@ function createRunPipelineTool(client) {
|
|
|
699
749
|
}
|
|
700
750
|
const stepStart = Date.now();
|
|
701
751
|
const taskType = normalizeTaskType(step.task_type, step.agent);
|
|
702
|
-
const routing = routeModel(context.directory, taskType);
|
|
703
752
|
const stepInput = carryContext ? `${carryContext}
|
|
704
753
|
|
|
705
754
|
---
|
|
@@ -711,7 +760,7 @@ ${step.prompt}` : step.prompt;
|
|
|
711
760
|
});
|
|
712
761
|
if (createRes.error || !createRes.data?.id) {
|
|
713
762
|
const errMsg = `Failed to create session: ${createRes.error?.detail ?? "unknown"}`;
|
|
714
|
-
trace.push({ agent: step.agent, task_type: taskType, model:
|
|
763
|
+
trace.push({ agent: step.agent, task_type: taskType, model: "", input: stepInput, output: errMsg, duration_ms: Date.now() - stepStart, success: false });
|
|
715
764
|
aborted = true;
|
|
716
765
|
break;
|
|
717
766
|
}
|
|
@@ -723,7 +772,6 @@ ${step.prompt}` : step.prompt;
|
|
|
723
772
|
path: { id: inflightChildId },
|
|
724
773
|
body: {
|
|
725
774
|
agent: step.agent,
|
|
726
|
-
model: routing.model,
|
|
727
775
|
parts: [{ type: "text", text: stepInput }],
|
|
728
776
|
tools: { question: false }
|
|
729
777
|
},
|
|
@@ -740,8 +788,8 @@ ${step.prompt}` : step.prompt;
|
|
|
740
788
|
}
|
|
741
789
|
if (!promptRes || promptRes.error) {
|
|
742
790
|
const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
|
|
743
|
-
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model:
|
|
744
|
-
recordRun(context.directory, step.agent,
|
|
791
|
+
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: `${errMsg}${retriesUsed > 0 ? ` (retries: ${retriesUsed})` : ""}`, duration_ms: Date.now() - stepStart, success: false });
|
|
792
|
+
recordRun(context.directory, step.agent, "", taskType, false, Date.now() - stepStart);
|
|
745
793
|
if (args.abort_on_failure) {
|
|
746
794
|
aborted = true;
|
|
747
795
|
break;
|
|
@@ -751,8 +799,8 @@ ${step.prompt}` : step.prompt;
|
|
|
751
799
|
const info = promptRes.data?.info;
|
|
752
800
|
if (info?.error) {
|
|
753
801
|
const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
|
|
754
|
-
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model:
|
|
755
|
-
recordRun(context.directory, step.agent,
|
|
802
|
+
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: `${errMsg}${retriesUsed > 0 ? ` (retries: ${retriesUsed})` : ""}`, duration_ms: Date.now() - stepStart, success: false });
|
|
803
|
+
recordRun(context.directory, step.agent, "", taskType, false, Date.now() - stepStart);
|
|
756
804
|
if (args.abort_on_failure) {
|
|
757
805
|
aborted = true;
|
|
758
806
|
break;
|
|
@@ -760,8 +808,8 @@ ${step.prompt}` : step.prompt;
|
|
|
760
808
|
continue;
|
|
761
809
|
}
|
|
762
810
|
const output = extractText(promptRes.data?.parts ?? []);
|
|
763
|
-
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model:
|
|
764
|
-
recordRun(context.directory, step.agent,
|
|
811
|
+
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: output || "(no text output)", duration_ms: Date.now() - stepStart, success: true });
|
|
812
|
+
recordRun(context.directory, step.agent, "", taskType, true, Date.now() - stepStart);
|
|
765
813
|
carryContext = output;
|
|
766
814
|
}
|
|
767
815
|
} finally {
|
|
@@ -795,7 +843,6 @@ function createDelegateTool(client) {
|
|
|
795
843
|
async execute(args, context) {
|
|
796
844
|
const startTime = Date.now();
|
|
797
845
|
const taskType = normalizeTaskType(args.task_type, args.agent);
|
|
798
|
-
const routing = routeModel(context.directory, taskType);
|
|
799
846
|
const retryAttempts = typeof args.retry_attempts === "number" ? args.retry_attempts : 1;
|
|
800
847
|
const maxRetries = Math.max(0, Math.floor(retryAttempts));
|
|
801
848
|
const createRes = await client.session.create({
|
|
@@ -829,7 +876,6 @@ ${args.prompt}` : args.prompt;
|
|
|
829
876
|
path: { id: childId },
|
|
830
877
|
body: {
|
|
831
878
|
agent: args.agent,
|
|
832
|
-
model: routing.model,
|
|
833
879
|
parts: [{ type: "text", text: fullPrompt }],
|
|
834
880
|
tools: { question: false }
|
|
835
881
|
},
|
|
@@ -840,41 +886,41 @@ ${args.prompt}` : args.prompt;
|
|
|
840
886
|
retriesUsed++;
|
|
841
887
|
}
|
|
842
888
|
if (!promptRes || promptRes.error) {
|
|
843
|
-
recordRun(context.directory, args.agent,
|
|
889
|
+
recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
|
|
844
890
|
return JSON.stringify({
|
|
845
891
|
agent: args.agent,
|
|
846
892
|
session_id: childId,
|
|
847
893
|
success: false,
|
|
848
894
|
error: `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`,
|
|
849
895
|
task_type: taskType,
|
|
850
|
-
model:
|
|
896
|
+
model: "",
|
|
851
897
|
retries_used: retriesUsed,
|
|
852
898
|
duration_ms: Date.now() - startTime
|
|
853
899
|
});
|
|
854
900
|
}
|
|
855
901
|
const info = promptRes.data?.info;
|
|
856
902
|
if (info?.error) {
|
|
857
|
-
recordRun(context.directory, args.agent,
|
|
903
|
+
recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
|
|
858
904
|
return JSON.stringify({
|
|
859
905
|
agent: args.agent,
|
|
860
906
|
session_id: childId,
|
|
861
907
|
success: false,
|
|
862
908
|
error: `Agent error: ${JSON.stringify(info.error)}`,
|
|
863
909
|
task_type: taskType,
|
|
864
|
-
model:
|
|
910
|
+
model: "",
|
|
865
911
|
retries_used: retriesUsed,
|
|
866
912
|
duration_ms: Date.now() - startTime
|
|
867
913
|
});
|
|
868
914
|
}
|
|
869
915
|
const output = extractText2(promptRes.data?.parts ?? []);
|
|
870
|
-
recordRun(context.directory, args.agent,
|
|
916
|
+
recordRun(context.directory, args.agent, "", taskType, true, Date.now() - startTime);
|
|
871
917
|
return JSON.stringify({
|
|
872
918
|
agent: args.agent,
|
|
873
919
|
session_id: childId,
|
|
874
920
|
success: true,
|
|
875
921
|
output: output || "(no text output)",
|
|
876
922
|
task_type: taskType,
|
|
877
|
-
model:
|
|
923
|
+
model: "",
|
|
878
924
|
retries_used: retriesUsed,
|
|
879
925
|
duration_ms: Date.now() - startTime
|
|
880
926
|
});
|
|
@@ -884,28 +930,28 @@ ${args.prompt}` : args.prompt;
|
|
|
884
930
|
|
|
885
931
|
// src/tools/repo-memory.ts
|
|
886
932
|
import { tool as tool6 } from "@opencode-ai/plugin";
|
|
887
|
-
import { readFileSync as
|
|
888
|
-
import { join as
|
|
933
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
934
|
+
import { join as join6 } from "path";
|
|
889
935
|
var MEMORY_FILE = "MEMORY.json";
|
|
890
936
|
function memoryPath(directory) {
|
|
891
|
-
return
|
|
937
|
+
return join6(codebaseDir(directory), MEMORY_FILE);
|
|
892
938
|
}
|
|
893
939
|
function emptyMemory() {
|
|
894
940
|
return { version: "1.0", last_updated: new Date().toISOString(), nodes: {} };
|
|
895
941
|
}
|
|
896
942
|
function readMemory(directory) {
|
|
897
943
|
const p = memoryPath(directory);
|
|
898
|
-
if (!
|
|
944
|
+
if (!existsSync6(p))
|
|
899
945
|
return emptyMemory();
|
|
900
946
|
try {
|
|
901
|
-
return JSON.parse(
|
|
947
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
902
948
|
} catch {
|
|
903
949
|
return emptyMemory();
|
|
904
950
|
}
|
|
905
951
|
}
|
|
906
952
|
function writeMemory(directory, memory) {
|
|
907
953
|
const base = codebaseDir(directory);
|
|
908
|
-
if (!
|
|
954
|
+
if (!existsSync6(base))
|
|
909
955
|
mkdirSync3(base, { recursive: true });
|
|
910
956
|
memory.last_updated = new Date().toISOString();
|
|
911
957
|
writeFileSync6(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
|
|
@@ -985,25 +1031,25 @@ var repoMemoryTool = tool6({
|
|
|
985
1031
|
|
|
986
1032
|
// src/tools/failure-replay.ts
|
|
987
1033
|
import { tool as tool7 } from "@opencode-ai/plugin";
|
|
988
|
-
import { readFileSync as
|
|
989
|
-
import { join as
|
|
1034
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
1035
|
+
import { join as join7 } from "path";
|
|
990
1036
|
var FAILURES_FILE = "FAILURES.json";
|
|
991
1037
|
function failuresPath(directory) {
|
|
992
|
-
return
|
|
1038
|
+
return join7(codebaseDir(directory), FAILURES_FILE);
|
|
993
1039
|
}
|
|
994
1040
|
function readStore(directory) {
|
|
995
1041
|
const p = failuresPath(directory);
|
|
996
|
-
if (!
|
|
1042
|
+
if (!existsSync7(p))
|
|
997
1043
|
return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
|
|
998
1044
|
try {
|
|
999
|
-
return JSON.parse(
|
|
1045
|
+
return JSON.parse(readFileSync7(p, "utf-8"));
|
|
1000
1046
|
} catch {
|
|
1001
1047
|
return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
|
|
1002
1048
|
}
|
|
1003
1049
|
}
|
|
1004
1050
|
function writeStore(directory, store) {
|
|
1005
1051
|
const base = codebaseDir(directory);
|
|
1006
|
-
if (!
|
|
1052
|
+
if (!existsSync7(base))
|
|
1007
1053
|
mkdirSync4(base, { recursive: true });
|
|
1008
1054
|
store.last_updated = new Date().toISOString();
|
|
1009
1055
|
writeFileSync7(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
@@ -1090,17 +1136,17 @@ var failureReplayTool = tool7({
|
|
|
1090
1136
|
|
|
1091
1137
|
// src/tools/decision-trace.ts
|
|
1092
1138
|
import { tool as tool8 } from "@opencode-ai/plugin";
|
|
1093
|
-
import { readFileSync as
|
|
1094
|
-
import { join as
|
|
1139
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, appendFileSync } from "fs";
|
|
1140
|
+
import { join as join8 } from "path";
|
|
1095
1141
|
var DECISIONS_FILE = "DECISIONS.jsonl";
|
|
1096
1142
|
function decisionsPath(directory) {
|
|
1097
|
-
return
|
|
1143
|
+
return join8(codebaseDir(directory), DECISIONS_FILE);
|
|
1098
1144
|
}
|
|
1099
1145
|
function readDecisions(directory) {
|
|
1100
1146
|
const p = decisionsPath(directory);
|
|
1101
|
-
if (!
|
|
1147
|
+
if (!existsSync8(p))
|
|
1102
1148
|
return [];
|
|
1103
|
-
return
|
|
1149
|
+
return readFileSync8(p, "utf-8").split(`
|
|
1104
1150
|
`).filter((l) => l.trim()).map((l) => {
|
|
1105
1151
|
try {
|
|
1106
1152
|
return JSON.parse(l);
|
|
@@ -1140,7 +1186,7 @@ var decisionTraceTool = tool8({
|
|
|
1140
1186
|
case "record": {
|
|
1141
1187
|
if (!args.entry)
|
|
1142
1188
|
return JSON.stringify({ error: "entry required" });
|
|
1143
|
-
if (!
|
|
1189
|
+
if (!existsSync8(base))
|
|
1144
1190
|
mkdirSync5(base, { recursive: true });
|
|
1145
1191
|
const entry = { ...args.entry, timestamp: new Date().toISOString() };
|
|
1146
1192
|
appendFileSync(decisionsPath(dir), JSON.stringify(entry) + `
|
|
@@ -1175,25 +1221,25 @@ var decisionTraceTool = tool8({
|
|
|
1175
1221
|
|
|
1176
1222
|
// src/tools/volatility-map.ts
|
|
1177
1223
|
import { tool as tool9 } from "@opencode-ai/plugin";
|
|
1178
|
-
import { readFileSync as
|
|
1179
|
-
import { join as
|
|
1224
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
1225
|
+
import { join as join9 } from "path";
|
|
1180
1226
|
var VOLATILITY_FILE = "VOLATILITY.json";
|
|
1181
1227
|
function volatilityPath(directory) {
|
|
1182
|
-
return
|
|
1228
|
+
return join9(codebaseDir(directory), VOLATILITY_FILE);
|
|
1183
1229
|
}
|
|
1184
1230
|
function readStore2(directory) {
|
|
1185
1231
|
const p = volatilityPath(directory);
|
|
1186
|
-
if (!
|
|
1232
|
+
if (!existsSync9(p))
|
|
1187
1233
|
return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
|
|
1188
1234
|
try {
|
|
1189
|
-
return JSON.parse(
|
|
1235
|
+
return JSON.parse(readFileSync9(p, "utf-8"));
|
|
1190
1236
|
} catch {
|
|
1191
1237
|
return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
|
|
1192
1238
|
}
|
|
1193
1239
|
}
|
|
1194
1240
|
function writeStore2(directory, store) {
|
|
1195
1241
|
const base = codebaseDir(directory);
|
|
1196
|
-
if (!
|
|
1242
|
+
if (!existsSync9(base))
|
|
1197
1243
|
mkdirSync6(base, { recursive: true });
|
|
1198
1244
|
store.last_updated = new Date().toISOString();
|
|
1199
1245
|
writeFileSync9(volatilityPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
@@ -1283,25 +1329,25 @@ var volatilityMapTool = tool9({
|
|
|
1283
1329
|
|
|
1284
1330
|
// src/tools/policy-engine.ts
|
|
1285
1331
|
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
1286
|
-
import { readFileSync as
|
|
1287
|
-
import { join as
|
|
1332
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync10, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
1333
|
+
import { join as join10 } from "path";
|
|
1288
1334
|
var POLICIES_FILE = "POLICIES.json";
|
|
1289
1335
|
function policiesPath(directory) {
|
|
1290
|
-
return
|
|
1336
|
+
return join10(codebaseDir(directory), POLICIES_FILE);
|
|
1291
1337
|
}
|
|
1292
1338
|
function readStore3(directory) {
|
|
1293
1339
|
const p = policiesPath(directory);
|
|
1294
|
-
if (!
|
|
1340
|
+
if (!existsSync10(p))
|
|
1295
1341
|
return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
|
|
1296
1342
|
try {
|
|
1297
|
-
return JSON.parse(
|
|
1343
|
+
return JSON.parse(readFileSync10(p, "utf-8"));
|
|
1298
1344
|
} catch {
|
|
1299
1345
|
return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
|
|
1300
1346
|
}
|
|
1301
1347
|
}
|
|
1302
1348
|
function writeStore3(directory, store) {
|
|
1303
1349
|
const base = codebaseDir(directory);
|
|
1304
|
-
if (!
|
|
1350
|
+
if (!existsSync10(base))
|
|
1305
1351
|
mkdirSync7(base, { recursive: true });
|
|
1306
1352
|
store.last_updated = new Date().toISOString();
|
|
1307
1353
|
writeFileSync10(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
@@ -1386,7 +1432,7 @@ var policyEngineTool = tool10({
|
|
|
1386
1432
|
|
|
1387
1433
|
// src/tools/hash-edit.ts
|
|
1388
1434
|
import { tool as tool11 } from "@opencode-ai/plugin";
|
|
1389
|
-
import { readFileSync as
|
|
1435
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
1390
1436
|
import { createHash } from "crypto";
|
|
1391
1437
|
var hashEditTool = tool11({
|
|
1392
1438
|
description: "Reliable file editing with content verification. Takes a target content, its expected MD5 hash, and replacement content. Only applies if the hash matches, preventing edits on stale file versions.",
|
|
@@ -1400,7 +1446,7 @@ var hashEditTool = tool11({
|
|
|
1400
1446
|
const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
|
|
1401
1447
|
let content;
|
|
1402
1448
|
try {
|
|
1403
|
-
content =
|
|
1449
|
+
content = readFileSync11(fullPath, "utf-8");
|
|
1404
1450
|
} catch (e) {
|
|
1405
1451
|
return `Error: Could not read file ${args.filePath}`;
|
|
1406
1452
|
}
|
|
@@ -1421,8 +1467,8 @@ var hashEditTool = tool11({
|
|
|
1421
1467
|
|
|
1422
1468
|
// src/tools/council.ts
|
|
1423
1469
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
1424
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
1425
|
-
import { join as
|
|
1470
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
|
|
1471
|
+
import { join as join11 } from "path";
|
|
1426
1472
|
function createCouncilTool(client) {
|
|
1427
1473
|
return tool12({
|
|
1428
1474
|
description: "Run an ensemble of agents (Council) on the same task to reach consensus or compare approaches. Runs 3 specialized agents in parallel and returns their synthesized outputs.",
|
|
@@ -1431,7 +1477,7 @@ function createCouncilTool(client) {
|
|
|
1431
1477
|
agents: tool12.schema.array(tool12.schema.string()).optional()
|
|
1432
1478
|
},
|
|
1433
1479
|
async execute(args, context) {
|
|
1434
|
-
const agents = args.agents || ["architect", "reviewer", "coder"];
|
|
1480
|
+
const agents = args.agents || ["architect", "reviewer", "backend-coder"];
|
|
1435
1481
|
const tasks = agents.map((agent) => ({
|
|
1436
1482
|
agent,
|
|
1437
1483
|
prompt: `TASK: ${args.task}
|
|
@@ -1493,9 +1539,9 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
|
|
|
1493
1539
|
function persistCouncilResult(directory, payload) {
|
|
1494
1540
|
try {
|
|
1495
1541
|
const base = codebaseDir(directory);
|
|
1496
|
-
if (!
|
|
1542
|
+
if (!existsSync11(base))
|
|
1497
1543
|
mkdirSync8(base, { recursive: true });
|
|
1498
|
-
const path =
|
|
1544
|
+
const path = join11(base, "COUNCILS.jsonl");
|
|
1499
1545
|
appendFileSync2(path, JSON.stringify(payload) + `
|
|
1500
1546
|
`, "utf-8");
|
|
1501
1547
|
} catch {}
|
|
@@ -1503,8 +1549,8 @@ function persistCouncilResult(directory, payload) {
|
|
|
1503
1549
|
|
|
1504
1550
|
// src/tools/context-generator.ts
|
|
1505
1551
|
import { tool as tool13 } from "@opencode-ai/plugin";
|
|
1506
|
-
import { writeFileSync as writeFileSync12, existsSync as
|
|
1507
|
-
import { join as
|
|
1552
|
+
import { writeFileSync as writeFileSync12, existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync2, statSync } from "fs";
|
|
1553
|
+
import { join as join12 } from "path";
|
|
1508
1554
|
var contextGeneratorTool = tool13({
|
|
1509
1555
|
description: "Auto-generate or update hierarchical context files (AGENTS.md, CLAUDE.md) throughout the project. These files provide critical grounding for AI agents.",
|
|
1510
1556
|
args: {
|
|
@@ -1513,20 +1559,20 @@ var contextGeneratorTool = tool13({
|
|
|
1513
1559
|
},
|
|
1514
1560
|
async execute(args, context) {
|
|
1515
1561
|
const root = context.directory;
|
|
1516
|
-
const target = args.targetDir ?
|
|
1517
|
-
if (!
|
|
1562
|
+
const target = args.targetDir ? join12(root, args.targetDir) : root;
|
|
1563
|
+
if (!existsSync12(target)) {
|
|
1518
1564
|
return `Error: Directory ${target} does not exist.`;
|
|
1519
1565
|
}
|
|
1520
|
-
const agentsMdPath =
|
|
1521
|
-
if (
|
|
1566
|
+
const agentsMdPath = join12(target, "AGENTS.md");
|
|
1567
|
+
if (existsSync12(agentsMdPath) && !args.force) {
|
|
1522
1568
|
return `AGENTS.md already exists in ${target}. Use force: true to overwrite.`;
|
|
1523
1569
|
}
|
|
1524
|
-
const pkgPath =
|
|
1570
|
+
const pkgPath = join12(root, "package.json");
|
|
1525
1571
|
let projectName = "Project";
|
|
1526
1572
|
let techStack = "Unknown";
|
|
1527
|
-
if (
|
|
1573
|
+
if (existsSync12(pkgPath)) {
|
|
1528
1574
|
try {
|
|
1529
|
-
const pkg = JSON.parse(
|
|
1575
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
1530
1576
|
projectName = pkg.name || projectName;
|
|
1531
1577
|
techStack = Object.keys(pkg.dependencies || {}).slice(0, 5).join(", ");
|
|
1532
1578
|
} catch {}
|
|
@@ -1544,7 +1590,7 @@ var contextGeneratorTool = tool13({
|
|
|
1544
1590
|
|
|
1545
1591
|
## Directory Map
|
|
1546
1592
|
${readdirSync2(target).slice(0, 10).map((f) => {
|
|
1547
|
-
const s = statSync(
|
|
1593
|
+
const s = statSync(join12(target, f));
|
|
1548
1594
|
return `- \`${f}\`${s.isDirectory() ? "/" : ""} : [Description]`;
|
|
1549
1595
|
}).join(`
|
|
1550
1596
|
`)}
|
|
@@ -1559,10 +1605,10 @@ Generated by FlowDeck Context Generator.
|
|
|
1559
1605
|
|
|
1560
1606
|
// src/tools/create-skill.ts
|
|
1561
1607
|
import { tool as tool14 } from "@opencode-ai/plugin";
|
|
1562
|
-
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync13, existsSync as
|
|
1563
|
-
import { join as
|
|
1608
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync13, existsSync as existsSync13 } from "fs";
|
|
1609
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
1564
1610
|
import { fileURLToPath } from "url";
|
|
1565
|
-
var SKILLS_DIR =
|
|
1611
|
+
var SKILLS_DIR = join13(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
|
|
1566
1612
|
var createSkillTool = tool14({
|
|
1567
1613
|
description: "Create a new reusable skill in the FlowDeck skill library (src/skills/). " + "Use this when you discover a repeatable pattern, solve a novel problem with human guidance, " + "or want to capture domain knowledge for future sessions.",
|
|
1568
1614
|
args: {
|
|
@@ -1572,9 +1618,9 @@ var createSkillTool = tool14({
|
|
|
1572
1618
|
tags: tool14.schema.array(tool14.schema.string()).optional().describe("Optional tags for categorisation, e.g. ['performance', 'typescript']")
|
|
1573
1619
|
},
|
|
1574
1620
|
async execute(args) {
|
|
1575
|
-
const skillDir =
|
|
1576
|
-
const skillFile =
|
|
1577
|
-
if (
|
|
1621
|
+
const skillDir = join13(SKILLS_DIR, args.name);
|
|
1622
|
+
const skillFile = join13(skillDir, "SKILL.md");
|
|
1623
|
+
if (existsSync13(skillFile)) {
|
|
1578
1624
|
return `Skill '${args.name}' already exists at ${skillFile}.
|
|
1579
1625
|
` + `Use a different name or delete the existing skill directory first.`;
|
|
1580
1626
|
}
|
|
@@ -1602,8 +1648,8 @@ origin: FlowDeck (self-learned)${tagLine}
|
|
|
1602
1648
|
|
|
1603
1649
|
// src/tools/reflect.ts
|
|
1604
1650
|
import { tool as tool15 } from "@opencode-ai/plugin";
|
|
1605
|
-
import { existsSync as
|
|
1606
|
-
import { join as
|
|
1651
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
1652
|
+
import { join as join14 } from "path";
|
|
1607
1653
|
var MAX_ARTIFACT_BYTES = 4000;
|
|
1608
1654
|
function tail(text, maxBytes) {
|
|
1609
1655
|
if (text.length <= maxBytes)
|
|
@@ -1632,11 +1678,11 @@ var reflectTool = tool15({
|
|
|
1632
1678
|
];
|
|
1633
1679
|
let found = 0;
|
|
1634
1680
|
for (const [rel, label] of ARTIFACT_PATHS) {
|
|
1635
|
-
const full =
|
|
1636
|
-
if (!
|
|
1681
|
+
const full = join14(root, rel);
|
|
1682
|
+
if (!existsSync14(full))
|
|
1637
1683
|
continue;
|
|
1638
1684
|
try {
|
|
1639
|
-
const raw =
|
|
1685
|
+
const raw = readFileSync13(full, "utf-8").trim();
|
|
1640
1686
|
if (!raw)
|
|
1641
1687
|
continue;
|
|
1642
1688
|
const count = raw.split(`
|
|
@@ -1660,13 +1706,13 @@ import { tool as tool16 } from "@opencode-ai/plugin";
|
|
|
1660
1706
|
|
|
1661
1707
|
// src/services/memory-store.ts
|
|
1662
1708
|
import { Database } from "bun:sqlite";
|
|
1663
|
-
import { existsSync as
|
|
1664
|
-
import { join as
|
|
1709
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
1710
|
+
import { join as join15 } from "path";
|
|
1665
1711
|
import { homedir } from "os";
|
|
1666
|
-
var MEMORY_DIR =
|
|
1667
|
-
var DB_PATH =
|
|
1712
|
+
var MEMORY_DIR = join15(homedir(), ".flowdeck-memory");
|
|
1713
|
+
var DB_PATH = join15(MEMORY_DIR, "memory.db");
|
|
1668
1714
|
function ensureDir() {
|
|
1669
|
-
if (!
|
|
1715
|
+
if (!existsSync15(MEMORY_DIR)) {
|
|
1670
1716
|
mkdirSync10(MEMORY_DIR, { recursive: true });
|
|
1671
1717
|
}
|
|
1672
1718
|
}
|
|
@@ -1945,16 +1991,16 @@ var memorySearchTool = tool16({
|
|
|
1945
1991
|
// src/tools/memory-status.ts
|
|
1946
1992
|
import { tool as tool17 } from "@opencode-ai/plugin";
|
|
1947
1993
|
import { Database as Database2 } from "bun:sqlite";
|
|
1948
|
-
import { existsSync as
|
|
1949
|
-
import { join as
|
|
1994
|
+
import { existsSync as existsSync16 } from "fs";
|
|
1995
|
+
import { join as join16 } from "path";
|
|
1950
1996
|
import { homedir as homedir2 } from "os";
|
|
1951
|
-
var DB_PATH2 =
|
|
1997
|
+
var DB_PATH2 = join16(homedir2(), ".flowdeck-memory", "memory.db");
|
|
1952
1998
|
var memoryStatusTool = tool17({
|
|
1953
1999
|
description: "Check FlowDeck memory database status, statistics, and recent sessions",
|
|
1954
2000
|
args: {},
|
|
1955
2001
|
async execute(_args, _context) {
|
|
1956
2002
|
try {
|
|
1957
|
-
const exists =
|
|
2003
|
+
const exists = existsSync16(DB_PATH2);
|
|
1958
2004
|
const result = {
|
|
1959
2005
|
database_exists: exists,
|
|
1960
2006
|
path: DB_PATH2,
|
|
@@ -2113,6 +2159,49 @@ var memoryHook = {
|
|
|
2113
2159
|
// src/hooks/guard-rails.ts
|
|
2114
2160
|
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
2115
2161
|
import { join as join18 } from "path";
|
|
2162
|
+
|
|
2163
|
+
// src/config/loader.ts
|
|
2164
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
2165
|
+
import { join as join17 } from "path";
|
|
2166
|
+
import { homedir as homedir3 } from "os";
|
|
2167
|
+
var CONFIG_FILENAME = "flowdeck.json";
|
|
2168
|
+
function getGlobalConfigDir() {
|
|
2169
|
+
return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join17(process.env.XDG_CONFIG_HOME, "opencode") : join17(homedir3(), ".config", "opencode"));
|
|
2170
|
+
}
|
|
2171
|
+
function loadFlowDeckConfig(directory) {
|
|
2172
|
+
const candidates = [];
|
|
2173
|
+
if (directory) {
|
|
2174
|
+
candidates.push(join17(directory, ".opencode", CONFIG_FILENAME));
|
|
2175
|
+
}
|
|
2176
|
+
candidates.push(join17(getGlobalConfigDir(), CONFIG_FILENAME));
|
|
2177
|
+
for (const configPath of candidates) {
|
|
2178
|
+
if (existsSync17(configPath)) {
|
|
2179
|
+
try {
|
|
2180
|
+
const content = readFileSync14(configPath, "utf-8");
|
|
2181
|
+
return JSON.parse(content);
|
|
2182
|
+
} catch {
|
|
2183
|
+
console.warn(`[flowdeck] Failed to load config from ${configPath}`);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return {};
|
|
2188
|
+
}
|
|
2189
|
+
function resolveDesignFirstConfig(config) {
|
|
2190
|
+
return {
|
|
2191
|
+
enabled: config.designFirst?.enabled ?? true,
|
|
2192
|
+
enforcement: config.designFirst?.enforcement ?? "strict",
|
|
2193
|
+
requireApprovalBeforeImplementation: config.designFirst?.requireApprovalBeforeImplementation ?? true,
|
|
2194
|
+
modelOverrides: config.designFirst?.modelOverrides ?? {},
|
|
2195
|
+
defaultSkillsByTaskType: config.designFirst?.defaultSkillsByTaskType ?? {
|
|
2196
|
+
"landing-page": ["landing-page-design", "wireframe-planning", "design-system-definition", "frontend-handoff"],
|
|
2197
|
+
dashboard: ["dashboard-design", "ui-ux-planning", "wireframe-planning", "responsive-review"],
|
|
2198
|
+
"admin-panel": ["ui-ux-planning", "wireframe-planning", "design-system-definition", "frontend-handoff"],
|
|
2199
|
+
"app-screen": ["app-shell-design", "ui-ux-planning", "wireframe-planning", "responsive-review"],
|
|
2200
|
+
"general-ui": ["ui-ux-planning", "wireframe-planning", "design-system-definition", "frontend-handoff"]
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
// src/hooks/guard-rails.ts
|
|
2116
2205
|
var PLANNING_DIR2 = ".planning";
|
|
2117
2206
|
var CONFIG_FILE = "config.json";
|
|
2118
2207
|
var STATE_FILE2 = "STATE.md";
|
|
@@ -2198,6 +2287,10 @@ async function guardRailsHook(ctx, input, _output) {
|
|
|
2198
2287
|
if (execMode === "guarded") {
|
|
2199
2288
|
throw new Error(`[flowdeck] GUARDED MODE: edit will proceed but flag for human review.`);
|
|
2200
2289
|
}
|
|
2290
|
+
const designGateMessage = getDesignGateMessage(dir);
|
|
2291
|
+
if (designGateMessage) {
|
|
2292
|
+
throw new Error(designGateMessage);
|
|
2293
|
+
}
|
|
2201
2294
|
const effectiveSeverity = getEffectiveSeverity(configPath, statePath2);
|
|
2202
2295
|
if (effectiveSeverity === null)
|
|
2203
2296
|
return;
|
|
@@ -2220,6 +2313,31 @@ async function guardRailsHook(ctx, input, _output) {
|
|
|
2220
2313
|
}
|
|
2221
2314
|
}
|
|
2222
2315
|
}
|
|
2316
|
+
function getDesignGateMessage(dir) {
|
|
2317
|
+
const designConfig = resolveDesignFirstConfig(loadFlowDeckConfig(dir));
|
|
2318
|
+
if (!designConfig.enabled || !designConfig.requireApprovalBeforeImplementation)
|
|
2319
|
+
return null;
|
|
2320
|
+
const state = readPlanningState(dir);
|
|
2321
|
+
if (state.design_override && state.design_override_reason && state.design_override_reason.trim().length > 0)
|
|
2322
|
+
return null;
|
|
2323
|
+
const designApproved = state.design_stage === "handoff_complete" && state.design_approved;
|
|
2324
|
+
if (state.requires_design_first || state.task_type && isUiHeavyTask(state.task_type) || planSuggestsUiHeavy(dir, state.phase || 1)) {
|
|
2325
|
+
if (designApproved)
|
|
2326
|
+
return null;
|
|
2327
|
+
if (designConfig.enforcement === "advisory") {
|
|
2328
|
+
return "[flowdeck] WARNING: UI-heavy task detected without approved design handoff. Run /fd-design --mode=draft first.";
|
|
2329
|
+
}
|
|
2330
|
+
return "[flowdeck] BLOCK: UI-heavy task requires approved design handoff. Run /fd-design --mode=draft or set explicit design override in STATE.md.";
|
|
2331
|
+
}
|
|
2332
|
+
return null;
|
|
2333
|
+
}
|
|
2334
|
+
function planSuggestsUiHeavy(dir, phase) {
|
|
2335
|
+
const planPath = phasePlanPath(dir, phase);
|
|
2336
|
+
if (!existsSync18(planPath))
|
|
2337
|
+
return false;
|
|
2338
|
+
const planContent = readFileSync15(planPath, "utf-8");
|
|
2339
|
+
return isUiHeavyTask(planContent);
|
|
2340
|
+
}
|
|
2223
2341
|
function effectiveSeverity(configPath, statePath2) {
|
|
2224
2342
|
if (existsSync18(configPath)) {
|
|
2225
2343
|
try {
|
|
@@ -2332,12 +2450,37 @@ function checkArchConstraint(directory, filePath) {
|
|
|
2332
2450
|
function checkPhaseEnforcement(directory) {
|
|
2333
2451
|
try {
|
|
2334
2452
|
const state = readPlanningState(directory);
|
|
2453
|
+
const flowdeckConfig = resolveDesignFirstConfig(loadFlowDeckConfig(directory));
|
|
2335
2454
|
if (state.phase > 0 && state.phase < 3) {
|
|
2336
2455
|
return `FLOWDECK [phase-gate]: writing to codebase is blocked in phase ${state.phase} (${state.phase === 1 ? "discuss" : "plan"}). Run /fd-plan --confirm to enter execute phase.`;
|
|
2337
2456
|
}
|
|
2457
|
+
if (flowdeckConfig.enabled && flowdeckConfig.requireApprovalBeforeImplementation && isUiDesignApprovalRequired(directory)) {
|
|
2458
|
+
if (flowdeckConfig.enforcement === "advisory") {
|
|
2459
|
+
return `FLOWDECK [design-gate]: advisory design-first mode detected missing approval. Run /fd-design --mode=draft or set design_override=true in STATE.md.`;
|
|
2460
|
+
}
|
|
2461
|
+
return `FLOWDECK [design-gate]: UI-heavy task requires approved design handoff before implementation. Run /fd-design --mode=draft and ensure design_stage=handoff_complete + design_approved=true, or set explicit design_override with reason.`;
|
|
2462
|
+
}
|
|
2338
2463
|
} catch {}
|
|
2339
2464
|
return null;
|
|
2340
2465
|
}
|
|
2466
|
+
function isUiDesignApprovalRequired(directory) {
|
|
2467
|
+
const state = readPlanningState(directory);
|
|
2468
|
+
if (state.design_override && state.design_override_reason && state.design_override_reason.trim().length > 0)
|
|
2469
|
+
return false;
|
|
2470
|
+
if (state.requires_design_first) {
|
|
2471
|
+
return !(state.design_stage === "handoff_complete" && state.design_approved);
|
|
2472
|
+
}
|
|
2473
|
+
if (state.task_type && isUiHeavyTask(state.task_type)) {
|
|
2474
|
+
return !(state.design_stage === "handoff_complete" && state.design_approved);
|
|
2475
|
+
}
|
|
2476
|
+
const planPath = phasePlanPath(directory, state.phase || 1);
|
|
2477
|
+
if (!existsSync19(planPath))
|
|
2478
|
+
return false;
|
|
2479
|
+
const planContent = readFileSync16(planPath, "utf-8");
|
|
2480
|
+
if (!isUiHeavyTask(planContent))
|
|
2481
|
+
return false;
|
|
2482
|
+
return !(state.design_stage === "handoff_complete" && state.design_approved);
|
|
2483
|
+
}
|
|
2341
2484
|
async function toolGuardHook(ctx, input, output) {
|
|
2342
2485
|
if (!IS_ENABLED())
|
|
2343
2486
|
return;
|
|
@@ -3106,7 +3249,9 @@ function blockMessage(toolName) {
|
|
|
3106
3249
|
` + `The orchestrator is a coordinator — it must delegate all implementation work.
|
|
3107
3250
|
|
|
3108
3251
|
` + `Use the \`delegate\` tool to hand this off:
|
|
3109
|
-
` + ` delegate({ agent: "@coder", prompt: "..." }) — code writing / editing
|
|
3252
|
+
` + ` delegate({ agent: "@backend-coder", prompt: "..." }) — backend code writing / editing
|
|
3253
|
+
` + ` delegate({ agent: "@frontend-coder", prompt: "..." }) — frontend code writing / editing
|
|
3254
|
+
` + ` delegate({ agent: "@devops", prompt: "..." }) — CI/CD, deploy, and infra changes
|
|
3110
3255
|
` + ` delegate({ agent: "@mapper", prompt: "..." }) — codebase mapping
|
|
3111
3256
|
` + ` delegate({ agent: "@researcher", prompt: "..." }) — research / file analysis
|
|
3112
3257
|
` + ` delegate({ agent: "@tester", prompt: "..." }) — tests / commands
|
|
@@ -3308,11 +3453,22 @@ For each incomplete step in PLAN.md:
|
|
|
3308
3453
|
5. Re-read STATE.md to confirm state
|
|
3309
3454
|
6. Move to the next incomplete step
|
|
3310
3455
|
|
|
3456
|
+
## Implementation Routing
|
|
3457
|
+
|
|
3458
|
+
When a plan step requires implementation, route to a role-specific agent:
|
|
3459
|
+
- Use @backend-coder for server, API, business logic, database, and non-UI application code.
|
|
3460
|
+
- Use @frontend-coder for UI components, client state, styling, and interaction behavior.
|
|
3461
|
+
- Use @devops for CI/CD workflows, deployment, infrastructure, runtime config, and operations scripts.
|
|
3462
|
+
- If a step mixes multiple domains, split it into multiple delegated tasks by domain.
|
|
3463
|
+
|
|
3311
3464
|
## Agent Team
|
|
3312
3465
|
|
|
3313
3466
|
| Agent | Invoke | Best For |
|
|
3314
3467
|
|-------|--------|----------|
|
|
3315
|
-
|
|
|
3468
|
+
| Design | @design | Discovery, UX planning, wireframes, visual system, implementation handoff, design fidelity review |
|
|
3469
|
+
| Backend Coder | @backend-coder | Backend code implementation |
|
|
3470
|
+
| Frontend Coder | @frontend-coder | Frontend code implementation |
|
|
3471
|
+
| DevOps | @devops | CI/CD and infrastructure implementation |
|
|
3316
3472
|
| Researcher | @researcher | API docs, library usage |
|
|
3317
3473
|
| Tester | @tester | Writing and running tests |
|
|
3318
3474
|
| Reviewer | @reviewer | Code quality review |
|
|
@@ -3323,7 +3479,6 @@ For each incomplete step in PLAN.md:
|
|
|
3323
3479
|
| Code Explorer | @code-explorer | Reading unfamiliar code |
|
|
3324
3480
|
| Debug Specialist | @debug-specialist | Root cause analysis |
|
|
3325
3481
|
| Build Resolver | @build-error-resolver | Build/compile failures |
|
|
3326
|
-
| Parallel Coordinator | @parallel-coordinator | Multi-track parallel work |
|
|
3327
3482
|
| Doc Updater | @doc-updater | Updating existing docs |
|
|
3328
3483
|
| Task Splitter | @task-splitter | Decomposing complex tasks |
|
|
3329
3484
|
| Discusser | @discusser | Requirements extraction |
|
|
@@ -3336,12 +3491,13 @@ For each incomplete step in PLAN.md:
|
|
|
3336
3491
|
## Phase State Machine
|
|
3337
3492
|
|
|
3338
3493
|
\`\`\`
|
|
3339
|
-
discuss → plan → execute → review
|
|
3494
|
+
discuss → plan → design (for UI-heavy tasks) → execute → review
|
|
3340
3495
|
\`\`\`
|
|
3341
3496
|
|
|
3342
3497
|
- **discuss**: Requirements extraction with @discusser
|
|
3343
3498
|
- **plan**: Plan creation with @planner, review with @plan-checker
|
|
3344
|
-
- **
|
|
3499
|
+
- **design**: UX structure, wireframe/layout planning, and visual system definition with @design
|
|
3500
|
+
- **execute**: Implementation with @backend-coder, @frontend-coder, @devops, @tester, and @researcher in parallel where possible, only after approved design handoff for UI-heavy tasks
|
|
3345
3501
|
- **review**: Review with @reviewer, @security-auditor
|
|
3346
3502
|
|
|
3347
3503
|
## Tracking
|
|
@@ -3363,7 +3519,7 @@ If a delegated agent fails:
|
|
|
3363
3519
|
3. If still failing, escalate:
|
|
3364
3520
|
|
|
3365
3521
|
\`\`\`
|
|
3366
|
-
BLOCKED:
|
|
3522
|
+
BLOCKED: implementation agent failed on step 3 (add payment endpoint).
|
|
3367
3523
|
Error: [exact error message]
|
|
3368
3524
|
Retried once with clarification. Still failing.
|
|
3369
3525
|
|
|
@@ -3384,11 +3540,26 @@ When a task required unusual human guidance, a novel solution strategy, or expos
|
|
|
3384
3540
|
|
|
3385
3541
|
Do NOT create a skill for routine tasks. Only capture genuinely novel or reusable patterns.`;
|
|
3386
3542
|
var AGENT_DESCRIPTIONS = {
|
|
3387
|
-
|
|
3388
|
-
- Role:
|
|
3543
|
+
design: `@design
|
|
3544
|
+
- Role: Runs design-first workflow for user-facing tasks
|
|
3545
|
+
- Permissions: Read/write files
|
|
3546
|
+
- Best for: UX structure, wireframes, visual direction, tokens, and frontend handoff
|
|
3547
|
+
- **Delegate when:** Task includes website/app/dashboard/admin/user-facing UI work`,
|
|
3548
|
+
"backend-coder": `@backend-coder
|
|
3549
|
+
- Role: Implements backend features and fixes based on confirmed plans
|
|
3389
3550
|
- Permissions: Read/write files
|
|
3390
|
-
- Best for:
|
|
3391
|
-
- **Delegate when:**
|
|
3551
|
+
- Best for: API, services, data layer, and business logic
|
|
3552
|
+
- **Delegate when:** Backend or server-side implementation work`,
|
|
3553
|
+
"frontend-coder": `@frontend-coder
|
|
3554
|
+
- Role: Implements frontend features and fixes based on confirmed plans
|
|
3555
|
+
- Permissions: Read/write files
|
|
3556
|
+
- Best for: UI components, client state, rendering, and interaction behavior
|
|
3557
|
+
- **Delegate when:** Frontend implementation work`,
|
|
3558
|
+
devops: `@devops
|
|
3559
|
+
- Role: Implements DevOps and infrastructure changes based on confirmed plans
|
|
3560
|
+
- Permissions: Read/write files
|
|
3561
|
+
- Best for: CI/CD, deployment config, infra scripts, and runtime operations
|
|
3562
|
+
- **Delegate when:** Infrastructure, pipeline, or operations implementation work`,
|
|
3392
3563
|
researcher: `@researcher
|
|
3393
3564
|
- Role: Researches documentation, APIs, and best practices
|
|
3394
3565
|
- Permissions: Read files
|
|
@@ -3460,11 +3631,6 @@ var AGENT_DESCRIPTIONS = {
|
|
|
3460
3631
|
- Permissions: Read/write files
|
|
3461
3632
|
- Best for: Requirements extraction
|
|
3462
3633
|
- **Delegate when:** Starting new feature or project phase`,
|
|
3463
|
-
"parallel-coordinator": `@parallel-coordinator
|
|
3464
|
-
- Role: Coordinates multi-wave parallel execution
|
|
3465
|
-
- Permissions: Read files
|
|
3466
|
-
- Best for: Multi-track parallel work
|
|
3467
|
-
- **Delegate when:** Need to execute multiple tasks in parallel`,
|
|
3468
3634
|
planner: `@planner
|
|
3469
3635
|
- Role: Creates detailed implementation plans
|
|
3470
3636
|
- Permissions: Read files
|
|
@@ -3766,7 +3932,7 @@ var createPlanCheckerAgent = (model, customPrompt, customAppendPrompt) => {
|
|
|
3766
3932
|
};
|
|
3767
3933
|
|
|
3768
3934
|
// src/agents/coder.ts
|
|
3769
|
-
var
|
|
3935
|
+
var BASE_IMPLEMENTER_PROMPT = `You implement features and fix bugs. You follow the plan exactly. You do not invent requirements.
|
|
3770
3936
|
|
|
3771
3937
|
## Before Writing Code
|
|
3772
3938
|
|
|
@@ -3876,11 +4042,62 @@ After implementing, report:
|
|
|
3876
4042
|
- Tests added or updated
|
|
3877
4043
|
- Any deviations from the plan and why
|
|
3878
4044
|
- Next step ready to execute`;
|
|
3879
|
-
var
|
|
3880
|
-
|
|
4045
|
+
var BACKEND_CODER_PROMPT = `${BASE_IMPLEMENTER_PROMPT}
|
|
4046
|
+
|
|
4047
|
+
## Domain Focus
|
|
4048
|
+
|
|
4049
|
+
Prioritize backend and platform code:
|
|
4050
|
+
- Server handlers, services, repositories, jobs, and business logic
|
|
4051
|
+
- Database and persistence-layer changes
|
|
4052
|
+
- API contracts and boundary validation
|
|
4053
|
+
`;
|
|
4054
|
+
var FRONTEND_CODER_PROMPT = `${BASE_IMPLEMENTER_PROMPT}
|
|
4055
|
+
|
|
4056
|
+
## Domain Focus
|
|
4057
|
+
|
|
4058
|
+
Prioritize frontend implementation quality:
|
|
4059
|
+
- UI components, client state, accessibility, and interaction behavior
|
|
4060
|
+
- Styling consistency with existing design system/tokens
|
|
4061
|
+
- Browser/runtime safety (no server-only assumptions in client code)
|
|
4062
|
+
`;
|
|
4063
|
+
var DEVOPS_PROMPT = `${BASE_IMPLEMENTER_PROMPT}
|
|
4064
|
+
|
|
4065
|
+
## Domain Focus
|
|
4066
|
+
|
|
4067
|
+
Prioritize infrastructure and delivery tasks:
|
|
4068
|
+
- CI/CD workflows, build pipelines, deployment configuration
|
|
4069
|
+
- Environment/runtime configuration and operational scripts
|
|
4070
|
+
- Reliability and rollback safety for production-facing changes
|
|
4071
|
+
`;
|
|
4072
|
+
var createBackendCoderAgent = (model, customPrompt, customAppendPrompt) => {
|
|
4073
|
+
const prompt = resolvePrompt(BACKEND_CODER_PROMPT, customPrompt, customAppendPrompt);
|
|
4074
|
+
return {
|
|
4075
|
+
name: "backend-coder",
|
|
4076
|
+
description: "Implements backend features and fixes based on confirmed plans. Follows existing code patterns and project conventions.",
|
|
4077
|
+
config: {
|
|
4078
|
+
model,
|
|
4079
|
+
temperature: 0.1,
|
|
4080
|
+
prompt
|
|
4081
|
+
}
|
|
4082
|
+
};
|
|
4083
|
+
};
|
|
4084
|
+
var createFrontendCoderAgent = (model, customPrompt, customAppendPrompt) => {
|
|
4085
|
+
const prompt = resolvePrompt(FRONTEND_CODER_PROMPT, customPrompt, customAppendPrompt);
|
|
3881
4086
|
return {
|
|
3882
|
-
name: "coder",
|
|
3883
|
-
description: "Implements features and fixes based on confirmed plans. Follows existing code patterns and project conventions.
|
|
4087
|
+
name: "frontend-coder",
|
|
4088
|
+
description: "Implements frontend features and fixes based on confirmed plans. Follows existing code patterns and project conventions.",
|
|
4089
|
+
config: {
|
|
4090
|
+
model,
|
|
4091
|
+
temperature: 0.1,
|
|
4092
|
+
prompt
|
|
4093
|
+
}
|
|
4094
|
+
};
|
|
4095
|
+
};
|
|
4096
|
+
var createDevopsAgent = (model, customPrompt, customAppendPrompt) => {
|
|
4097
|
+
const prompt = resolvePrompt(DEVOPS_PROMPT, customPrompt, customAppendPrompt);
|
|
4098
|
+
return {
|
|
4099
|
+
name: "devops",
|
|
4100
|
+
description: "Implements DevOps and infrastructure changes based on confirmed plans. Follows existing repo conventions and operational safety practices.",
|
|
3884
4101
|
config: {
|
|
3885
4102
|
model,
|
|
3886
4103
|
temperature: 0.1,
|
|
@@ -4027,6 +4244,13 @@ var REVIEWER_PROMPT = `You review code for correctness, security, and quality. Y
|
|
|
4027
4244
|
4. Apply the checklist below
|
|
4028
4245
|
5. Report by severity — CRITICAL first, then HIGH, MEDIUM, PASS
|
|
4029
4246
|
|
|
4247
|
+
If the task is UI-heavy and a design artifact is available, include design fidelity checks:
|
|
4248
|
+
- visual hierarchy and spacing consistency
|
|
4249
|
+
- CTA flow quality
|
|
4250
|
+
- responsive behavior
|
|
4251
|
+
- accessibility semantics and states
|
|
4252
|
+
- empty/loading/error/success state coverage
|
|
4253
|
+
|
|
4030
4254
|
## Security Checklist — CRITICAL
|
|
4031
4255
|
|
|
4032
4256
|
**Hardcoded credentials:**
|
|
@@ -4231,7 +4455,7 @@ Never fabricate information to appear more helpful.
|
|
|
4231
4455
|
## Scope Boundaries
|
|
4232
4456
|
|
|
4233
4457
|
- Report facts only. Do not make implementation decisions.
|
|
4234
|
-
- Do not write code unless asked. Return research findings for the
|
|
4458
|
+
- Do not write code unless asked. Return research findings for the implementation agent to act on.
|
|
4235
4459
|
- If you find a better approach than what was requested, mention it as an option — do not substitute it.
|
|
4236
4460
|
|
|
4237
4461
|
## Research Areas
|
|
@@ -4351,7 +4575,7 @@ var createWriterAgent = (model, customPrompt, customAppendPrompt) => {
|
|
|
4351
4575
|
};
|
|
4352
4576
|
|
|
4353
4577
|
// src/agents/security-auditor.ts
|
|
4354
|
-
var SECURITY_AUDITOR_PROMPT = `You audit code for security vulnerabilities. You report findings with severity and specific remediation. You do not fix — that is
|
|
4578
|
+
var SECURITY_AUDITOR_PROMPT = `You audit code for security vulnerabilities. You report findings with severity and specific remediation. You do not fix — that is the implementation agent's job (@backend-coder, @frontend-coder, or @devops).
|
|
4355
4579
|
|
|
4356
4580
|
## Audit Scope
|
|
4357
4581
|
|
|
@@ -4454,7 +4678,7 @@ For high/critical vulnerabilities: report exact package, CVE ID, and whether it'
|
|
|
4454
4678
|
|
|
4455
4679
|
## After Finding Issues
|
|
4456
4680
|
|
|
4457
|
-
Report only. Do not fix. Tag @coder with specific remediations for each finding.`;
|
|
4681
|
+
Report only. Do not fix. Tag the appropriate implementation agent (@backend-coder, @frontend-coder, or @devops) with specific remediations for each finding.`;
|
|
4458
4682
|
var createSecurityAuditorAgent = (model, customPrompt, customAppendPrompt) => {
|
|
4459
4683
|
const prompt = resolvePrompt(SECURITY_AUDITOR_PROMPT, customPrompt, customAppendPrompt);
|
|
4460
4684
|
return {
|
|
@@ -4802,7 +5026,7 @@ request → router → UserController.create() → UserService.create() → ❌
|
|
|
4802
5026
|
|
|
4803
5027
|
## Scope
|
|
4804
5028
|
|
|
4805
|
-
Report only. Do not implement the fix. Tag @coder with the recommended fix.`;
|
|
5029
|
+
Report only. Do not implement the fix. Tag the appropriate implementation agent (@backend-coder, @frontend-coder, or @devops) with the recommended fix.`;
|
|
4806
5030
|
var BUILD_ERROR_RESOLVER_PROMPT = `You fix build failures. You read the full error output, find the root cause, and apply the minimum fix to get the build green.
|
|
4807
5031
|
|
|
4808
5032
|
## Diagnostic Commands
|
|
@@ -4905,7 +5129,7 @@ npx tsc --noEmit src/path/to/file.ts
|
|
|
4905
5129
|
|
|
4906
5130
|
- Build fails because of architectural problems → @architect
|
|
4907
5131
|
- A feature is not working correctly → @debug-specialist
|
|
4908
|
-
- Missing functionality needs to be written → @coder`;
|
|
5132
|
+
- Missing functionality needs to be written → @backend-coder/@frontend-coder/@devops`;
|
|
4909
5133
|
var createDebugSpecialistAgent = (model, customPrompt, customAppendPrompt) => {
|
|
4910
5134
|
const prompt = resolvePrompt(DEBUG_SPECIALIST_PROMPT, customPrompt, customAppendPrompt);
|
|
4911
5135
|
return {
|
|
@@ -4932,7 +5156,7 @@ var createBuildErrorResolverAgent = (model, customPrompt, customAppendPrompt) =>
|
|
|
4932
5156
|
};
|
|
4933
5157
|
|
|
4934
5158
|
// src/agents/specialist.ts
|
|
4935
|
-
var TASK_SPLITTER_PROMPT = `You decompose complex tasks into parallel workstreams. You identify dependencies, group independent work into waves, and produce a plan that @
|
|
5159
|
+
var TASK_SPLITTER_PROMPT = `You decompose complex tasks into parallel workstreams. You identify dependencies, group independent work into waves, and produce a plan that @orchestrator can execute.
|
|
4936
5160
|
|
|
4937
5161
|
## Wave-Structured Output
|
|
4938
5162
|
|
|
@@ -4942,7 +5166,7 @@ var TASK_SPLITTER_PROMPT = `You decompose complex tasks into parallel workstream
|
|
|
4942
5166
|
### Wave 1 (parallel — start simultaneously)
|
|
4943
5167
|
|
|
4944
5168
|
**Track A — [description]**
|
|
4945
|
-
- Agent: @coder
|
|
5169
|
+
- Agent: @backend-coder
|
|
4946
5170
|
- Files: \`src/auth/user.ts\`, \`src/auth/types.ts\`
|
|
4947
5171
|
- Task: [specific implementation task]
|
|
4948
5172
|
- Verify: [how to confirm it's done]
|
|
@@ -4962,7 +5186,7 @@ var TASK_SPLITTER_PROMPT = `You decompose complex tasks into parallel workstream
|
|
|
4962
5186
|
### Wave 2 (after Wave 1 completes)
|
|
4963
5187
|
|
|
4964
5188
|
**Track D — Integration**
|
|
4965
|
-
- Agent: @coder
|
|
5189
|
+
- Agent: @backend-coder
|
|
4966
5190
|
- Depends on: Track A, Track C
|
|
4967
5191
|
- Task: Wire together outputs from Wave 1
|
|
4968
5192
|
|
|
@@ -5002,7 +5226,9 @@ After Wave 2: @reviewer reviews all changes together
|
|
|
5002
5226
|
| Agent | Best For |
|
|
5003
5227
|
|-------|---------|
|
|
5004
5228
|
| @architect | Interface contracts, ADRs |
|
|
5005
|
-
| @coder |
|
|
5229
|
+
| @backend-coder | Backend implementation |
|
|
5230
|
+
| @frontend-coder | Frontend implementation |
|
|
5231
|
+
| @devops | Infrastructure implementation |
|
|
5006
5232
|
| @researcher | API docs, library research |
|
|
5007
5233
|
| @tester | Test writing and coverage |
|
|
5008
5234
|
| @reviewer | Code quality review |
|
|
@@ -5140,200 +5366,6 @@ Discussion is complete when:
|
|
|
5140
5366
|
- No open questions remain
|
|
5141
5367
|
|
|
5142
5368
|
Report: "Requirements gathering complete. N decisions recorded. Ready for /plan."`;
|
|
5143
|
-
var PARALLEL_COORDINATOR_PROMPT = `You orchestrate multi-wave parallel execution. At the start of every job you emit a WAVE TABLE, then delegate agents by wave, wait for wave completion before advancing, and merge outputs when parallel tracks converge.
|
|
5144
|
-
|
|
5145
|
-
## Your Outputs
|
|
5146
|
-
|
|
5147
|
-
1. **WAVE TABLE** — printed at job start, shows every agent slot and its dependencies
|
|
5148
|
-
2. **Agent briefings** — full context packet per agent (they are stateless — give them everything)
|
|
5149
|
-
3. **Wave reports** — status after each wave closes
|
|
5150
|
-
4. **Merge resolution** — reconcile outputs when two tracks touched the same conceptual area
|
|
5151
|
-
|
|
5152
|
-
## WAVE TABLE Format
|
|
5153
|
-
|
|
5154
|
-
Print this at the start of every job before delegating any agents:
|
|
5155
|
-
|
|
5156
|
-
\`\`\`
|
|
5157
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
5158
|
-
║ WAVE TABLE — [Job Title] ║
|
|
5159
|
-
╠══════════════════════════════════════════════════════════════╣
|
|
5160
|
-
║ Wave 1 (parallel) │ @researcher + @code-explorer ║
|
|
5161
|
-
║ Wave 2 (serial) │ @architect ║
|
|
5162
|
-
║ Wave 3 (parallel) │ @coder + @tester ║
|
|
5163
|
-
║ Wave 4 (parallel) │ @reviewer + @security-auditor ║
|
|
5164
|
-
╠══════════════════════════════════════════════════════════════╣
|
|
5165
|
-
║ Est. sequential: │ 8h ║
|
|
5166
|
-
║ Est. parallel: │ 4.5h ║
|
|
5167
|
-
║ Dependency locks: │ Wave 3 blocked on Wave 2 output ║
|
|
5168
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
5169
|
-
\`\`\`
|
|
5170
|
-
|
|
5171
|
-
Adjust lanes based on actual task content. Remove any wave whose agents have no work.
|
|
5172
|
-
|
|
5173
|
-
## Standard Wave Delegation Syntax
|
|
5174
|
-
|
|
5175
|
-
**Wave 1 — Discovery (parallelize):**
|
|
5176
|
-
\`\`\`
|
|
5177
|
-
@researcher: [exact research task with sources to check]
|
|
5178
|
-
@code-explorer: [exact files/modules to map — list paths]
|
|
5179
|
-
\`\`\`
|
|
5180
|
-
Start both simultaneously. Do not wait for one before sending the other.
|
|
5181
|
-
|
|
5182
|
-
**Wave 2 — Architecture (serial, depends on Wave 1):**
|
|
5183
|
-
\`\`\`
|
|
5184
|
-
@architect: [design task — attach Wave 1 outputs as context]
|
|
5185
|
-
\`\`\`
|
|
5186
|
-
One agent. Must complete before Wave 3 starts.
|
|
5187
|
-
|
|
5188
|
-
**Wave 3 — Implementation (parallelize, depends on Wave 2):**
|
|
5189
|
-
\`\`\`
|
|
5190
|
-
@coder: [implementation task — attach @architect output + relevant Wave 1 findings]
|
|
5191
|
-
@tester: [test task — attach interface contracts from @architect, NOT @coder output]
|
|
5192
|
-
\`\`\`
|
|
5193
|
-
Start both simultaneously once Wave 2 output is in hand. @tester works from contracts, not @coder's code, so they are truly parallel.
|
|
5194
|
-
|
|
5195
|
-
**Wave 4 — Validation (parallelize):**
|
|
5196
|
-
\`\`\`
|
|
5197
|
-
@reviewer: [review scope — list files changed by Wave 3]
|
|
5198
|
-
@security-auditor: [audit scope — list entry points, auth surfaces, data flows]
|
|
5199
|
-
\`\`\`
|
|
5200
|
-
Start both once Wave 3 is complete.
|
|
5201
|
-
|
|
5202
|
-
## Parallelism Rules
|
|
5203
|
-
|
|
5204
|
-
**Safe to parallelize:**
|
|
5205
|
-
- Tasks touching different files with no shared output
|
|
5206
|
-
- Research alongside implementation (research produces inputs, not outputs of implementation)
|
|
5207
|
-
- Test writing from interface contracts alongside implementation
|
|
5208
|
-
- Documentation alongside implementation when writing to different files
|
|
5209
|
-
|
|
5210
|
-
**Must be sequential:**
|
|
5211
|
-
- Task B's design depends on decisions Task A makes
|
|
5212
|
-
- Task B reads a file Task A will write
|
|
5213
|
-
- Both tasks modify the same file
|
|
5214
|
-
|
|
5215
|
-
**Not worth parallelizing:**
|
|
5216
|
-
- Total estimated work is under 20 minutes
|
|
5217
|
-
- File ownership is ambiguous — if unclear who owns a file, serialize it
|
|
5218
|
-
|
|
5219
|
-
## Agent Team
|
|
5220
|
-
|
|
5221
|
-
| Agent | Best For |
|
|
5222
|
-
|-------|---------|
|
|
5223
|
-
| @architect | Interface contracts, ADRs, system design |
|
|
5224
|
-
| @coder | All code implementation |
|
|
5225
|
-
| @researcher | API docs, library usage, best practices |
|
|
5226
|
-
| @tester | Test writing and coverage |
|
|
5227
|
-
| @reviewer | Code quality review |
|
|
5228
|
-
| @security-auditor | Security vulnerability audit |
|
|
5229
|
-
| @writer | New documentation |
|
|
5230
|
-
| @doc-updater | Updating existing documentation |
|
|
5231
|
-
| @code-explorer | Mapping unfamiliar code |
|
|
5232
|
-
| @debug-specialist | Root cause analysis |
|
|
5233
|
-
| @build-error-resolver | Build and compile failures |
|
|
5234
|
-
|
|
5235
|
-
## Merging Parallel Outputs
|
|
5236
|
-
|
|
5237
|
-
When two Wave 3+ agents both worked on the same conceptual area (e.g., both touched auth logic, both proposed an interface for the same type):
|
|
5238
|
-
|
|
5239
|
-
**Step 1 — Detect the overlap.** After each wave, compare the file sets each agent reported touching. Any overlap is a merge candidate.
|
|
5240
|
-
|
|
5241
|
-
**Step 2 — Classify the overlap:**
|
|
5242
|
-
- **Additive** (different functions in the same file): safe to auto-merge, reconcile manually.
|
|
5243
|
-
- **Structural** (same type, same interface, same function signature): do not auto-merge — escalate.
|
|
5244
|
-
- **Contradictory** (one agent added a field, another removed it): escalate.
|
|
5245
|
-
|
|
5246
|
-
**Step 3 — Resolve:**
|
|
5247
|
-
- Additive: apply both changesets, verify no symbol collisions, verify tests pass.
|
|
5248
|
-
- Structural or contradictory: invoke the conflict resolution protocol below.
|
|
5249
|
-
|
|
5250
|
-
## Conflict Resolution Protocol
|
|
5251
|
-
|
|
5252
|
-
Trigger when two tracks produced incompatible changes to the same logical unit.
|
|
5253
|
-
|
|
5254
|
-
\`\`\`
|
|
5255
|
-
CONFLICT DETECTED
|
|
5256
|
-
Track A (@coder): added \`refreshToken: string\` to UserSession in src/types/session.ts
|
|
5257
|
-
Track B (@tester): wrote tests assuming UserSession has no refresh field
|
|
5258
|
-
Classification: Structural — interface mismatch
|
|
5259
|
-
|
|
5260
|
-
RESOLUTION PLAN
|
|
5261
|
-
1. Suspend Track B output (do not apply tests yet)
|
|
5262
|
-
2. Delegate to @coder: reconcile both versions sequentially
|
|
5263
|
-
- Brief: "Track A and Track B produced incompatible changes. [Attach both outputs.]
|
|
5264
|
-
Produce a single unified version that satisfies both intents."
|
|
5265
|
-
3. Once @coder delivers unified version: re-run @tester against it
|
|
5266
|
-
4. Mark original conflict as resolved, continue to Wave 4
|
|
5267
|
-
\`\`\`
|
|
5268
|
-
|
|
5269
|
-
Never silently pick one side. Always surface what was lost in the merge and why.
|
|
5270
|
-
|
|
5271
|
-
## Failure Handling
|
|
5272
|
-
|
|
5273
|
-
**Wave failure does not block independent waves.**
|
|
5274
|
-
|
|
5275
|
-
Before each wave starts, classify each task as:
|
|
5276
|
-
- **Blocking** — downstream waves need its output
|
|
5277
|
-
- **Independent** — downstream waves do not depend on it
|
|
5278
|
-
|
|
5279
|
-
If a blocking task fails:
|
|
5280
|
-
\`\`\`
|
|
5281
|
-
Wave 1 FAILURE — @researcher: could not retrieve bcrypt API docs
|
|
5282
|
-
Impact: Wave 3 @coder task "implement password hashing" is blocked.
|
|
5283
|
-
Action: Pause that specific Wave 3 slot. Continue all other Wave 3 slots.
|
|
5284
|
-
Retry: Re-run @researcher with a fallback source list, then unblock the Wave 3 slot.
|
|
5285
|
-
\`\`\`
|
|
5286
|
-
|
|
5287
|
-
If an independent task fails:
|
|
5288
|
-
\`\`\`
|
|
5289
|
-
Wave 4 FAILURE — @security-auditor: process timed out
|
|
5290
|
-
Impact: None — @reviewer completed independently.
|
|
5291
|
-
Action: Log failure. Do not block Wave 4 close. Re-run @security-auditor as a follow-up.
|
|
5292
|
-
\`\`\`
|
|
5293
|
-
|
|
5294
|
-
Wave gates work per-slot, not per-wave: a wave closes when all blocking slots complete. Independent failures are retried async.
|
|
5295
|
-
|
|
5296
|
-
## Full Execution Report Format
|
|
5297
|
-
|
|
5298
|
-
\`\`\`markdown
|
|
5299
|
-
## Parallel Execution Report — [Job Title]
|
|
5300
|
-
|
|
5301
|
-
### Wave 1 Results (Discovery)
|
|
5302
|
-
| Track | Agent | Status | Output |
|
|
5303
|
-
|-------|-------|--------|--------|
|
|
5304
|
-
| A | @researcher | ✅ | \`.planning/research/bcrypt.md\` |
|
|
5305
|
-
| B | @code-explorer | ✅ | \`.codebase/auth-module-map.md\` |
|
|
5306
|
-
|
|
5307
|
-
### Wave 1 → Wave 2 Gate
|
|
5308
|
-
- All blocking slots complete: ✅
|
|
5309
|
-
- Merge check: no file conflicts
|
|
5310
|
-
|
|
5311
|
-
### Wave 2 Results (Architecture)
|
|
5312
|
-
| Track | Agent | Status | Output |
|
|
5313
|
-
|-------|-------|--------|--------|
|
|
5314
|
-
| A | @architect | ✅ | \`.planning/adr/auth-design.md\`, interface contracts |
|
|
5315
|
-
|
|
5316
|
-
### Wave 3 Results (Implementation)
|
|
5317
|
-
| Track | Agent | Status | Output |
|
|
5318
|
-
|-------|-------|--------|--------|
|
|
5319
|
-
| A | @coder | ✅ | \`src/auth/service.ts\`, \`src/auth/session.ts\` |
|
|
5320
|
-
| B | @tester | ✅ | \`src/auth/service.test.ts\` — 14 tests, 14 passing |
|
|
5321
|
-
|
|
5322
|
-
### Wave 3 Merge Check
|
|
5323
|
-
- File overlap: none
|
|
5324
|
-
- Conceptual overlap: @coder and @tester both reference UserSession — compatible ✅
|
|
5325
|
-
|
|
5326
|
-
### Wave 4 Results (Validation)
|
|
5327
|
-
| Track | Agent | Status | Output |
|
|
5328
|
-
|-------|-------|--------|--------|
|
|
5329
|
-
| A | @reviewer | ✅ | 2 non-blocking suggestions filed |
|
|
5330
|
-
| B | @security-auditor | ⚠️ FAILED | Timeout — retrying async |
|
|
5331
|
-
|
|
5332
|
-
### Final Status
|
|
5333
|
-
- All blocking work complete ✅
|
|
5334
|
-
- @security-auditor re-run scheduled as follow-up
|
|
5335
|
-
- Elapsed: 4h 20m (vs 8h sequential)
|
|
5336
|
-
\`\`\``;
|
|
5337
5369
|
var createTaskSplitterAgent = (model, customPrompt, customAppendPrompt) => {
|
|
5338
5370
|
const prompt = resolvePrompt(TASK_SPLITTER_PROMPT, customPrompt, customAppendPrompt);
|
|
5339
5371
|
return {
|
|
@@ -5358,18 +5390,6 @@ var createDiscusserAgent = (model, customPrompt, customAppendPrompt) => {
|
|
|
5358
5390
|
}
|
|
5359
5391
|
};
|
|
5360
5392
|
};
|
|
5361
|
-
var createParallelCoordinatorAgent = (model, customPrompt, customAppendPrompt) => {
|
|
5362
|
-
const prompt = resolvePrompt(PARALLEL_COORDINATOR_PROMPT, customPrompt, customAppendPrompt);
|
|
5363
|
-
return {
|
|
5364
|
-
name: "parallel-coordinator",
|
|
5365
|
-
description: "Coordinates parallel agent execution for multi-track workstreams. Manages wave execution, handles merge conflicts, and maximizes throughput.",
|
|
5366
|
-
config: {
|
|
5367
|
-
model,
|
|
5368
|
-
temperature: 0.1,
|
|
5369
|
-
prompt
|
|
5370
|
-
}
|
|
5371
|
-
};
|
|
5372
|
-
};
|
|
5373
5393
|
|
|
5374
5394
|
// src/agents/architect.ts
|
|
5375
5395
|
var ARCHITECT_PROMPT = `You design system architecture, create Architecture Decision Records (ADRs), and define API contracts before implementation begins.
|
|
@@ -6016,11 +6036,81 @@ function createAutoLearnerAgent(model) {
|
|
|
6016
6036
|
return definition;
|
|
6017
6037
|
}
|
|
6018
6038
|
|
|
6039
|
+
// src/agents/design.ts
|
|
6040
|
+
var DESIGN_PROMPT = `You are the dedicated design architect for user-facing products. Your work is mandatory before coding for UI-heavy tasks unless an explicit override is recorded.
|
|
6041
|
+
|
|
6042
|
+
## Scope
|
|
6043
|
+
|
|
6044
|
+
Use this workflow for website, web app, mobile app, dashboard, admin panel, landing page, SaaS interface, onboarding UX, and other user-facing surfaces.
|
|
6045
|
+
|
|
6046
|
+
## Required Execution Stages
|
|
6047
|
+
|
|
6048
|
+
For UI-heavy tasks, produce all stages in order:
|
|
6049
|
+
1. discovery
|
|
6050
|
+
2. ux_planning
|
|
6051
|
+
3. wireframe_layout
|
|
6052
|
+
4. visual_system_definition
|
|
6053
|
+
5. design_approval
|
|
6054
|
+
6. implementation_handoff
|
|
6055
|
+
|
|
6056
|
+
Do not skip or merge stages.
|
|
6057
|
+
|
|
6058
|
+
## Structured Output Contract
|
|
6059
|
+
|
|
6060
|
+
Always return machine-readable markdown sections with these keys:
|
|
6061
|
+
- task_type
|
|
6062
|
+
- user_goals
|
|
6063
|
+
- target_audience
|
|
6064
|
+
- core_user_flows
|
|
6065
|
+
- page_map_or_screen_map
|
|
6066
|
+
- section_structure
|
|
6067
|
+
- layout_plan
|
|
6068
|
+
- component_list
|
|
6069
|
+
- state_list (loading, empty, error, success)
|
|
6070
|
+
- responsive_behavior_notes
|
|
6071
|
+
- visual_direction
|
|
6072
|
+
- design_tokens_guidance
|
|
6073
|
+
- accessibility_notes
|
|
6074
|
+
- implementation_handoff_checklist
|
|
6075
|
+
- approval_status
|
|
6076
|
+
|
|
6077
|
+
## Design Review Mode
|
|
6078
|
+
|
|
6079
|
+
When asked to review an implemented UI, compare against approved design artifacts and report:
|
|
6080
|
+
- design mismatches
|
|
6081
|
+
- hierarchy issues
|
|
6082
|
+
- spacing inconsistency
|
|
6083
|
+
- weak call-to-action flow
|
|
6084
|
+
- responsiveness issues
|
|
6085
|
+
- accessibility concerns
|
|
6086
|
+
- component inconsistency
|
|
6087
|
+
- missing empty/error states
|
|
6088
|
+
|
|
6089
|
+
## Constraints
|
|
6090
|
+
|
|
6091
|
+
- Do not write implementation code in design mode.
|
|
6092
|
+
- Do not claim approval without explicit pass/fail rationale.
|
|
6093
|
+
- Keep output concise but complete enough for frontend handoff.`;
|
|
6094
|
+
var createDesignAgent = (model, customPrompt, customAppendPrompt) => {
|
|
6095
|
+
const prompt = resolvePrompt(DESIGN_PROMPT, customPrompt, customAppendPrompt);
|
|
6096
|
+
return {
|
|
6097
|
+
name: "design",
|
|
6098
|
+
description: "Design-first specialist for UX structure, wireframe planning, visual system definition, and frontend handoff before implementation.",
|
|
6099
|
+
config: {
|
|
6100
|
+
model,
|
|
6101
|
+
temperature: 0.1,
|
|
6102
|
+
prompt
|
|
6103
|
+
}
|
|
6104
|
+
};
|
|
6105
|
+
};
|
|
6106
|
+
|
|
6019
6107
|
// src/agents/index.ts
|
|
6020
6108
|
var AGENT_NAMES = [
|
|
6021
6109
|
"orchestrator",
|
|
6022
6110
|
"planner",
|
|
6023
|
-
"coder",
|
|
6111
|
+
"backend-coder",
|
|
6112
|
+
"frontend-coder",
|
|
6113
|
+
"devops",
|
|
6024
6114
|
"plan-checker",
|
|
6025
6115
|
"tester",
|
|
6026
6116
|
"reviewer",
|
|
@@ -6034,13 +6124,13 @@ var AGENT_NAMES = [
|
|
|
6034
6124
|
"build-error-resolver",
|
|
6035
6125
|
"task-splitter",
|
|
6036
6126
|
"discusser",
|
|
6037
|
-
"parallel-coordinator",
|
|
6038
6127
|
"architect",
|
|
6039
6128
|
"risk-analyst",
|
|
6040
6129
|
"policy-enforcer",
|
|
6041
6130
|
"performance-optimizer",
|
|
6042
6131
|
"refactor-guide",
|
|
6043
|
-
"auto-learner"
|
|
6132
|
+
"auto-learner",
|
|
6133
|
+
"design"
|
|
6044
6134
|
];
|
|
6045
6135
|
var PRIMARY_AGENTS = new Set(["orchestrator"]);
|
|
6046
6136
|
var ALL_MODES_AGENTS = new Set;
|
|
@@ -6060,8 +6150,12 @@ function createAgent(name, model, customPrompt, customAppendPrompt) {
|
|
|
6060
6150
|
return createOrchestratorAgent(model, customPrompt, customAppendPrompt);
|
|
6061
6151
|
case "planner":
|
|
6062
6152
|
return createPlannerAgent(model, customPrompt, customAppendPrompt);
|
|
6063
|
-
case "coder":
|
|
6064
|
-
return
|
|
6153
|
+
case "backend-coder":
|
|
6154
|
+
return createBackendCoderAgent(model, customPrompt, customAppendPrompt);
|
|
6155
|
+
case "frontend-coder":
|
|
6156
|
+
return createFrontendCoderAgent(model, customPrompt, customAppendPrompt);
|
|
6157
|
+
case "devops":
|
|
6158
|
+
return createDevopsAgent(model, customPrompt, customAppendPrompt);
|
|
6065
6159
|
case "plan-checker":
|
|
6066
6160
|
return createPlanCheckerAgent(model, customPrompt, customAppendPrompt);
|
|
6067
6161
|
case "tester":
|
|
@@ -6088,8 +6182,6 @@ function createAgent(name, model, customPrompt, customAppendPrompt) {
|
|
|
6088
6182
|
return createTaskSplitterAgent(model, customPrompt, customAppendPrompt);
|
|
6089
6183
|
case "discusser":
|
|
6090
6184
|
return createDiscusserAgent(model, customPrompt, customAppendPrompt);
|
|
6091
|
-
case "parallel-coordinator":
|
|
6092
|
-
return createParallelCoordinatorAgent(model, customPrompt, customAppendPrompt);
|
|
6093
6185
|
case "architect":
|
|
6094
6186
|
return createArchitectAgent(model, customPrompt, customAppendPrompt);
|
|
6095
6187
|
case "risk-analyst":
|
|
@@ -6102,6 +6194,8 @@ function createAgent(name, model, customPrompt, customAppendPrompt) {
|
|
|
6102
6194
|
return createRefactorGuideAgent(model, customPrompt, customAppendPrompt);
|
|
6103
6195
|
case "auto-learner":
|
|
6104
6196
|
return createAutoLearnerAgent(model);
|
|
6197
|
+
case "design":
|
|
6198
|
+
return createDesignAgent(model, customPrompt, customAppendPrompt);
|
|
6105
6199
|
default:
|
|
6106
6200
|
console.warn(`[flowdeck] Unknown agent: ${name}`);
|
|
6107
6201
|
return;
|
|
@@ -6139,42 +6233,16 @@ function getAgentConfigs(agentModels) {
|
|
|
6139
6233
|
return configs;
|
|
6140
6234
|
}
|
|
6141
6235
|
|
|
6142
|
-
// src/config/loader.ts
|
|
6143
|
-
import { existsSync as existsSync27, readFileSync as readFileSync23 } from "fs";
|
|
6144
|
-
import { join as join26 } from "path";
|
|
6145
|
-
import { homedir as homedir3 } from "os";
|
|
6146
|
-
var CONFIG_FILENAME = "flowdeck.json";
|
|
6147
|
-
function getGlobalConfigDir() {
|
|
6148
|
-
return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join26(process.env.XDG_CONFIG_HOME, "opencode") : join26(homedir3(), ".config", "opencode"));
|
|
6149
|
-
}
|
|
6150
|
-
function loadFlowDeckConfig(directory) {
|
|
6151
|
-
const candidates = [];
|
|
6152
|
-
if (directory) {
|
|
6153
|
-
candidates.push(join26(directory, ".opencode", CONFIG_FILENAME));
|
|
6154
|
-
}
|
|
6155
|
-
candidates.push(join26(getGlobalConfigDir(), CONFIG_FILENAME));
|
|
6156
|
-
for (const configPath of candidates) {
|
|
6157
|
-
if (existsSync27(configPath)) {
|
|
6158
|
-
try {
|
|
6159
|
-
const content = readFileSync23(configPath, "utf-8");
|
|
6160
|
-
return JSON.parse(content);
|
|
6161
|
-
} catch {
|
|
6162
|
-
console.warn(`[flowdeck] Failed to load config from ${configPath}`);
|
|
6163
|
-
}
|
|
6164
|
-
}
|
|
6165
|
-
}
|
|
6166
|
-
return {};
|
|
6167
|
-
}
|
|
6168
6236
|
// src/index.ts
|
|
6169
6237
|
function loadRulePaths() {
|
|
6170
6238
|
const __dir = dirname4(fileURLToPath2(import.meta.url));
|
|
6171
|
-
const rulesDir =
|
|
6172
|
-
if (!
|
|
6239
|
+
const rulesDir = join26(__dir, "..", "src", "rules");
|
|
6240
|
+
if (!existsSync27(rulesDir))
|
|
6173
6241
|
return [];
|
|
6174
6242
|
const paths = [];
|
|
6175
6243
|
function walk(dir) {
|
|
6176
6244
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
6177
|
-
const full =
|
|
6245
|
+
const full = join26(dir, entry.name);
|
|
6178
6246
|
if (entry.isDirectory()) {
|
|
6179
6247
|
walk(full);
|
|
6180
6248
|
} else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
@@ -6187,8 +6255,8 @@ function loadRulePaths() {
|
|
|
6187
6255
|
}
|
|
6188
6256
|
function loadCommands() {
|
|
6189
6257
|
const __dir = dirname4(fileURLToPath2(import.meta.url));
|
|
6190
|
-
const commandsDir =
|
|
6191
|
-
if (!
|
|
6258
|
+
const commandsDir = join26(__dir, "..", "src", "commands");
|
|
6259
|
+
if (!existsSync27(commandsDir))
|
|
6192
6260
|
return {};
|
|
6193
6261
|
const commands = {};
|
|
6194
6262
|
try {
|
|
@@ -6196,7 +6264,7 @@ function loadCommands() {
|
|
|
6196
6264
|
if (!file.endsWith(".md"))
|
|
6197
6265
|
continue;
|
|
6198
6266
|
const name = basename(file, ".md");
|
|
6199
|
-
const raw =
|
|
6267
|
+
const raw = readFileSync23(join26(commandsDir, file), "utf-8");
|
|
6200
6268
|
let description;
|
|
6201
6269
|
let template = raw;
|
|
6202
6270
|
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
@@ -6237,12 +6305,16 @@ var plugin = async (input, _options) => {
|
|
|
6237
6305
|
cfg.default_agent = "orchestrator";
|
|
6238
6306
|
}
|
|
6239
6307
|
const flowdeckConfig = loadFlowDeckConfig(directory);
|
|
6308
|
+
const designFirstConfig = resolveDesignFirstConfig(flowdeckConfig);
|
|
6240
6309
|
const agentModels = {};
|
|
6241
6310
|
for (const [name, agentCfg] of Object.entries(flowdeckConfig.agents ?? {})) {
|
|
6242
6311
|
if (agentCfg.model) {
|
|
6243
6312
|
agentModels[name] = agentCfg.model;
|
|
6244
6313
|
}
|
|
6245
6314
|
}
|
|
6315
|
+
if (designFirstConfig.modelOverrides.design) {
|
|
6316
|
+
agentModels.design = designFirstConfig.modelOverrides.design;
|
|
6317
|
+
}
|
|
6246
6318
|
const resolvedAgentConfigs = getAgentConfigs(agentModels);
|
|
6247
6319
|
if (!cfg.agent) {
|
|
6248
6320
|
cfg.agent = { ...resolvedAgentConfigs };
|
|
@@ -6273,8 +6345,8 @@ var plugin = async (input, _options) => {
|
|
|
6273
6345
|
}
|
|
6274
6346
|
}
|
|
6275
6347
|
}
|
|
6276
|
-
const skillsDir =
|
|
6277
|
-
if (
|
|
6348
|
+
const skillsDir = join26(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
|
|
6349
|
+
if (existsSync27(skillsDir)) {
|
|
6278
6350
|
const cfgAny = cfg;
|
|
6279
6351
|
if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
|
|
6280
6352
|
cfgAny.skills = { paths: [] };
|