@flumecode/runner 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +100 -36
- package/package.json +1 -1
- package/skills-plugin/skills/format-code-plugin-generator/SKILL.md +64 -0
- package/skills-plugin/skills/request-to-plan/SKILL.md +2 -1
- package/skills-plugin/skills/resolve-merge-conflict/SKILL.md +13 -5
- package/skills-plugin/skills/revise-implementation/SKILL.md +8 -8
- package/skills-plugin/skills/unit-test-plugin-generator/SKILL.md +74 -0
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ function writeConfig(config) {
|
|
|
27
27
|
|
|
28
28
|
// src/run.ts
|
|
29
29
|
import { existsSync as existsSync4 } from "node:fs";
|
|
30
|
-
import { join as
|
|
30
|
+
import { join as join6 } from "node:path";
|
|
31
31
|
|
|
32
32
|
// src/version.ts
|
|
33
33
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -182,6 +182,8 @@ async function safeText(res) {
|
|
|
182
182
|
|
|
183
183
|
// src/plugins/socket.ts
|
|
184
184
|
import { exec as execCb } from "node:child_process";
|
|
185
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
186
|
+
import { join as join4 } from "node:path";
|
|
185
187
|
import { promisify as promisify2 } from "node:util";
|
|
186
188
|
|
|
187
189
|
// src/workspace.ts
|
|
@@ -612,7 +614,12 @@ function parseManifest(raw) {
|
|
|
612
614
|
if (typeof r.key !== "string" || !r.key) return null;
|
|
613
615
|
if (r.socket !== "pre-commit") return null;
|
|
614
616
|
if (typeof r.run !== "string" || !r.run) return null;
|
|
615
|
-
|
|
617
|
+
let report;
|
|
618
|
+
const rep = r.report;
|
|
619
|
+
if (rep && typeof rep.file === "string" && rep.file && rep.format === "jest") {
|
|
620
|
+
report = { file: rep.file, format: "jest" };
|
|
621
|
+
}
|
|
622
|
+
return { key: r.key, socket: r.socket, run: r.run, ...report ? { report } : {} };
|
|
616
623
|
}
|
|
617
624
|
|
|
618
625
|
// src/plugins/socket.ts
|
|
@@ -633,15 +640,40 @@ async function runSocket(socketName, dir) {
|
|
|
633
640
|
const results = [];
|
|
634
641
|
for (const plugin of plugins) {
|
|
635
642
|
const result = await runPluginCommand(plugin.run, dir);
|
|
643
|
+
const metrics = await readMetrics(plugin.report, dir);
|
|
636
644
|
if (result.exitCode !== 0) {
|
|
637
|
-
results.push({
|
|
645
|
+
results.push({
|
|
646
|
+
key: plugin.key,
|
|
647
|
+
status: "failed",
|
|
648
|
+
output: cap(result.output),
|
|
649
|
+
...metrics ? { metrics } : {}
|
|
650
|
+
});
|
|
638
651
|
lastSocketResults = results;
|
|
639
652
|
throw new PreCommitError(`[plugin:${plugin.key}] ${result.output}`);
|
|
640
653
|
}
|
|
641
|
-
results.push({
|
|
654
|
+
results.push({
|
|
655
|
+
key: plugin.key,
|
|
656
|
+
status: "passed",
|
|
657
|
+
output: cap(result.output),
|
|
658
|
+
...metrics ? { metrics } : {}
|
|
659
|
+
});
|
|
642
660
|
}
|
|
643
661
|
lastSocketResults = results;
|
|
644
662
|
}
|
|
663
|
+
async function readMetrics(report, dir) {
|
|
664
|
+
if (!report) return void 0;
|
|
665
|
+
try {
|
|
666
|
+
const raw = JSON.parse(await readFile2(join4(dir, report.file), "utf8"));
|
|
667
|
+
if (report.format === "jest") {
|
|
668
|
+
return {
|
|
669
|
+
testsRun: Number(raw.numTotalTests) || 0,
|
|
670
|
+
testsFailed: Number(raw.numFailedTests) || 0
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
return void 0;
|
|
676
|
+
}
|
|
645
677
|
async function runPluginCommand(command2, cwd) {
|
|
646
678
|
try {
|
|
647
679
|
const result = await exec2(command2, { cwd, maxBuffer: 1 << 24 });
|
|
@@ -746,7 +778,9 @@ var pseudoCodeEntrySchema = z2.object({
|
|
|
746
778
|
});
|
|
747
779
|
var stepSchema = z2.object({
|
|
748
780
|
title: z2.string().min(1).describe("A concise imperative title for this step."),
|
|
749
|
-
description: z2.string().min(1).describe(
|
|
781
|
+
description: z2.array(z2.string().min(1)).min(1).describe(
|
|
782
|
+
"Bullet points that explain this step's change so a reviewer can judge whether the design is correct. Each array item is one short, self-contained bullet \u2014 not a single paragraph, and not a restatement of the pseudo code. " + INLINE_CODE_HINT
|
|
783
|
+
),
|
|
750
784
|
pseudoCode: z2.array(pseudoCodeEntrySchema).optional().describe(
|
|
751
785
|
"Per-file pseudo code. Provide an entry for every non-documentation file this step touches. Each entry contains the file path and pseudo code describing the changes to that file."
|
|
752
786
|
)
|
|
@@ -758,6 +792,9 @@ var planInputSchema = {
|
|
|
758
792
|
scope: z2.enum(["feat", "fix", "chore", "docs", "test", "refactor"]).describe("The primary intent of the change."),
|
|
759
793
|
goal: z2.string().min(1).describe("One or two sentences stating the outcome. " + INLINE_CODE_HINT),
|
|
760
794
|
assumptions: z2.array(z2.string()).describe("Anything decided during planning, including unanswered defaults."),
|
|
795
|
+
requirements: z2.array(z2.string().min(1)).min(1).describe(
|
|
796
|
+
"Required, human-readable statements of what this change must accomplish and why, in plain language a non-technical reader can follow. Distinct from acceptanceCriteria: requirements explain intent/rationale; acceptance criteria are the machine-checkable proof. At least 1 required. " + INLINE_CODE_HINT
|
|
797
|
+
),
|
|
761
798
|
steps: z2.array(stepSchema).min(1).describe("Ordered list of changes. Each step says what and why, with file references."),
|
|
762
799
|
acceptanceCriteria: z2.array(z2.string().min(1)).min(2).describe(
|
|
763
800
|
"Concrete, deterministically-checkable conditions that together define done. Each names a trigger/precondition and the exact observable result (run X -> output Y; file Z contains W; f(a) returns b) \u2014 no vague adjectives, not a restatement of a step. The set must collectively cover every step's change. At least 2 required. " + INLINE_CODE_HINT
|
|
@@ -781,12 +818,19 @@ function renderPlan(plan) {
|
|
|
781
818
|
}
|
|
782
819
|
}
|
|
783
820
|
lines2.push("");
|
|
821
|
+
lines2.push("## Requirements");
|
|
822
|
+
for (const requirement of plan.requirements) {
|
|
823
|
+
lines2.push(`- ${requirement}`);
|
|
824
|
+
}
|
|
825
|
+
lines2.push("");
|
|
784
826
|
lines2.push("## Steps");
|
|
785
827
|
for (const [i, step] of plan.steps.entries()) {
|
|
786
828
|
lines2.push("");
|
|
787
829
|
lines2.push(`### ${i + 1}. ${step.title}`);
|
|
788
830
|
lines2.push("");
|
|
789
|
-
|
|
831
|
+
for (const bullet of step.description) {
|
|
832
|
+
lines2.push(`- ${bullet}`);
|
|
833
|
+
}
|
|
790
834
|
if (step.pseudoCode && step.pseudoCode.length > 0) {
|
|
791
835
|
for (const entry of step.pseudoCode) {
|
|
792
836
|
lines2.push("");
|
|
@@ -835,7 +879,7 @@ function createPlanTooling() {
|
|
|
835
879
|
let renderedPlans = null;
|
|
836
880
|
const submitPlan = tool2(
|
|
837
881
|
SUBMIT_PLAN,
|
|
838
|
-
"Submit ALL your plans in a single call \u2014 one entry per plan; each becomes its own independently-acceptable Accept-as-plan draft. Do NOT call submit_plan more than once. acceptanceCriteria is required in each plan and must contain at least 2 observable, verifiable conditions. The 'title' field names each specific plan \u2014 make it concise and distinct from the request title and from sibling plan titles.",
|
|
882
|
+
"Submit ALL your plans in a single call \u2014 one entry per plan; each becomes its own independently-acceptable Accept-as-plan draft. Do NOT call submit_plan more than once. acceptanceCriteria is required in each plan and must contain at least 2 observable, verifiable conditions. The 'title' field names each specific plan \u2014 make it concise and distinct from the request title and from sibling plan titles. requirements is required in each plan: at least 1 plain-language statement of what the change must accomplish and why (human-readable intent), separate from the machine-checkable acceptanceCriteria. ",
|
|
839
883
|
submitPlanInputSchema,
|
|
840
884
|
async (args) => {
|
|
841
885
|
const parsed = submitPlanSchema.parse(args);
|
|
@@ -907,8 +951,11 @@ var reportInputSchema = {
|
|
|
907
951
|
caveats: z3.string().min(1).describe(
|
|
908
952
|
"Markdown: anything deferred, unmet, or worth a human's eyes, incl. diff hunks that map to no plan AC. Write 'None.' if nothing. Rendered under '## Caveats / follow-ups'. " + INLINE_CODE_HINT
|
|
909
953
|
),
|
|
910
|
-
acceptanceCriteria: z3.array(acVerdictSchema).
|
|
911
|
-
"One entry per acceptance criterion from the plan, in plan order, each with a verdict and the diff evidence behind it."
|
|
954
|
+
acceptanceCriteria: z3.array(acVerdictSchema).describe(
|
|
955
|
+
"One entry per acceptance criterion from the plan, in plan order, each with a verdict and the diff evidence behind it. May be empty for resolve runs (no plan to verify)."
|
|
956
|
+
),
|
|
957
|
+
conflictResolution: z3.string().optional().describe(
|
|
958
|
+
"Markdown: present ONLY when a merge conflict was actually resolved. Explain, per conflicted file, how ours/theirs were integrated. Rendered under '## Conflict resolution'. Omit entirely when no conflict occurred."
|
|
912
959
|
)
|
|
913
960
|
};
|
|
914
961
|
var reportSchema = z3.object(reportInputSchema);
|
|
@@ -916,21 +963,26 @@ function renderReport(report) {
|
|
|
916
963
|
const lines2 = [];
|
|
917
964
|
lines2.push(report.summary.trim());
|
|
918
965
|
lines2.push("", "## Files changed", "", report.filesChanged.trim());
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
lines2.push(`### ${STATUS_ICON[ac.status]} ${ac.criterion}`);
|
|
923
|
-
lines2.push("");
|
|
924
|
-
lines2.push(ac.rationale.trim());
|
|
925
|
-
for (const ev of ac.evidence) {
|
|
966
|
+
if (report.acceptanceCriteria.length > 0) {
|
|
967
|
+
lines2.push("", "## Acceptance criteria");
|
|
968
|
+
for (const ac of report.acceptanceCriteria) {
|
|
926
969
|
lines2.push("");
|
|
927
|
-
lines2.push(
|
|
970
|
+
lines2.push(`### ${STATUS_ICON[ac.status]} ${ac.criterion}`);
|
|
928
971
|
lines2.push("");
|
|
929
|
-
lines2.push(
|
|
930
|
-
|
|
931
|
-
|
|
972
|
+
lines2.push(ac.rationale.trim());
|
|
973
|
+
for (const ev of ac.evidence) {
|
|
974
|
+
lines2.push("");
|
|
975
|
+
lines2.push(ev.note ? `\`${ev.file}\` \u2014 ${ev.note}` : `\`${ev.file}\``);
|
|
976
|
+
lines2.push("");
|
|
977
|
+
lines2.push("```diff");
|
|
978
|
+
lines2.push(ev.hunk.replace(/\n+$/, ""));
|
|
979
|
+
lines2.push("```");
|
|
980
|
+
}
|
|
932
981
|
}
|
|
933
982
|
}
|
|
983
|
+
if (report.conflictResolution?.trim()) {
|
|
984
|
+
lines2.push("", "## Conflict resolution", "", report.conflictResolution.trim());
|
|
985
|
+
}
|
|
934
986
|
lines2.push("", "## Code quality", "", report.codeQuality.trim());
|
|
935
987
|
lines2.push("", "## Caveats / follow-ups", "", report.caveats.trim());
|
|
936
988
|
return lines2.join("\n");
|
|
@@ -1110,11 +1162,11 @@ function errorMessage(err) {
|
|
|
1110
1162
|
|
|
1111
1163
|
// src/rules.ts
|
|
1112
1164
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1113
|
-
import { join as
|
|
1165
|
+
import { join as join5 } from "node:path";
|
|
1114
1166
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1115
1167
|
var RULES_DIR = fileURLToPath3(new URL("../skills-plugin/rules", import.meta.url));
|
|
1116
1168
|
function loadRule(name) {
|
|
1117
|
-
const raw = readFileSync3(
|
|
1169
|
+
const raw = readFileSync3(join5(RULES_DIR, `${name}.md`), "utf8");
|
|
1118
1170
|
return stripFrontMatter(raw).trim();
|
|
1119
1171
|
}
|
|
1120
1172
|
function stripFrontMatter(raw) {
|
|
@@ -1355,6 +1407,7 @@ async function pushAndOpenPr(ctx, dir, config, abort, opts = { rebase: true }) {
|
|
|
1355
1407
|
const committed = await commitWithRepair(ctx, dir, abort);
|
|
1356
1408
|
if (!committed) return { outcome: { kind: "none" }, autoMerged: false };
|
|
1357
1409
|
let autoMerged = false;
|
|
1410
|
+
let conflictResolution;
|
|
1358
1411
|
if (opts.rebase) {
|
|
1359
1412
|
try {
|
|
1360
1413
|
await rebaseOntoMergeBranch(ctx, dir);
|
|
@@ -1364,14 +1417,15 @@ async function pushAndOpenPr(ctx, dir, config, abort, opts = { rebase: true }) {
|
|
|
1364
1417
|
console.warn(
|
|
1365
1418
|
` rebase onto ${ctx.repo.mergeBranch} conflicted \u2014 merging it in and resolving with the agent\u2026`
|
|
1366
1419
|
);
|
|
1367
|
-
await mergeAndResolveConflicts(ctx, dir, config, abort);
|
|
1420
|
+
const { report: mergeReport } = await mergeAndResolveConflicts(ctx, dir, config, abort);
|
|
1421
|
+
conflictResolution = mergeReport?.conflictResolution;
|
|
1368
1422
|
await commitWithRepair(ctx, dir, abort, { skipSocket: true });
|
|
1369
1423
|
autoMerged = true;
|
|
1370
1424
|
}
|
|
1371
1425
|
}
|
|
1372
1426
|
await pushBranch(ctx, dir);
|
|
1373
1427
|
const pr = await openPullRequest(ctx);
|
|
1374
|
-
return { outcome: pr ? { kind: "pr", pr } : { kind: "pushed" }, autoMerged };
|
|
1428
|
+
return { outcome: pr ? { kind: "pr", pr } : { kind: "pushed" }, autoMerged, conflictResolution };
|
|
1375
1429
|
}
|
|
1376
1430
|
async function mergeAndResolveConflicts(ctx, dir, config, abort) {
|
|
1377
1431
|
const { conflicted } = await mergeInMergeBranch(ctx, dir);
|
|
@@ -1397,7 +1451,7 @@ async function mergeAndResolveConflicts(ctx, dir, config, abort) {
|
|
|
1397
1451
|
`Could not fully resolve the merge \u2014 ${unresolved.length} file(s) still contain conflict markers: ${unresolved.join(", ")}`
|
|
1398
1452
|
);
|
|
1399
1453
|
}
|
|
1400
|
-
return { resolved: true, text: result.text.trim() || null };
|
|
1454
|
+
return { resolved: true, text: result.text.trim() || null, report: result.report ?? void 0 };
|
|
1401
1455
|
}
|
|
1402
1456
|
async function commitWithRepair(ctx, dir, abort, opts = {}) {
|
|
1403
1457
|
for (let attempt = 1; ; attempt++) {
|
|
@@ -1527,7 +1581,7 @@ async function processChatJob(ctx, dir, config, abort) {
|
|
|
1527
1581
|
console.log(` \u2026job ${ctx.jobId} posted ${result.widgets.length} widget(s); awaiting reply`);
|
|
1528
1582
|
return { text: reply, widgets: result.widgets };
|
|
1529
1583
|
}
|
|
1530
|
-
const wikiExists = existsSync4(
|
|
1584
|
+
const wikiExists = existsSync4(join6(dir, ".flumecode", "wiki"));
|
|
1531
1585
|
let documented = false;
|
|
1532
1586
|
if (ctx.permissionMode !== "plan" && wikiExists && await hasChanges(dir)) {
|
|
1533
1587
|
try {
|
|
@@ -1616,7 +1670,7 @@ ${reply}`;
|
|
|
1616
1670
|
|
|
1617
1671
|
> \u26A0\uFE0F Dependencies failed to install (\`${installResult.manager}\`); tests may not have run.`;
|
|
1618
1672
|
}
|
|
1619
|
-
const wikiExists = existsSync4(
|
|
1673
|
+
const wikiExists = existsSync4(join6(dir, ".flumecode", "wiki"));
|
|
1620
1674
|
let documented = false;
|
|
1621
1675
|
if (wikiExists && await hasChanges(dir)) {
|
|
1622
1676
|
try {
|
|
@@ -1636,12 +1690,13 @@ ${reply}`;
|
|
|
1636
1690
|
} else if (!wikiExists) {
|
|
1637
1691
|
console.log(` no .flumecode/wiki \u2014 skipping wiki reconcile for ${ctx.jobId}`);
|
|
1638
1692
|
}
|
|
1639
|
-
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, config, abort, {
|
|
1693
|
+
const { outcome, autoMerged, conflictResolution } = await pushAndOpenPr(ctx, dir, config, abort, {
|
|
1640
1694
|
rebase: !resumed
|
|
1641
1695
|
});
|
|
1642
1696
|
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1643
1697
|
const lintPlugins = getSocketResults();
|
|
1644
|
-
const
|
|
1698
|
+
const reportWithConflict = report && conflictResolution ? { ...report, conflictResolution } : report;
|
|
1699
|
+
const finalReport = reportWithConflict && lintPlugins.length ? { ...reportWithConflict, lint: { plugins: lintPlugins } } : reportWithConflict;
|
|
1645
1700
|
return {
|
|
1646
1701
|
text: reply,
|
|
1647
1702
|
widgets: [],
|
|
@@ -1661,8 +1716,8 @@ async function processReviseJob(ctx, dir, resumed, config, abort) {
|
|
|
1661
1716
|
maxTurns: ORCHESTRATOR_MAX_TURNS,
|
|
1662
1717
|
abortController: abort
|
|
1663
1718
|
});
|
|
1664
|
-
const
|
|
1665
|
-
let reply =
|
|
1719
|
+
const report = result.report ?? void 0;
|
|
1720
|
+
let reply = (report ? renderReport(report) : result.text.trim()) || "(the agent produced no reply)";
|
|
1666
1721
|
if (result.plans?.length) reply = result.plans[0] ?? reply;
|
|
1667
1722
|
if (installResult.status === "failed") {
|
|
1668
1723
|
reply += `
|
|
@@ -1673,7 +1728,7 @@ async function processReviseJob(ctx, dir, resumed, config, abort) {
|
|
|
1673
1728
|
console.log(` \u2026revise ${ctx.jobId} posted ${result.widgets.length} widget(s); awaiting reply`);
|
|
1674
1729
|
return { text: reply, widgets: result.widgets };
|
|
1675
1730
|
}
|
|
1676
|
-
const wikiExists = existsSync4(
|
|
1731
|
+
const wikiExists = existsSync4(join6(dir, ".flumecode", "wiki"));
|
|
1677
1732
|
let documented = false;
|
|
1678
1733
|
if (wikiExists && await hasChanges(dir)) {
|
|
1679
1734
|
try {
|
|
@@ -1693,15 +1748,19 @@ async function processReviseJob(ctx, dir, resumed, config, abort) {
|
|
|
1693
1748
|
} else if (!wikiExists) {
|
|
1694
1749
|
console.log(` no .flumecode/wiki \u2014 skipping wiki reconcile for ${ctx.jobId}`);
|
|
1695
1750
|
}
|
|
1696
|
-
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, config, abort, {
|
|
1751
|
+
const { outcome, autoMerged, conflictResolution } = await pushAndOpenPr(ctx, dir, config, abort, {
|
|
1697
1752
|
rebase: !resumed
|
|
1698
1753
|
});
|
|
1699
1754
|
if (outcome.kind !== "none") {
|
|
1700
1755
|
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1701
1756
|
}
|
|
1757
|
+
const lintPlugins = getSocketResults();
|
|
1758
|
+
const reportWithConflict = report && conflictResolution ? { ...report, conflictResolution } : report;
|
|
1759
|
+
const finalReport = reportWithConflict && lintPlugins.length ? { ...reportWithConflict, lint: { plugins: lintPlugins } } : reportWithConflict;
|
|
1702
1760
|
return {
|
|
1703
1761
|
text: reply,
|
|
1704
1762
|
widgets: [],
|
|
1763
|
+
...finalReport ? { report: finalReport } : {},
|
|
1705
1764
|
...outcome.kind === "pr" ? { pr: outcome.pr } : {},
|
|
1706
1765
|
...result.plans?.length ? { plans: result.plans } : {}
|
|
1707
1766
|
};
|
|
@@ -1710,8 +1769,13 @@ async function processResolveJob(ctx, dir, config, abort) {
|
|
|
1710
1769
|
console.log(`
|
|
1711
1770
|
\u25B6 Resolve ${ctx.jobId} \u2014 ${ctx.repo.fullName}: "${jobTitle(ctx)}"`);
|
|
1712
1771
|
const installResult = await installDependencies(dir);
|
|
1713
|
-
const { resolved, text } = await mergeAndResolveConflicts(ctx, dir, config, abort);
|
|
1714
|
-
let reply
|
|
1772
|
+
const { resolved, text, report } = await mergeAndResolveConflicts(ctx, dir, config, abort);
|
|
1773
|
+
let reply;
|
|
1774
|
+
if (resolved) {
|
|
1775
|
+
reply = report ? renderReport(report) : text || "(the agent produced no report)";
|
|
1776
|
+
} else {
|
|
1777
|
+
reply = `Merged \`${ctx.repo.mergeBranch ?? "the merge branch"}\` into \`${ctx.repo.checkoutBranch}\` cleanly \u2014 there were no conflicts to resolve.`;
|
|
1778
|
+
}
|
|
1715
1779
|
if (installResult.status === "failed") {
|
|
1716
1780
|
reply += `
|
|
1717
1781
|
|
|
@@ -1723,7 +1787,7 @@ async function processResolveJob(ctx, dir, config, abort) {
|
|
|
1723
1787
|
const pr = await openPullRequest(ctx);
|
|
1724
1788
|
const outcome = pr ? { kind: "pr", pr } : { kind: "pushed" };
|
|
1725
1789
|
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch });
|
|
1726
|
-
return { text: reply, widgets: [], ...pr ? { pr } : {} };
|
|
1790
|
+
return { text: reply, widgets: [], ...report ? { report } : {}, ...pr ? { pr } : {} };
|
|
1727
1791
|
}
|
|
1728
1792
|
async function processReleaseJob(ctx, dir, resumed, config, abort) {
|
|
1729
1793
|
console.log(`
|
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: format-code-plugin-generator
|
|
3
|
+
description: >-
|
|
4
|
+
Generate a concrete plan to install the FlumeCode Format plugin for THIS repo —
|
|
5
|
+
a .flumecode/plugins/format-code/ manifest wired to the pre-commit socket that
|
|
6
|
+
auto-formats code (prettier --write) so changes ride into the commit.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# format-code-plugin-generator
|
|
10
|
+
|
|
11
|
+
You generate a concrete, repo-specific plan to install the FlumeCode Format
|
|
12
|
+
plugin. You work **read-only**: inspect the repo and produce a plan via
|
|
13
|
+
`submit_plan`; never edit files.
|
|
14
|
+
|
|
15
|
+
## Orient yourself first
|
|
16
|
+
|
|
17
|
+
Before producing the plan, inspect:
|
|
18
|
+
|
|
19
|
+
1. `.flumecode/wiki/README.md` and `components/plugins.md` (if present) for context.
|
|
20
|
+
2. `package.json` `scripts` — look for `format`, `format:write`, `prettier` references.
|
|
21
|
+
3. `.prettierrc*` — confirm Prettier is configured.
|
|
22
|
+
4. `.husky/pre-commit` — find the existing formatting step this plugin replaces.
|
|
23
|
+
|
|
24
|
+
From this, determine the **exact shell command** the `run` script should execute
|
|
25
|
+
(e.g. `pnpm format`). Do not hard-code — derive from the repo.
|
|
26
|
+
|
|
27
|
+
## Produce the plan
|
|
28
|
+
|
|
29
|
+
Call `submit_plan` **once**, passing a `plans` array with one entry whose steps
|
|
30
|
+
instruct the implementer to create:
|
|
31
|
+
|
|
32
|
+
### Artifact — `.flumecode/plugins/format-code/plugin.json`
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"key": "format-code",
|
|
37
|
+
"socket": "pre-commit",
|
|
38
|
+
"run": "<detected format write command>"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Derive `run` from the repo's detected commands (e.g. `pnpm format`). Do not hard-code — include the actual commands discovered in the Orient step.
|
|
43
|
+
|
|
44
|
+
### Manifest shape
|
|
45
|
+
|
|
46
|
+
The manifest `plugin.json` must have exactly these fields:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
{ key, socket, run }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This is the shape the FlumeCode plugin loader expects.
|
|
53
|
+
|
|
54
|
+
### Acceptance criteria the plan must include
|
|
55
|
+
|
|
56
|
+
- `.flumecode/plugins/format-code/plugin.json` exists with `key: "format-code"`, `socket: "pre-commit"`, and `run` set to the detected write-format command.
|
|
57
|
+
- The `run` command reformats files in place and exits 0; reformatted files are staged and included in the commit.
|
|
58
|
+
|
|
59
|
+
## Always
|
|
60
|
+
|
|
61
|
+
- Stay read-only. Produce the plan via `submit_plan`; never edit files.
|
|
62
|
+
- The plan must be specific enough for an `implement-plan` run to execute
|
|
63
|
+
without re-deriving the commands — include the actual detected commands in
|
|
64
|
+
the step descriptions and artifact content.
|
|
@@ -66,9 +66,10 @@ Field-by-field guidance:
|
|
|
66
66
|
and nothing more.
|
|
67
67
|
- **`assumptions`** — anything you decided during investigation (including
|
|
68
68
|
unanswered defaults from Phase 1).
|
|
69
|
+
- **`requirements`** — **required; at least 1 item.** Plain-language statements of what this change must accomplish and why, written so a non-technical reader can follow them. Distinct from `acceptanceCriteria`: requirements explain intent and rationale; acceptance criteria are the machine-checkable proof. At least 1 item required.
|
|
69
70
|
- **`steps`** — an ordered list. For each step provide:
|
|
70
71
|
- **`title`** — a concise imperative phrase naming the step (e.g. "Add submit_plan schema to plan.ts").
|
|
71
|
-
- **`description`** — what
|
|
72
|
+
- **`description`** — an array of bullet points that help the reviewer understand the upcoming `pseudoCode` and decide whether the plan and design are correct. Each item is a distinct, self-contained point about what is changing and why — not a single paragraph, and not a line-by-line restatement of the pseudo code. Use concrete file references (`path/to/file.ts`) and name the functions/symbols involved. Apply inline-code formatting to all identifiers.
|
|
72
73
|
- **`pseudoCode`** — an array of `{ file, pseudoCode }` entries. Provide an entry for every file the step touches **except** documentation files (SKILL.md, README.md, wiki pages, etc.). `pseudoCode` is optional in the schema but expected for all non-documentation files. Each entry names the file path and contains pseudo code that precisely describes the changes to make in that file.
|
|
73
74
|
- **`acceptanceCriteria`** — **required; at least 2 items.** Each criterion must
|
|
74
75
|
be a concrete, deterministically-checkable condition that a third party can verify
|
|
@@ -93,8 +93,16 @@ before you finish. (You don't need to `git add`; the runner stages and commits f
|
|
|
93
93
|
|
|
94
94
|
## Your final reply
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
Call **`submit_report`** with the structured report. Fields:
|
|
97
|
+
|
|
98
|
+
- `summary`: one or two sentences on what the resolve run did.
|
|
99
|
+
- `filesChanged`: markdown list of files changed (from `git --no-pager diff --stat`).
|
|
100
|
+
- `codeQuality`: markdown: whether build/tests passed, any quality observations.
|
|
101
|
+
- `caveats`: markdown: anything deferred, risky, or worth the user's attention. Write `None.` if nothing.
|
|
102
|
+
- `acceptanceCriteria`: **leave this empty (`[]`)** — there is no plan to verify for a resolve run.
|
|
103
|
+
- `conflictResolution`: **required** — a markdown section, one paragraph or bullet per conflicted
|
|
104
|
+
file, explaining which side you kept and why (or how you merged both intents). Wrap file names
|
|
105
|
+
and code identifiers in inline backticks. This is what the user reads to understand how each
|
|
106
|
+
conflict was integrated.
|
|
107
|
+
|
|
108
|
+
The runner renders the report and appends the pull-request link — do not add one yourself.
|
|
@@ -41,7 +41,7 @@ actual code. Pick exactly one:
|
|
|
41
41
|
- **Re-plan** — the request meaningfully changes scope or direction, enough that a
|
|
42
42
|
fresh plan should be agreed before building. Call **`submit_plan`** with a `plans[]` array
|
|
43
43
|
containing the revised structured fields (same per-plan shape as the request-to-plan skill:
|
|
44
|
-
`scope`, `goal`, `assumptions`, `steps`, `acceptanceCriteria` — at least 2 —, `risks`,
|
|
44
|
+
`scope`, `goal`, `assumptions`, `requirements` — at least 1 —, `steps`, `acceptanceCriteria` — at least 2 —, `risks`,
|
|
45
45
|
`outOfScope`). Include only one entry for a revise turn. The runner posts it as a revision
|
|
46
46
|
the user can accept; make no code changes this turn.
|
|
47
47
|
- **Implement** — the request is clear and reasonable. Make the change (via
|
|
@@ -80,13 +80,13 @@ essentials:
|
|
|
80
80
|
Your last message **is** the comment posted to the plan thread — write it for the
|
|
81
81
|
user:
|
|
82
82
|
|
|
83
|
-
- **Implemented:**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pull-request link
|
|
83
|
+
- **Implemented:** call **`submit_report`** with the structured report, exactly as
|
|
84
|
+
`implement-plan` does. Include one `acceptanceCriteria` entry per plan AC (with a
|
|
85
|
+
met / not_met / unclear verdict and the diff hunk(s) that prove it), plus the four
|
|
86
|
+
required markdown sections (`summary`, `filesChanged`, `codeQuality`, `caveats`).
|
|
87
|
+
Base `filesChanged` and evidence on the actual `git --no-pager diff`, not on what
|
|
88
|
+
a subagent claimed; if the diff is empty, say nothing was changed. The runner
|
|
89
|
+
renders the report and appends the pull-request link — do not add one yourself.
|
|
90
90
|
- **Clarify / push back:** your question or reasoning, as prose (plus any widget).
|
|
91
91
|
- **Re-plan:** you called `submit_plan`; the rendered plan is posted automatically,
|
|
92
92
|
so keep any extra reply text minimal.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unit-test-plugin-generator
|
|
3
|
+
description: >-
|
|
4
|
+
Generate a concrete plan to install the FlumeCode Unit Test plugin for THIS repo —
|
|
5
|
+
a .flumecode/plugins/unit-test/ manifest wired to the pre-commit socket that runs
|
|
6
|
+
the repo's unit tests and emits a JSON report the runner parses into pass/fail counts.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# unit-test-plugin-generator
|
|
10
|
+
|
|
11
|
+
You generate a concrete, repo-specific plan to install the FlumeCode Unit Test
|
|
12
|
+
plugin. You work **read-only**: inspect the repo and produce a plan via
|
|
13
|
+
`submit_plan`; never edit files.
|
|
14
|
+
|
|
15
|
+
## Orient yourself first
|
|
16
|
+
|
|
17
|
+
Before producing the plan, inspect:
|
|
18
|
+
|
|
19
|
+
1. `.flumecode/wiki/README.md` and `components/plugins.md` (if present) for context.
|
|
20
|
+
2. `package.json` `scripts` — look for `test`, `test:unit`, or similar test commands.
|
|
21
|
+
3. `vitest.config.*` — to detect Vitest as the test runner.
|
|
22
|
+
4. `jest.config.*` — to detect Jest as the test runner.
|
|
23
|
+
|
|
24
|
+
From this, determine:
|
|
25
|
+
|
|
26
|
+
- Which test runner is in use (Vitest or Jest).
|
|
27
|
+
- The **exact shell command** that runs the tests and writes a JSON report file (e.g. `pnpm vitest run --reporter=json --outputFile=.flumecode/tmp/unit-test-report.json`). Do not hard-code — derive from the repo.
|
|
28
|
+
|
|
29
|
+
## Produce the plan
|
|
30
|
+
|
|
31
|
+
Call `submit_plan` **once**, passing a `plans` array with one entry whose steps
|
|
32
|
+
instruct the implementer to:
|
|
33
|
+
|
|
34
|
+
### Artifact 1 — `.flumecode/plugins/unit-test/plugin.json`
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"key": "unit-test",
|
|
39
|
+
"socket": "pre-commit",
|
|
40
|
+
"run": "<detected command, e.g. pnpm vitest run --reporter=json --outputFile=.flumecode/tmp/unit-test-report.json>",
|
|
41
|
+
"report": { "file": ".flumecode/tmp/unit-test-report.json", "format": "jest" }
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Derive `run` from the repo's detected test runner and scripts. Do not hard-code —
|
|
46
|
+
include the actual commands discovered in the Orient step.
|
|
47
|
+
|
|
48
|
+
### Artifact 2 — `.gitignore` update
|
|
49
|
+
|
|
50
|
+
Add `.flumecode/tmp/` to `.gitignore` so the transient JSON report file is never committed.
|
|
51
|
+
|
|
52
|
+
### Manifest shape
|
|
53
|
+
|
|
54
|
+
The manifest `plugin.json` must have exactly these fields:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
{ key, socket, run, report }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
where `report.file` points to the JSON report output path (repo-relative) and
|
|
61
|
+
`report.format` is `"jest"` (Vitest's JSON reporter uses the same schema).
|
|
62
|
+
|
|
63
|
+
### Acceptance criteria the plan must include
|
|
64
|
+
|
|
65
|
+
- `.flumecode/plugins/unit-test/plugin.json` exists with `key: "unit-test"`, `socket: "pre-commit"`, `run` set to the detected test command that writes a JSON report, and `report` set to `{ "file": ".flumecode/tmp/unit-test-report.json", "format": "jest" }`.
|
|
66
|
+
- The `run` command exits non-zero on any test failure and writes the JSON report file.
|
|
67
|
+
- `.flumecode/tmp/` is present in `.gitignore`.
|
|
68
|
+
|
|
69
|
+
## Always
|
|
70
|
+
|
|
71
|
+
- Stay read-only. Produce the plan via `submit_plan`; never edit files.
|
|
72
|
+
- The plan must be specific enough for an `implement-plan` run to execute
|
|
73
|
+
without re-deriving the commands — include the actual detected commands in
|
|
74
|
+
the step descriptions and artifact content.
|