flanders 0.3.0 → 0.4.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/lib/commands/Implement.d.ts +5 -0
- package/lib/commands/Implement.js +115 -42
- package/lib/commands/Install.d.ts +2 -0
- package/lib/commands/Install.js +151 -1
- package/lib/commands/InstallModelProbe.js +39 -41
- package/lib/prompts/prompts.js +10 -10
- package/lib/prompts/skills.d.ts +1 -1
- package/lib/prompts/skills.js +39 -30
- package/lib/workspace/FlandersConfig.d.ts +5 -1
- package/lib/workspace/FlandersConfig.js +9 -1
- package/lib/workspace/SpecDiscovery.js +4 -4
- package/package.json +1 -1
|
@@ -32,6 +32,9 @@ export declare class Implement {
|
|
|
32
32
|
private _activeScript;
|
|
33
33
|
private _activeReviewerSessions;
|
|
34
34
|
private _reviewerStates;
|
|
35
|
+
private _reviewerLogicalStatuses;
|
|
36
|
+
private _reviewerAbortControllers;
|
|
37
|
+
private _cancelledReviewers;
|
|
35
38
|
private _currentIndexLabel;
|
|
36
39
|
private _currentIteration;
|
|
37
40
|
private _currentTask;
|
|
@@ -62,6 +65,8 @@ export declare class Implement {
|
|
|
62
65
|
private _testStage;
|
|
63
66
|
private _setReviewerState;
|
|
64
67
|
private _renderReviewingFooter;
|
|
68
|
+
private _setReviewerLogicalStatus;
|
|
69
|
+
private _evaluateReviewRoundCompletion;
|
|
65
70
|
private _reviewerStage;
|
|
66
71
|
private _runOneReviewerToVerdict;
|
|
67
72
|
private _formatPathList;
|
|
@@ -66,6 +66,9 @@ class Implement {
|
|
|
66
66
|
this._activeScript = null;
|
|
67
67
|
this._activeReviewerSessions = new Set();
|
|
68
68
|
this._reviewerStates = null;
|
|
69
|
+
this._reviewerLogicalStatuses = [];
|
|
70
|
+
this._reviewerAbortControllers = new Map();
|
|
71
|
+
this._cancelledReviewers = new Set();
|
|
69
72
|
this._currentIndexLabel = "";
|
|
70
73
|
this._currentIteration = 0;
|
|
71
74
|
this._currentTask = null;
|
|
@@ -526,16 +529,49 @@ class Implement {
|
|
|
526
529
|
return;
|
|
527
530
|
this._block.setFooter({ kind: "reviewing", reviewers: this._reviewerStates.slice() });
|
|
528
531
|
}
|
|
532
|
+
_setReviewerLogicalStatus(idx, status) {
|
|
533
|
+
this._reviewerLogicalStatuses[idx] = status;
|
|
534
|
+
if (status !== "running") {
|
|
535
|
+
this._evaluateReviewRoundCompletion();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
_evaluateReviewRoundCompletion() {
|
|
539
|
+
const statuses = this._reviewerLogicalStatuses;
|
|
540
|
+
const reviewers = this._config.reviewers;
|
|
541
|
+
if (statuses.some(s => s === "running")) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
for (let i = 0; i < reviewers.length; i++) {
|
|
545
|
+
if (!reviewers[i].optional && statuses[i] !== "done") {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const doneCount = statuses.filter(s => s === "done").length;
|
|
550
|
+
if (doneCount < this._config.minimumReviews) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
for (let i = 0; i < statuses.length; i++) {
|
|
554
|
+
if (statuses[i] === "waiting") {
|
|
555
|
+
this._cancelledReviewers.add(i);
|
|
556
|
+
this._reviewerAbortControllers.get(i).abort();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
529
560
|
async _reviewerStage(plan, task, ws, iteration, prepActive) {
|
|
530
561
|
const reviewers = this._config.reviewers;
|
|
531
562
|
this._reviewerStates = reviewers.map(r => ({ tool: r.tool, model: r.model, effort: r.effort, state: "running" }));
|
|
563
|
+
this._reviewerLogicalStatuses = reviewers.map(() => "running");
|
|
564
|
+
this._cancelledReviewers = new Set();
|
|
532
565
|
this._renderReviewingFooter();
|
|
533
566
|
for (let i = 0; i < reviewers.length; i++) {
|
|
534
567
|
await this._workspace.clearReviewerErrorLog(i + 1);
|
|
535
568
|
}
|
|
536
569
|
await this._workspace.clearErrorLog();
|
|
537
570
|
let failureCaught = null;
|
|
538
|
-
const
|
|
571
|
+
const outcomes = reviewers.map(() => "cancelled");
|
|
572
|
+
const launches = reviewers.map((reviewer, idx) => this._runOneReviewerToVerdict(plan, task, ws, iteration, prepActive, reviewer, idx)
|
|
573
|
+
.then(outcome => { outcomes[idx] = outcome; })
|
|
574
|
+
.catch(e => {
|
|
539
575
|
if (failureCaught === null) {
|
|
540
576
|
failureCaught = e;
|
|
541
577
|
}
|
|
@@ -552,7 +588,9 @@ class Implement {
|
|
|
552
588
|
}
|
|
553
589
|
const perFile = [];
|
|
554
590
|
for (let i = 0; i < reviewers.length; i++) {
|
|
555
|
-
|
|
591
|
+
if (outcomes[i] === "verdict") {
|
|
592
|
+
perFile.push(await this._workspace.readReviewerErrorLog(i + 1));
|
|
593
|
+
}
|
|
556
594
|
}
|
|
557
595
|
const aggregate = perFile.join("\n").replace(/^\s+|\s+$/g, "");
|
|
558
596
|
if (aggregate.length === 0) {
|
|
@@ -576,49 +614,80 @@ class Implement {
|
|
|
576
614
|
if (!useBranchA) {
|
|
577
615
|
prompt = await this._appendLinkedContent(plan, task, prompt);
|
|
578
616
|
}
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (this._disposed)
|
|
617
|
+
const controller = new AbortController();
|
|
618
|
+
this._reviewerAbortControllers.set(idx, controller);
|
|
619
|
+
try {
|
|
620
|
+
const aggregateOutput = [];
|
|
621
|
+
for (;;) {
|
|
622
|
+
if (this._disposed) {
|
|
623
|
+
return "cancelled";
|
|
624
|
+
}
|
|
625
|
+
this._setReviewerState(idx, "running");
|
|
626
|
+
this._setReviewerLogicalStatus(idx, "running");
|
|
627
|
+
let currentSession = null;
|
|
628
|
+
const onAbort = () => {
|
|
629
|
+
if (!currentSession)
|
|
593
630
|
return;
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
631
|
+
void currentSession.dispose();
|
|
632
|
+
};
|
|
633
|
+
const callbacks = {
|
|
634
|
+
onLongWaitStart: () => {
|
|
635
|
+
if (this._disposed)
|
|
636
|
+
return;
|
|
637
|
+
this._setReviewerState(idx, "waiting");
|
|
638
|
+
this._setReviewerLogicalStatus(idx, "waiting");
|
|
639
|
+
},
|
|
640
|
+
onLongWaitEnd: () => {
|
|
641
|
+
if (this._disposed)
|
|
642
|
+
return;
|
|
643
|
+
this._setReviewerState(idx, "running");
|
|
644
|
+
this._setReviewerLogicalStatus(idx, "running");
|
|
645
|
+
},
|
|
646
|
+
register: (session) => {
|
|
647
|
+
currentSession = session;
|
|
648
|
+
this._activeReviewerSessions.add(session);
|
|
649
|
+
controller.signal.addEventListener("abort", onAbort);
|
|
650
|
+
},
|
|
651
|
+
unregister: (session) => {
|
|
652
|
+
this._activeReviewerSessions.delete(session);
|
|
653
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
654
|
+
currentSession = null;
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
let runResult;
|
|
658
|
+
try {
|
|
659
|
+
runResult = useBranchA
|
|
660
|
+
? await this._runAiWith(reviewer.tool, reviewer.model, reviewer.effort, prompt, null, this._currentPrepSessionId, callbacks)
|
|
661
|
+
: await this._runAiWith(reviewer.tool, reviewer.model, reviewer.effort, prompt, null, null, callbacks);
|
|
601
662
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
663
|
+
catch (e) {
|
|
664
|
+
if (this._cancelledReviewers.has(idx)) {
|
|
665
|
+
return "cancelled";
|
|
666
|
+
}
|
|
667
|
+
throw e;
|
|
668
|
+
}
|
|
669
|
+
const { result, capturedOutput } = runResult;
|
|
670
|
+
this._taskTokens.it += result.inputTokens;
|
|
671
|
+
this._taskTokens.ot += result.outputTokens;
|
|
672
|
+
await this._persistMetrics(plan, task.line);
|
|
673
|
+
this._updateMetrics(plan);
|
|
674
|
+
aggregateOutput.push(capturedOutput);
|
|
675
|
+
if (!await this._workspace.reviewerErrorLogExists(reviewerNum)) {
|
|
676
|
+
await this._writeLog(ws.reviewerOutputLog(iteration, reviewerNum), aggregateOutput.join("\n---\n"));
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const trimmed = (await this._workspace.readReviewerErrorLog(reviewerNum)).trim();
|
|
680
|
+
this._setReviewerState(idx, trimmed.length === 0 ? "ok" : "fail");
|
|
681
|
+
this._setReviewerLogicalStatus(idx, "done");
|
|
682
|
+
const verdictLine = trimmed.length === 0
|
|
683
|
+
? "Verdict: PASS"
|
|
684
|
+
: `Verdict: FAIL ${trimmed}`;
|
|
685
|
+
await this._writeLog(ws.reviewerOutputLog(iteration, reviewerNum), `${aggregateOutput.join("\n---\n")}\n\n${verdictLine}`);
|
|
686
|
+
return "verdict";
|
|
614
687
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
? "Verdict: PASS"
|
|
619
|
-
: `Verdict: FAIL ${trimmed}`;
|
|
620
|
-
await this._writeLog(ws.reviewerOutputLog(iteration, reviewerNum), `${aggregateOutput.join("\n---\n")}\n\n${verdictLine}`);
|
|
621
|
-
return;
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
this._reviewerAbortControllers.delete(idx);
|
|
622
691
|
}
|
|
623
692
|
}
|
|
624
693
|
_formatPathList(items) {
|
|
@@ -779,6 +848,10 @@ class Implement {
|
|
|
779
848
|
return;
|
|
780
849
|
}
|
|
781
850
|
this._disposed = true;
|
|
851
|
+
for (const controller of this._reviewerAbortControllers.values()) {
|
|
852
|
+
controller.abort();
|
|
853
|
+
}
|
|
854
|
+
this._reviewerAbortControllers.clear();
|
|
782
855
|
const activeSession = (_a = this._activeSession) === null || _a === void 0 ? void 0 : _a.session;
|
|
783
856
|
const activeScript = (_b = this._activeScript) === null || _b === void 0 ? void 0 : _b.script;
|
|
784
857
|
const reviewerSessions = [...this._activeReviewerSessions];
|
|
@@ -23,6 +23,8 @@ export type ResolvedAnswers = Readonly<{
|
|
|
23
23
|
workerModel?: string;
|
|
24
24
|
workerEffort?: string;
|
|
25
25
|
reviewers?: readonly ReviewerFlagAnswers[];
|
|
26
|
+
optionalReviewerIndices?: readonly number[];
|
|
27
|
+
reviewerMinimum?: number;
|
|
26
28
|
}>;
|
|
27
29
|
export declare function parseInstallFlags(rawArgs: readonly string[]): Readonly<{
|
|
28
30
|
ok: true;
|
package/lib/commands/Install.js
CHANGED
|
@@ -112,6 +112,34 @@ const CLAUDE_CROSS_FAMILY_ALIASES = [
|
|
|
112
112
|
];
|
|
113
113
|
const CLAUDE_BACK_LABEL = "← back";
|
|
114
114
|
const REVIEWER_INDEXED_RE = /^--reviewer-(\d+)-(tool|model|effort)=/;
|
|
115
|
+
const REVIEWER_OPTIONAL_INDEXED_RE = /^--reviewer-(\d+)-optional$/;
|
|
116
|
+
function validateWeightedFlagsForReviewerCount(reviewerMinimum, optionalReviewerIndices, reviewerCount) {
|
|
117
|
+
if (reviewerCount === 1) {
|
|
118
|
+
if (reviewerMinimum !== undefined) {
|
|
119
|
+
return "Invalid flag for a single-reviewer configuration: --reviewer-minimum. Weighted-review flags require two or more reviewers.\n";
|
|
120
|
+
}
|
|
121
|
+
if (optionalReviewerIndices !== undefined) {
|
|
122
|
+
const idx = optionalReviewerIndices[0];
|
|
123
|
+
const flag = idx === 1 ? "--reviewer-optional" : `--reviewer-${idx}-optional`;
|
|
124
|
+
return `Invalid flag for a single-reviewer configuration: ${flag}. Weighted-review flags require two or more reviewers.\n`;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (reviewerMinimum !== undefined && (reviewerMinimum < 1 || reviewerMinimum > reviewerCount)) {
|
|
129
|
+
return `Invalid value for --reviewer-minimum: "${reviewerMinimum}". Must be an integer between 1 and ${reviewerCount}.\n`;
|
|
130
|
+
}
|
|
131
|
+
if (optionalReviewerIndices !== undefined) {
|
|
132
|
+
for (const idx of optionalReviewerIndices) {
|
|
133
|
+
if (idx > reviewerCount) {
|
|
134
|
+
return `Invalid reviewer flag: --reviewer-${idx}-optional references reviewer ${idx}, beyond the configured reviewer list of ${reviewerCount}.\n`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (reviewerMinimum === reviewerCount && optionalReviewerIndices !== undefined) {
|
|
139
|
+
return `Invalid flag combination: --reviewer-minimum equal to the reviewer count (${reviewerCount}) leaves no reviewer that can be optional, so it cannot be combined with --reviewer[-N]-optional.\n`;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
115
143
|
function parseInstallFlags(rawArgs) {
|
|
116
144
|
var _a;
|
|
117
145
|
const hasGlobal = rawArgs.includes("--global");
|
|
@@ -226,6 +254,38 @@ function parseInstallFlags(rawArgs) {
|
|
|
226
254
|
}
|
|
227
255
|
answers.reviewers = list;
|
|
228
256
|
}
|
|
257
|
+
const optionalIndices = new Set();
|
|
258
|
+
for (const arg of rawArgs) {
|
|
259
|
+
if (arg === "--reviewer-optional") {
|
|
260
|
+
optionalIndices.add(1);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const match = arg.match(REVIEWER_OPTIONAL_INDEXED_RE);
|
|
264
|
+
if (match === null) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const idx = Number(match[1]);
|
|
268
|
+
if (idx < 1) {
|
|
269
|
+
return { ok: false, diagnostic: `Invalid reviewer flag: "${arg}". --reviewer-N-optional requires N >= 1.\n` };
|
|
270
|
+
}
|
|
271
|
+
optionalIndices.add(idx);
|
|
272
|
+
}
|
|
273
|
+
if (optionalIndices.size > 0) {
|
|
274
|
+
answers.optionalReviewerIndices = [...optionalIndices].sort((a, b) => a - b);
|
|
275
|
+
}
|
|
276
|
+
const minimumRaw = extractFlagValue(rawArgs, "--reviewer-minimum");
|
|
277
|
+
if (minimumRaw !== undefined) {
|
|
278
|
+
if (!/^\d+$/.test(minimumRaw)) {
|
|
279
|
+
return { ok: false, diagnostic: `Invalid value for --reviewer-minimum: "${minimumRaw}". Expected a non-negative integer.\n` };
|
|
280
|
+
}
|
|
281
|
+
answers.reviewerMinimum = Number(minimumRaw);
|
|
282
|
+
}
|
|
283
|
+
if (answers.reviewers !== undefined) {
|
|
284
|
+
const diagnostic = validateWeightedFlagsForReviewerCount(answers.reviewerMinimum, answers.optionalReviewerIndices, answers.reviewers.length);
|
|
285
|
+
if (diagnostic !== null) {
|
|
286
|
+
return { ok: false, diagnostic };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
229
289
|
return { ok: true, answers };
|
|
230
290
|
}
|
|
231
291
|
class Install {
|
|
@@ -590,6 +650,95 @@ class Install {
|
|
|
590
650
|
idx++;
|
|
591
651
|
}
|
|
592
652
|
}
|
|
653
|
+
if (suppliedReviewers === undefined) {
|
|
654
|
+
const diagnostic = validateWeightedFlagsForReviewerCount(answers.reviewerMinimum, answers.optionalReviewerIndices, reviewers.length);
|
|
655
|
+
if (diagnostic !== null) {
|
|
656
|
+
contexts.output.writeError(diagnostic);
|
|
657
|
+
return 1;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
let minimumReviews;
|
|
661
|
+
const reviewerConfigs = [];
|
|
662
|
+
if (reviewers.length === 1) {
|
|
663
|
+
minimumReviews = 1;
|
|
664
|
+
reviewerConfigs.push({ ...reviewers[0], optional: false });
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
const reviewerCount = reviewers.length;
|
|
668
|
+
if (answers.reviewerMinimum !== undefined) {
|
|
669
|
+
minimumReviews = answers.reviewerMinimum;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
if (this._disposed) {
|
|
673
|
+
return 1;
|
|
674
|
+
}
|
|
675
|
+
let chosen = null;
|
|
676
|
+
while (chosen === null) {
|
|
677
|
+
const entry = await promptText(contexts.ask, {
|
|
678
|
+
question: "Minimum reviewers that must run to a verdict in each review round",
|
|
679
|
+
placeholder: `1-${reviewerCount}, empty for ${reviewerCount}`
|
|
680
|
+
});
|
|
681
|
+
if (entry === null) {
|
|
682
|
+
return 1;
|
|
683
|
+
}
|
|
684
|
+
if (this._disposed) {
|
|
685
|
+
return 1;
|
|
686
|
+
}
|
|
687
|
+
const trimmed = entry.trim();
|
|
688
|
+
if (trimmed === "") {
|
|
689
|
+
chosen = reviewerCount;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
const parsed = Number(trimmed);
|
|
693
|
+
if (/^\d+$/.test(trimmed) && parsed >= 1 && parsed <= reviewerCount) {
|
|
694
|
+
chosen = parsed;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
contexts.output.write(`Enter an integer between 1 and ${reviewerCount}, or leave empty for ${reviewerCount}.\n`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
minimumReviews = chosen;
|
|
702
|
+
}
|
|
703
|
+
if (minimumReviews < reviewerCount) {
|
|
704
|
+
if (answers.optionalReviewerIndices !== undefined) {
|
|
705
|
+
const optionalSet = new Set(answers.optionalReviewerIndices);
|
|
706
|
+
for (let i = 0; i < reviewerCount; i++) {
|
|
707
|
+
reviewerConfigs.push({ ...reviewers[i], optional: optionalSet.has(i + 1) });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
for (let i = 0; i < reviewerCount; i++) {
|
|
712
|
+
if (this._disposed) {
|
|
713
|
+
return 1;
|
|
714
|
+
}
|
|
715
|
+
const reviewer = reviewers[i];
|
|
716
|
+
const modelLabel = reviewer.model === "" ? "default configured model" : reviewer.model;
|
|
717
|
+
const effortLabel = reviewer.effort === "" ? "default configured effort" : reviewer.effort;
|
|
718
|
+
const optionalOption = await promptChoice(contexts.ask, {
|
|
719
|
+
header: `Reviewer ${i + 1} optional`,
|
|
720
|
+
question: `Is reviewer ${i + 1} (${reviewer.tool} · ${modelLabel} · ${effortLabel}) optional?`,
|
|
721
|
+
options: [
|
|
722
|
+
{ label: "no", description: "Required — always waits out its rate-limit waits; the round never completes without its verdict" },
|
|
723
|
+
{ label: "yes", description: "Optional — reviews exactly like a required reviewer; the only difference is the round abandons it while it is in a rate-limit wait, once every required reviewer is in and the minimum is met" }
|
|
724
|
+
]
|
|
725
|
+
});
|
|
726
|
+
if (!optionalOption) {
|
|
727
|
+
return 1;
|
|
728
|
+
}
|
|
729
|
+
if (this._disposed) {
|
|
730
|
+
return 1;
|
|
731
|
+
}
|
|
732
|
+
reviewerConfigs.push({ ...reviewer, optional: optionalOption.label === "yes" });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
for (let i = 0; i < reviewerCount; i++) {
|
|
738
|
+
reviewerConfigs.push({ ...reviewers[i], optional: false });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
593
742
|
const scopeRoot = mode === "global"
|
|
594
743
|
? contexts.platform.homedir()
|
|
595
744
|
: options.projectRoot;
|
|
@@ -656,7 +805,8 @@ class Install {
|
|
|
656
805
|
}
|
|
657
806
|
const config = {
|
|
658
807
|
worker: { tool: workerTool, model: workerModel, effort: workerEffort },
|
|
659
|
-
reviewers
|
|
808
|
+
reviewers: reviewerConfigs,
|
|
809
|
+
minimumReviews
|
|
660
810
|
};
|
|
661
811
|
const configWrittenPath = await (0, FlandersConfig_1.write)(contexts.fs, {
|
|
662
812
|
scope: mode,
|
|
@@ -9,6 +9,39 @@ function isCommandNotFound(combinedOutput) {
|
|
|
9
9
|
const lower = combinedOutput.toLowerCase();
|
|
10
10
|
return COMMAND_NOT_FOUND_MARKERS.some(marker => lower.includes(marker));
|
|
11
11
|
}
|
|
12
|
+
function parseModelCatalog(stdout) {
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = JSON.parse(stdout);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const models = parsed.models;
|
|
24
|
+
if (!Array.isArray(models)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const slugs = [];
|
|
28
|
+
for (const entry of models) {
|
|
29
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const record = entry;
|
|
33
|
+
if (typeof record.slug !== "string" || typeof record.visibility !== "string") {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (record.visibility === "list") {
|
|
37
|
+
slugs.push(record.slug);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (slugs.length === 0) {
|
|
41
|
+
return { kind: "no-list" };
|
|
42
|
+
}
|
|
43
|
+
return { kind: "list", models: slugs };
|
|
44
|
+
}
|
|
12
45
|
function probeModelList(script) {
|
|
13
46
|
return new Promise(resolve => {
|
|
14
47
|
const stdoutChunks = [];
|
|
@@ -48,52 +81,17 @@ function probeModelList(script) {
|
|
|
48
81
|
});
|
|
49
82
|
proc.on("exit", (code) => {
|
|
50
83
|
const stdout = stdoutChunks.join("");
|
|
84
|
+
const catalog = parseModelCatalog(stdout);
|
|
85
|
+
if (catalog) {
|
|
86
|
+
settle(catalog);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
51
89
|
const stderr = stderrChunks.join("");
|
|
52
90
|
if (code === 127 || isCommandNotFound(stderr + stdout)) {
|
|
53
91
|
notStarted(null);
|
|
54
92
|
return;
|
|
55
93
|
}
|
|
56
|
-
|
|
57
|
-
settle({ kind: "no-list" });
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
let parsed;
|
|
61
|
-
try {
|
|
62
|
-
parsed = JSON.parse(stdout);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
settle({ kind: "no-list" });
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
69
|
-
settle({ kind: "no-list" });
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const models = parsed.models;
|
|
73
|
-
if (!Array.isArray(models)) {
|
|
74
|
-
settle({ kind: "no-list" });
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const slugs = [];
|
|
78
|
-
for (const entry of models) {
|
|
79
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
80
|
-
settle({ kind: "no-list" });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const record = entry;
|
|
84
|
-
if (typeof record.slug !== "string" || typeof record.visibility !== "string") {
|
|
85
|
-
settle({ kind: "no-list" });
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (record.visibility === "list") {
|
|
89
|
-
slugs.push(record.slug);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (slugs.length === 0) {
|
|
93
|
-
settle({ kind: "no-list" });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
settle({ kind: "list", models: slugs });
|
|
94
|
+
settle({ kind: "no-list" });
|
|
97
95
|
});
|
|
98
96
|
});
|
|
99
97
|
}
|
package/lib/prompts/prompts.js
CHANGED
|
@@ -35,7 +35,7 @@ ${s.readOnlyParagraph}`;
|
|
|
35
35
|
2. A contract referenced by ${s.specRef} is not honored.
|
|
36
36
|
3. A rule referenced by ${s.specRef} is not applied in the changes — you have the positive obligation to verify that every referenced rule is actively applied; a referenced rule that is not applied is FAIL.
|
|
37
37
|
4. A contract or rule from the global lists above that you determine should have been applied but was not, even if not referenced by ${s.specRef}, is FAIL.
|
|
38
|
-
5. A behavior rule from the behavior-rule list above whose \`.
|
|
38
|
+
5. A behavior rule from the behavior-rule list above whose \`.spec/flanders\` scope encloses the files the working-tree changes touch is not honored by the changes, even if ${s.specRef} did not reference it, is FAIL.
|
|
39
39
|
|
|
40
40
|
Exhaustiveness: do not stop at the first violation. Run every verification you are required to run and every additional check your judgment deems applicable, even after one of them has already produced a FAIL. The five conditions above and the ${s.critProtocolName} are executed in full on every invocation; encountering a violation in one of them does not exempt you from completing the rest. The goal is that a single review produces the complete list of fixes ${s.nextWorker} needs to apply.
|
|
41
41
|
|
|
@@ -149,13 +149,13 @@ If you cannot confidently determine how to build the project, leave the build sc
|
|
|
149
149
|
|
|
150
150
|
## Available rules
|
|
151
151
|
|
|
152
|
-
Each path below is the rule's namespace. Before deciding the build or test commands, scan this list and open every rule whose scope governs how the project is built or how its tests are run — for example, any rule under a \`testing/\` or \`build/\` subfolder of a \`.
|
|
152
|
+
Each path below is the rule's namespace. Before deciding the build or test commands, scan this list and open every rule whose scope governs how the project is built or how its tests are run — for example, any rule under a \`testing/\` or \`build/\` subfolder of a \`.spec/rules\` folder, or any rule that prescribes a specific runner, invocation form, required flag, or toolchain convention. Reading is not optional for rules whose scope matches build/test invocation. The commands you write must honor those rules: if a rule pins the test runner to a specific invocation form or required flag, the script you write must use that exact invocation.
|
|
153
153
|
|
|
154
154
|
${"<RULE_LIST>"}
|
|
155
155
|
|
|
156
156
|
Git boundary: you must not execute any git command that modifies repository state. Read-only git commands (\`git status\`, \`git log\`, \`git show\`, \`git diff\`, \`git blame\`, \`git ls-files\`) are allowed if they help you understand the project; commits, staging, branches, tags, stashes, resets, restores, merges, rebases, edits under \`.git/\`, and any remote git operation are forbidden. See rules/ai/agents/no-git-writes.md for the full obligation.
|
|
157
157
|
|
|
158
|
-
Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.
|
|
158
|
+
Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
|
|
159
159
|
|
|
160
160
|
${foregroundBoundary}`,
|
|
161
161
|
worker: `You are the worker agent for the Flanders implement iteration loop.
|
|
@@ -173,7 +173,7 @@ Your output will be inspected by an adversarial reviewer immediately after you f
|
|
|
173
173
|
2. A contract referenced by the task is not honored.
|
|
174
174
|
3. A rule referenced by the task is not actively applied — acknowledging a rule is not enough; the changes must demonstrate compliance.
|
|
175
175
|
4. A contract or rule from the global lists below that the reviewer determines should have been applied but was not — even if the task did not reference it.
|
|
176
|
-
5. A behavior rule from the behavior-rule list below whose \`.
|
|
176
|
+
5. A behavior rule from the behavior-rule list below whose \`.spec/flanders\` scope encloses the files your changes touch is not honored by the changes — in-scope behavior rules are mandatory whether or not the task links them.
|
|
177
177
|
|
|
178
178
|
Condition 4 causes most rejections in practice. Rules whose scope matches your changes (testing rules when you touch tests, disposable rules when you touch async resources, UI rules when you change terminal output, etc.) are mandatory whether the task links them or not. Treat the global contract and rule lists below as part of your specification, not as optional reading. The reviewer will also enumerate every occurrence of a pattern violation, not just the first one, so partial compliance within a file is itself a FAIL.
|
|
179
179
|
|
|
@@ -209,7 +209,7 @@ Do not flip the task's checkbox in the plan file. Flanders flips the checkbox it
|
|
|
209
209
|
|
|
210
210
|
Git boundary: you must not execute any git command that modifies repository state — no \`git add\`, \`git commit\`, \`git stash\`, \`git reset\`, \`git restore\`, \`git checkout -b\`, \`git branch\`, \`git tag\`, \`git rebase\`, \`git merge\`, \`git cherry-pick\`, no edits under \`.git/\`, and no remote git operations (\`fetch\`, \`pull\`, \`push\`). Read-only git commands (\`git status\`, \`git diff\`, \`git log\`, \`git show\`, \`git blame\`, \`git ls-files\`) are allowed when you need to inspect the repo. Leave your implementation as a dirty working tree — Flanders performs the commit itself once your changes pass build, test, and review. If your task seems to require a git write, stop and explain it in your final message instead of doing it. The full obligation lives in rules/ai/agents/no-git-writes.md.
|
|
211
211
|
|
|
212
|
-
Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.
|
|
212
|
+
Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
|
|
213
213
|
|
|
214
214
|
${foregroundBoundary}
|
|
215
215
|
|
|
@@ -227,7 +227,7 @@ ${"<RULE_LIST>"}
|
|
|
227
227
|
|
|
228
228
|
## Available behavior rules
|
|
229
229
|
|
|
230
|
-
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes you author are named, placed, and organized within the part of the project tree that the rule's \`.
|
|
230
|
+
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes you author are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes. You must honor every behavior rule whose \`.spec/flanders\` scope encloses the files your changes touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them; the reviewer FAILS for any in-scope behavior rule the changes do not honor.
|
|
231
231
|
|
|
232
232
|
${"<BEHAVIOR_RULE_LIST>"}`,
|
|
233
233
|
reviewer: `You are the adversarial reviewer agent for the Flanders implement iteration loop.
|
|
@@ -257,7 +257,7 @@ ${"<RULE_LIST>"}
|
|
|
257
257
|
|
|
258
258
|
## Available behavior rules
|
|
259
259
|
|
|
260
|
-
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes the worker authored are named, placed, and organized within the part of the project tree that the rule's \`.
|
|
260
|
+
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes the worker authored are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes. You must verify that the working-tree changes honor every behavior rule whose \`.spec/flanders\` scope encloses the files they touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them.
|
|
261
261
|
|
|
262
262
|
${"<BEHAVIOR_RULE_LIST>"}
|
|
263
263
|
|
|
@@ -265,7 +265,7 @@ ${implementReviewerMethodology.audit}
|
|
|
265
265
|
|
|
266
266
|
Git boundary: you are an inspection-only agent. You must not execute any git command that modifies repository state — no \`git add\`, \`git commit\`, \`git stash\`, \`git reset\`, \`git restore\`, \`git checkout -b\`, \`git branch\`, \`git tag\`, no edits under \`.git/\`, and no remote git operations. Read-only git commands (\`git status\`, \`git diff\`, \`git log\`, \`git show\`, \`git blame\`, \`git ls-files\`) are allowed and are how you should inspect the worker's changes. The full obligation lives in rules/ai/agents/no-git-writes.md.
|
|
267
267
|
|
|
268
|
-
Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.
|
|
268
|
+
Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
|
|
269
269
|
|
|
270
270
|
${foregroundBoundary}`,
|
|
271
271
|
prep: `You are the prep agent for the Flanders implement iteration loop.
|
|
@@ -289,7 +289,7 @@ You must not implement, modify, or write anything in the project. Do not use Edi
|
|
|
289
289
|
|
|
290
290
|
## Spec-folder write boundary
|
|
291
291
|
|
|
292
|
-
You must not write to any \`.
|
|
292
|
+
You must not write to any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may create, modify, delete, or rename files in them. See shared/spec-folder-write-authority.md for the full obligation.
|
|
293
293
|
|
|
294
294
|
## Git boundary
|
|
295
295
|
|
|
@@ -311,7 +311,7 @@ ${"<RULE_LIST>"}
|
|
|
311
311
|
|
|
312
312
|
## Available behavior rules
|
|
313
313
|
|
|
314
|
-
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes Flanders authors are named, placed, and organized within the part of the project tree that the rule's \`.
|
|
314
|
+
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes Flanders authors are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes; every behavior rule whose \`.spec/flanders\` scope encloses the files this task's work touches must be honored. Read all of those in-scope behavior rules now, so the worker and reviewer forked from this session honor them. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them.
|
|
315
315
|
|
|
316
316
|
${"<BEHAVIOR_RULE_LIST>"}
|
|
317
317
|
|
package/lib/prompts/skills.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const planSkillBody: string;
|
|
2
|
-
export declare const specSkillBody = "---\ndescription: Translate a free-form request into one or more spec markdown files inside the project's .docs/contracts and .docs/rules folders.\n---\n\nYou are the /flanders-spec skill. Your sole deliverable is one or more markdown files inside the project's `.docs/contracts` and `.docs/rules` folders. You must not write, modify, or delete any source code or any file outside the project's `.docs/contracts` and `.docs/rules` folders.\n\n## Input resolution\n\nThe user invokes you as: /flanders-spec [<data>]\n\n- If <data> is omitted, take the user's natural-language request from the same turn or from subsequent turns of the conversation.\n- If <data> is supplied and resolves to an existing file path, read the file's content and use it as input.\n- If <data> is supplied and does not resolve to an existing file, use the value verbatim as inline input.\n\n## What a contract is\n\nA contract is a markdown document that describes the public behavior of the directory its `.docs` folder scopes \u2014 what code outside that directory relies on \u2014 stated abstractly, never naming internal symbols, internal data shapes, or paths inside a source directory; at the project-root `.docs` folder the boundary is the whole project, so its contracts capture what the end user sees, does, and relies on.\n\nContracts are the public surface of the scope they belong to. Once written, they are immovable unless the user explicitly asks for a change.\n\n## What a rule is\n\nA rule is a markdown document that captures a single, atomic piece of implementation guidance internal to the directory its `.docs` folder scopes \u2014 a constraint, convention, or pattern that the directory's code must follow. Each rule file describes exactly one rule.\n\nBundles of related rules (for example, the multiple obligations that make up SOLID, or the dispose pattern) are modeled as a subfolder under the scope's `.docs/rules` folder containing one file per atomic rule inside, never as a single multi-rule file.\n\nThe namespace of a rule is its path relative to the project root. The namespace is what downstream tooling uses to organize, filter, and reference rules.\n\nRules are immovable once written unless the user explicitly asks for a change.\n\n## Contract vs rule: how the skill classifies and places\n\nFor every obligation in the request, the skill decides whether it is a contract or a rule and which `.docs` folder it belongs to: public behavior across a scope's boundary is a contract, internal implementation guidance is a rule, and the spec lands in the `.docs` folder of the lowest directory that encloses all the code its obligation governs \u2014 an obligation governing one directory goes in that directory's `.docs` folder, an obligation spanning sibling directories goes in their nearest common ancestor's `.docs` folder, and an obligation about project-boundary behavior goes in the project-root `.docs` folder. A spec is a contract because code outside its scope depends on it, not because the end user observes it directly; only at the project root do those coincide. A single request may carry both kinds and may span several scopes; the skill writes each spec to its proper `.docs` folder in the same invocation. The classification and placement are the skill's own decisions, not questions put to the user \u2014 the user reviews and approves them in the drafting phase before anything is persisted.\n\n## Procedure\n\n1. Resolve the input from the invocation rule above.\n2. Discover every directory named `.docs` across the whole project tree at every depth, excluding every path the project's git ignore rules exclude (for example by enumerating with `git ls-files --cached --others --exclude-standard` \u2014 which lists tracked files plus untracked-but-not-ignored files \u2014 and dropping any candidate that sits under a git-ignored path, for example via `git check-ignore`); the files under each `.docs/contracts` subfolder form the canonical contracts listing and the files under each `.docs/rules` subfolder form the canonical rules listing; the files under each `.docs/flanders` subfolder form the behavior-rule listing, treating every file inside a `.docs/flanders` folder at any depth as a behavior rule; each file is identified by its namespace \u2014 its path relative to the project root, which for nested `.docs` folders includes the directories above the `.docs` folder, so files sharing a leaf filename in different `.docs` folders stay distinct. A missing or empty discovery \u2014 no `.docs` folder, or none containing any file \u2014 yields an empty canonical reference set. This is the canonical reference set for the run.\n3. Before drafting anything, read every file in the canonical reference set that is relevant to the request. Reading the relevant existing files is mandatory \u2014 a draft begun without having read them is invalid, regardless of your confidence. When in doubt, read rather than omit: a deliverable that contradicts or duplicates an unread file is invalid.\n\n **Behavior rules.** Before persisting any file, read every behavior rule whose `.docs/flanders` scope encloses each file you are about to write \u2014 the `.docs` folder you write the file into and every parent `.docs` folder up to the project root \u2014 and honor all of them. Behavior rules govern how you name, place, and organize the files you author; an in-scope behavior rule is binding on that work, not advisory, and applies whether or not the request mentions it.\n\n **Rename sweep.** When the run renames, relocates, or removes a term that can recur across the corpus beyond the files it is editing \u2014 a folder name, a path segment, a flag, an identifier, a fixed string, or a namespace convention \u2014 establish the full set of files to touch by searching the whole corpus (every contract and every rule) for the old term and inspecting every occurrence the search returns. The search is exhaustive over the corpus; it is not narrowed to the files you already planned to edit. Triage each occurrence individually into exactly one of two dispositions: an occurrence the rename must update, which the run edits; or an occurrence that is an intentional reference the rename leaves alone (for example a cross-reference to an unrelated file, or a deliberately unchanged example). An occurrence is never left unexamined on the grounds that its file looked irrelevant. Coverage is driven by the token, not by a judgment of which files are relevant: the set of files the run edits is the union of the occurrences the sweep shows must be updated, and a file the sweep surfaces that you had not planned to touch is added to the run.\n4. **Clarification phase.** Whenever the request leaves an obligation ambiguous, leaves a UI or logic decision unspecified, leaves a rule or its scope of enforcement unspecified, or admits multiple valid interpretations, ask the user clarifying questions sequentially \u2014 one question per turn. Prefer multiple-choice questions when the answer space is bounded. Use open-ended questions only when multiple-choice would force a false dichotomy. When two or three substantially different approaches would all satisfy the request, present those approaches with a short trade-off summary for each and ask the user to pick or redirect, instead of silently choosing one. The clarification phase ends only when you have enough information to draft files that contain no placeholders, no contradictions, and no scope ambiguity.\n5. **Drafting phase.** Before persisting any file:\n - Present the planned file layout \u2014 which files will exist, which `.docs` folder each falls in, which are contracts and which are rules (the classification and placement made visible), and the key obligations of each file \u2014 as a structured summary, and wait for user approval or redirection.\n - Once the layout is approved, persist every resulting file in a single batch without any further per-file or per-section confirmation step.\n - Update related existing files in place when the request affects obligations they already cover, and create new files only for obligations not already covered. Do not duplicate an obligation across files, whether within a folder or across the two folders.\n - Do not write historical, transitional, or migration content into the contracts and rules you produce. A spec file states only the present spec \u2014 what the software does now and what the code must do now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing (for example, \"replaces the former X\", \"previously Y\", a changelog of what this run changed) belongs in the commit message or pull-request description, not in a permanent spec file.\n - State each obligation as the behavior the code performs \u2014 what the software does and what the code must do. The set of things the code does not do is unbounded, so do not enumerate non-actions: satisfy a request to remove or stop a behavior by describing the resulting positive behavior, letting the removed behavior vanish by omission. Write an explicit prohibition \u2014 \"does not\u2026\", \"never\u2026\", \"must not\u2026\" \u2014 only when it is load-bearing, namely when BOTH conditions hold: (1) its absence is not already entailed by a positive obligation (a positive obligation stated exclusively, such as \"the only X is Y\", already excludes every alternative, so a prohibition restating that exclusion adds nothing); and (2) it guards a behavior a competent implementer reading only the positive spec would plausibly introduce \u2014 an attractive default they would reach for, or a behavior that falls inside a responsibility the component otherwise has. A prohibition that fails either condition is redundant and is not written.\n6. After approval, run a self-review pass before finalizing each file: re-read the draft and check for placeholders left behind, contradictions with the canonical reference set, ambiguous wording, and scope that drifted beyond what the user requested. Fix any issue in place; if a fix would change the meaning of content the user approved in the layout summary, surface the issue to the user and ask before applying it.\n7. Organize the resulting files in whichever shape best fits the content:\n - Within a `.docs/contracts` folder: a single descriptive file when the scope is small; multiple files when the scope has clearly separable concerns (for example, a logic file and a UI file); subfolders grouping related files when the scope has multiple sections (for example, one folder per major feature).\n - Within a `.docs/rules` folder: one file per atomic rule. Subfolders group thematically related rules (for example, a testing/ subfolder for testing rules, a dependencies/ subfolder for dependency-management rules, a solid/ subfolder with one file per SOLID principle). A bundle of related rules MUST be modeled as a subfolder of single-rule files, never as one multi-rule file.\n8. Filenames must be descriptive of their content \u2014 the user must be able to tell what each contract file covers, and which single rule each rule file pins, from the name alone.\n9. Before declaring complete, run the final validator over the persisted file(s). The validator is the gate \u2014 only declare complete when it returns PASS. The procedure is in the Final validation section below.\n\n## Final validation\n\nBefore declaring this skill complete, run a final validator over the persisted or updated file(s). The validator is the gate \u2014 only declare complete when it returns PASS.\n\nWhat a passing gate certifies: a pass certifies that the file(s) you wrote or updated in this run satisfy the validator's checks and do not contradict the corpus the validator inspected. It does not certify that the entire corpus is mutually consistent independent of this run's files \u2014 whole-corpus consistency is not re-verified on every run, and a passing gate is not a proof of it. Report a pass as a statement about this run's own output, never as a statement that the whole spec is globally sound.\n\n### Validator host\n\nLaunch the validator as a fresh subagent via the AI tool's subagent mechanism, in a session that does not share context with this drafting session. The fresh session is load-bearing \u2014 it forces the validator to re-derive its judgments from the file(s) on disk rather than from this session's confirmation bias.\n\nThe subagent mechanism is tool-specific. In Claude Code, the host spawns the validator through the Agent tool. In Codex CLI, the host spawns it through whatever Codex documents as its subagent surface at the time of the run.\n\nYou may fall back to an inline pass (running the validator in this same session) only when the subagent mechanism is unavailable in the current environment, or when a subagent invocation returns an unrecoverable error (spawn failure, transport error, environment refusal). Inline fallback for ergonomic reasons \u2014 the artifact looks small, tokens feel tight, you are confident \u2014 is forbidden. When you take the inline path, state in chat that you are falling back and name the concrete reason; a silent fallback is a violation. The validator is read-only on the project and does not run git mutations.\n\n### Validator inputs\n\nPass the validator:\n- The absolute path(s) to the file(s) you just wrote or updated, partitioned by folder, plus an explicit enumeration of which subset of the canonical listings is under audit in this run.\n- The canonical contracts listing captured in step 2 of the procedure.\n- The canonical rules listing captured in step 2 of the procedure.\n- When this run renamed, relocated, or removed a term that can recur across the corpus (per the Rename sweep obligation in the procedure above), the explicit list of those old term(s). The list is empty when the run changed no such term.\n- The verbatim text of the check categories below. The host MUST inline these categories in the validator's prompt \u2014 it does not just point the validator at a file by path, and it does not rely on the validator discovering them by transitive reading.\n\nThe validator reads the file(s) in full, plus any contract or rule from the listings it judges relevant to forming its verdict.\n\n### Validator checks\n\nThree categories, all mandatory; failure in any one is a FAIL. Each category is audited independently and violations are enumerated exhaustively. The category set is selected by the folder each file landed in: category A applies to each file that landed in a `.docs/contracts` folder; category B applies to each file that landed in a `.docs/rules` folder; category C applies to every file written or updated in the run.\n\n**A. Contract artifacts (each file written or updated under a `.docs/contracts` folder)**\n\nA1. Format and shape. Every contract file written or updated lives inside a `.docs/contracts` folder, is non-empty, is markdown, has a filename descriptive of its content, and is organized as described in step 7 of the procedure.\n\nA2. Content rules. Verify the artifact satisfies EACH of the following independently:\n- Free of placeholders. No `<TBD>` or analogous task markers, no template-style blanks, no parenthetical \"(to be decided)\" deferrals.\n- Free of ambiguous wording. Open-ended phrasing \u2014 hedge phrases such as `may or may not`, `left to the implementer`, `pick one of`, `or equivalent`, `at the discretion of the user`, `or \u2014 alternatively \u2014`, `or X if Y`, or any formulation that leaves an obligation undefined \u2014 is FAIL. A contract obligation reads as a single concrete commitment, never as a choice the reader is invited to make.\n- Describes only public behavior across its scope's boundary \u2014 what code outside the directory its `.docs` folder scopes can rely on, stated abstractly, where for the project-root `.docs/contracts` that boundary is the end user. References to implementation details \u2014 names of specific classes, functions, libraries, modules, or frameworks; paths under src/, lib/, or any source folder; internal data shapes that consumers across the boundary do not directly observe; private helper or coordinator types; the existence of specific test files or runners; choices of HTTP client, ORM, database engine, build tool, or other tooling consumers do not directly interact with \u2014 are out of scope of a contract and are FAIL.\n- Free of historical or migration content. The contract states only the present spec \u2014 what the software does now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.\n- No obligation is duplicated across files. When the request relates to obligations already covered by existing files, those files are updated rather than duplicated.\n\n**B. Rule artifacts (each file written or updated under a `.docs/rules` folder)**\n\nB1. Format and shape. Every rule file written or updated lives inside a `.docs/rules` folder, is non-empty, is markdown, captures exactly one atomic rule (a file that pins two or more independent obligations is FAIL \u2014 those obligations belong in separate files inside the same subfolder), has a filename descriptive of the single rule it captures, and bundles of related rules are modeled as subfolders containing single-rule files.\n\nB2. Content rules. Verify the artifact satisfies EACH of the following independently:\n- Free of placeholders. No `<TBD>` or analogous task markers, no template-style blanks, no parenthetical \"(to be decided)\" deferrals.\n- Scope of enforcement is explicit. The rule has a \"Who this applies to\" or equivalent section that names exactly which code, agents, surfaces, file patterns, or call sites the rule binds. An open-ended \"applies everywhere\" without enumeration of the actual surface is FAIL.\n- Free of ambiguous wording. Hedge phrasing that turns the obligation into a choice instead of a commitment \u2014 `may or may not`, `pick one of`, `or equivalent`, `left to the implementer`, `at the discretion of`, `or \u2014 alternatively \u2014`, `or X if Y` \u2014 is FAIL.\n- Free of historical or migration content. The rule states only the present spec. Content recording what the rule used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.\n- No rule is duplicated across files. When the request relates to a rule already covered by an existing file, that file is updated rather than a parallel duplicate created.\n\n**C. Non-contradiction with the canonical corpus (every file written or updated in this run)**\n\nThe file(s) written or updated do not contradict any other contract in the project's contracts (the canonical contracts listing, spanning every `.docs/contracts` folder) and do not contradict any rule in the project's rules (the canonical rules listing, spanning every `.docs/rules` folder). A contradiction is an obligation pinned in two places with incompatible content. Tightening, extending, or qualifying an existing obligation in a way the existing text already allows is not a contradiction.\n\n**Renamed-term sweep.** For each old term the host passed (the terms this run renamed, relocated, or removed), the validator searches the whole corpus for that term and inspects every occurrence. An occurrence that is a stale, un-updated instance of the renamed term \u2014 a leftover that should have been changed in this run \u2014 is FAIL. An occurrence that is an intentional reference the rename correctly leaves alone is not a violation. The validator drives this check from the passed term(s), not from its own judgment of which files are relevant, so that a stale occurrence in a file the validator would not otherwise open is still caught. When the passed list is empty, this check is vacuously satisfied.\n\nOut of scope of the validator: verifying that paths referenced by a contract or rule physically resolve on disk.\n\n### Validator output\n\nThe validator's final response ends with a single verdict line, with no Evidence Report and no other multi-line content after it:\n\n- `PASS`\n- `FAIL <enumerated issues>` \u2014 each issue stated clearly enough that the auto-fix step can act on it. Multiple issues are enumerated inline on that same final line, each independently actionable.\n\nIf the validator wants to show its work, it does so in the body of its response above the verdict line.\n\n### On FAIL: bounded triage-then-fix loop\n\nWhen the validator returns FAIL, enter the triage-then-fix loop:\n\n1. Triage each issue. For every issue enumerated in the FAIL report, classify it against the clarification-scope criteria of this skill's clarification phase \u2014 the same criteria that govern the initial clarification phase above: obligation ambiguous, UI or logic decision unspecified, rule or scope of enforcement unspecified, or multiple valid interpretations.\n2. For issues whose fix would commit the skill to an answer that, per the clarification phase, the user is the one who must give and that the user did not give in the initial clarification phase of this invocation: re-enter the clarification phase for that specific ambiguity before any rewrite. Re-entered clarification follows the same mechanics \u2014 one question per turn, multiple-choice preferred when bounded, no bundling. The re-entered phase is scoped to the specific ambiguity at hand and never re-asks decisions the user has already given in this invocation.\n3. For every other issue \u2014 formatting, naming, descriptive-filename violations, placeholders that do not require a user-level decision, and any other fix the skill is authorized to resolve on its own \u2014 apply in place without asking.\n4. Rewrite the affected file(s) in place, addressing every enumerated issue.\n5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file(s).\n6. Repeat the cycle. Perform at most FIVE triage-then-fix passes per /flanders-spec invocation. The fifth FAIL ends the loop.\n\nWhen the loop ends with a PASS at any iteration, declare complete.\n\nWhen the loop ends with FAIL after five passes, do not declare complete: surface the last FAIL report and the file path(s) to the user in chat, then stop.\n\n## Output language\n\nResolve the natural language to write each spec file in by this priority order:\n\n1. When the request explicitly states a language to write in, write in that language.\n2. Otherwise, when at least one spec file already exists in the project, write in the language of those existing spec files, determined by inspecting a single existing spec file \u2014 reading more than one is unnecessary, since the corpus is kept in one language.\n3. Otherwise \u2014 when the request names no language and no spec file exists yet \u2014 write in the language the request itself is written in.\n\nDo not translate already-written content; the resolved language governs only the content you author in this run.\n\n## Interaction language\n\nEvery message you address to the user during the run \u2014 your clarifying questions, the approach trade-off summaries, the drafting-phase layout summary, and any other text you print in chat \u2014 is written in the natural language of the user's most recent message in the conversation. When the user switches the language they write in partway through the interaction, every subsequent message you address to the user follows the language of their latest message. This is resolved independently of the Output language above: it governs only what you say to the user in the conversation, never the language of the spec files you write.\n\n## Idempotency and overwrites\n\nExisting files in the project's `.docs/contracts` and `.docs/rules` folders are not protected. Because you receive the current state of both folders and update related files in place, re-running with related input will modify those files rather than create parallel duplicates. Preserving prior versions is the user's responsibility (typically through version control).";
|
|
2
|
+
export declare const specSkillBody = "---\ndescription: Translate a free-form request into one or more spec markdown files inside the project's .spec/contracts and .spec/rules folders.\n---\n\nYou are the /flanders-spec skill. Your sole deliverable is one or more markdown files inside the project's `.spec/contracts` and `.spec/rules` folders. You must not write, modify, or delete any source code or any file outside the project's `.spec/contracts` and `.spec/rules` folders.\n\n## Input resolution\n\nThe user invokes you as: /flanders-spec [<data>]\n\n- If <data> is omitted, take the user's natural-language request from the same turn or from subsequent turns of the conversation.\n- If <data> is supplied and resolves to an existing file path, read the file's content and use it as input.\n- If <data> is supplied and does not resolve to an existing file, use the value verbatim as inline input.\n\n## What a contract is\n\nA contract is a markdown document that describes the public behavior of the directory its `.spec` folder scopes \u2014 what code outside that directory relies on \u2014 stated abstractly, never naming internal symbols, internal data shapes, or paths inside a source directory; at the project-root `.spec` folder the boundary is the whole project, so its contracts capture what the end user sees, does, and relies on.\n\nContracts are the public surface of the scope they belong to. Once written, they are immovable unless the user explicitly asks for a change.\n\n## What a rule is\n\nA rule is a markdown document that captures a single, atomic piece of implementation guidance internal to the directory its `.spec` folder scopes \u2014 a constraint, convention, or pattern that the directory's code must follow. The rule is the atomic unit, not the file: each rule is a single atomic obligation, and a rule file holds one rule on its own, or several related rules as discrete atomic sections.\n\nBundles of related rules (for example, the multiple obligations that make up SOLID, or the dispose pattern) are modeled either as a subfolder under the scope's `.spec/rules` folder containing one file per atomic rule, or as a single file that groups those related rules as discrete atomic sections. The atomic unit is the rule, not the file; both shapes keep every rule atomic.\n\nThe namespace of a rule is its path relative to the project root. The namespace is what downstream tooling uses to organize, filter, and reference rules.\n\nRules are immovable once written unless the user explicitly asks for a change.\n\n## Contract vs rule: how the skill classifies and places\n\nFor every obligation in the request, the skill decides whether it is a contract or a rule and which `.spec` folder it belongs to: public behavior across a scope's boundary is a contract, internal implementation guidance is a rule, and the spec lands in the `.spec` folder of the lowest directory that encloses all the code its obligation governs \u2014 an obligation governing one directory goes in that directory's `.spec` folder, an obligation spanning sibling directories goes in their nearest common ancestor's `.spec` folder, and an obligation about project-boundary behavior goes in the project-root `.spec` folder. A spec is a contract because code outside its scope depends on it, not because the end user observes it directly; only at the project root do those coincide. A single request may carry both kinds and may span several scopes; the skill writes each spec to its proper `.spec` folder in the same invocation. The classification and placement are the skill's own decisions, not questions put to the user \u2014 the user reviews and approves them in the drafting phase before anything is persisted.\n\n## Procedure\n\n1. Resolve the input from the invocation rule above.\n2. Discover every directory named `.spec` across the whole project tree at every depth, excluding every path the project's git ignore rules exclude (for example by enumerating with `git ls-files --cached --others --exclude-standard` \u2014 which lists tracked files plus untracked-but-not-ignored files \u2014 and dropping any candidate that sits under a git-ignored path, for example via `git check-ignore`); the files under each `.spec/contracts` subfolder form the canonical contracts listing and the files under each `.spec/rules` subfolder form the canonical rules listing; the files under each `.spec/flanders` subfolder form the behavior-rule listing, treating every file inside a `.spec/flanders` folder at any depth as a behavior rule; each file is identified by its namespace \u2014 its path relative to the project root, which for nested `.spec` folders includes the directories above the `.spec` folder, so files sharing a leaf filename in different `.spec` folders stay distinct. A missing or empty discovery \u2014 no `.spec` folder, or none containing any file \u2014 yields an empty canonical reference set. This is the canonical reference set for the run.\n3. Before drafting anything, read every file in the canonical reference set that is relevant to the request. Reading the relevant existing files is mandatory \u2014 a draft begun without having read them is invalid, regardless of your confidence. When in doubt, read rather than omit: a deliverable that contradicts or duplicates an unread file is invalid.\n\n **Behavior rules.** Before persisting any file, read every behavior rule whose `.spec/flanders` scope encloses each file you are about to write \u2014 the `.spec` folder you write the file into and every parent `.spec` folder up to the project root \u2014 and honor all of them. Behavior rules govern how you name, place, and organize the files you author; an in-scope behavior rule is binding on that work, not advisory, and applies whether or not the request mentions it.\n\n **Rename sweep.** When the run renames, relocates, or removes a term that can recur across the corpus beyond the files it is editing \u2014 a folder name, a path segment, a flag, an identifier, a fixed string, or a namespace convention \u2014 establish the full set of files to touch by searching the whole corpus (every contract and every rule) for the old term and inspecting every occurrence the search returns. The search is exhaustive over the corpus; it is not narrowed to the files you already planned to edit. Triage each occurrence individually into exactly one of two dispositions: an occurrence the rename must update, which the run edits; or an occurrence that is an intentional reference the rename leaves alone (for example a cross-reference to an unrelated file, or a deliberately unchanged example). An occurrence is never left unexamined on the grounds that its file looked irrelevant. Coverage is driven by the token, not by a judgment of which files are relevant: the set of files the run edits is the union of the occurrences the sweep shows must be updated, and a file the sweep surfaces that you had not planned to touch is added to the run.\n4. **Clarification phase.** Whenever the request leaves an obligation ambiguous, leaves a UI or logic decision unspecified, leaves a rule or its scope of enforcement unspecified, or admits multiple valid interpretations, ask the user clarifying questions sequentially \u2014 one question per turn. Prefer multiple-choice questions when the answer space is bounded. Use open-ended questions only when multiple-choice would force a false dichotomy. When two or three substantially different approaches would all satisfy the request, present those approaches with a short trade-off summary for each and ask the user to pick or redirect, instead of silently choosing one. The clarification phase ends only when you have enough information to draft files that contain no placeholders, no contradictions, and no scope ambiguity.\n5. **Drafting phase.** Before persisting any file:\n - Present the planned file layout \u2014 which files will exist, which `.spec` folder each falls in, which are contracts and which are rules (the classification and placement made visible), and the key obligations of each file \u2014 as a structured summary, and wait for user approval or redirection.\n - Once the layout is approved, persist every resulting file in a single batch without any further per-file or per-section confirmation step.\n - Update related existing files in place when the request affects obligations they already cover, and create new files only for obligations not already covered. Do not duplicate an obligation across files, whether within a folder or across the two folders.\n - Do not write historical, transitional, or migration content into the contracts and rules you produce. A spec file states only the present spec \u2014 what the software does now and what the code must do now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing (for example, \"replaces the former X\", \"previously Y\", a changelog of what this run changed) belongs in the commit message or pull-request description, not in a permanent spec file.\n - State each obligation as the behavior the code performs \u2014 what the software does and what the code must do. The set of things the code does not do is unbounded, so do not enumerate non-actions: satisfy a request to remove or stop a behavior by describing the resulting positive behavior, letting the removed behavior vanish by omission. Write an explicit prohibition \u2014 \"does not\u2026\", \"never\u2026\", \"must not\u2026\" \u2014 only when it is load-bearing, namely when BOTH conditions hold: (1) its absence is not already entailed by a positive obligation (a positive obligation stated exclusively, such as \"the only X is Y\", already excludes every alternative, so a prohibition restating that exclusion adds nothing); and (2) it guards a behavior a competent implementer reading only the positive spec would plausibly introduce \u2014 an attractive default they would reach for, or a behavior that falls inside a responsibility the component otherwise has. A prohibition that fails either condition is redundant and is not written.\n6. After approval, run a self-review pass before finalizing each file: re-read the draft and check for placeholders left behind, contradictions with the canonical reference set, ambiguous wording, and scope that drifted beyond what the user requested. Fix any issue in place; if a fix would change the meaning of content the user approved in the layout summary, surface the issue to the user and ask before applying it.\n7. Organize the resulting files in whichever shape best fits the content:\n - Within a `.spec/contracts` folder: a single descriptive file when the scope is small; multiple files when the scope has clearly separable concerns (for example, a logic file and a UI file); subfolders grouping related files when the scope has multiple sections (for example, one folder per major feature).\n - Within a `.spec/rules` folder: the rule is the atomic unit, not the file. A standalone file holds one isolated rule; a single file groups a cluster of related rules as discrete atomic sections; and a subfolder holds a file per rule (or per sub-cluster) when the scope spans several distinct clusters (for example, a testing/ subfolder for testing rules, a dependencies/ subfolder for dependency-management rules, a solid/ subfolder for the SOLID principles). A subfolder of single-rule files and a single file grouping related rules as sections are both valid; each rule stays atomic in either shape.\n8. Filenames must be descriptive of their content \u2014 the user must be able to tell what each contract file covers, and which rule or cluster of related rules each rule file pins, from the name alone.\n9. Before declaring complete, run the final validator over the persisted file(s). The validator is the gate \u2014 only declare complete when it returns PASS. The procedure is in the Final validation section below.\n10. After declaring the spec complete, recommend the next step and, at the user's choice, launch it, per the Recommending and launching the next step section below.\n\n## Final validation\n\nBefore declaring this skill complete, run a final validator over the persisted or updated file(s). The validator is the gate \u2014 only declare complete when it returns PASS.\n\nWhat a passing gate certifies: a pass certifies that the file(s) you wrote or updated in this run satisfy the validator's checks and do not contradict the corpus the validator inspected. It does not certify that the entire corpus is mutually consistent independent of this run's files \u2014 whole-corpus consistency is not re-verified on every run, and a passing gate is not a proof of it. Report a pass as a statement about this run's own output, never as a statement that the whole spec is globally sound.\n\n### Validator host\n\nLaunch the validator as a fresh subagent via the AI tool's subagent mechanism, in a session that does not share context with this drafting session. The fresh session is load-bearing \u2014 it forces the validator to re-derive its judgments from the file(s) on disk rather than from this session's confirmation bias.\n\nThe subagent mechanism is tool-specific. In Claude Code, the host spawns the validator through the Agent tool. In Codex CLI, the host spawns it through whatever Codex documents as its subagent surface at the time of the run.\n\nYou may fall back to an inline pass (running the validator in this same session) only when the subagent mechanism is unavailable in the current environment, or when a subagent invocation returns an unrecoverable error (spawn failure, transport error, environment refusal). Inline fallback for ergonomic reasons \u2014 the artifact looks small, tokens feel tight, you are confident \u2014 is forbidden. When you take the inline path, state in chat that you are falling back and name the concrete reason; a silent fallback is a violation. The validator is read-only on the project and does not run git mutations.\n\n### Validator inputs\n\nPass the validator:\n- The absolute path(s) to the file(s) you just wrote or updated, partitioned by folder, plus an explicit enumeration of which subset of the canonical listings is under audit in this run.\n- The canonical contracts listing captured in step 2 of the procedure.\n- The canonical rules listing captured in step 2 of the procedure.\n- When this run renamed, relocated, or removed a term that can recur across the corpus (per the Rename sweep obligation in the procedure above), the explicit list of those old term(s). The list is empty when the run changed no such term.\n- The verbatim text of the check categories below. The host MUST inline these categories in the validator's prompt \u2014 it does not just point the validator at a file by path, and it does not rely on the validator discovering them by transitive reading.\n\nThe validator reads the file(s) in full, plus any contract or rule from the listings it judges relevant to forming its verdict.\n\n### Validator checks\n\nThree categories, all mandatory; failure in any one is a FAIL. Each category is audited independently and violations are enumerated exhaustively. The category set is selected by the folder each file landed in: category A applies to each file that landed in a `.spec/contracts` folder; category B applies to each file that landed in a `.spec/rules` folder; category C applies to every file written or updated in the run.\n\n**A. Contract artifacts (each file written or updated under a `.spec/contracts` folder)**\n\nA1. Format and shape. Every contract file written or updated lives inside a `.spec/contracts` folder, is non-empty, is markdown, has a filename descriptive of its content, and is organized as described in step 7 of the procedure.\n\nA2. Content rules. Verify the artifact satisfies EACH of the following independently:\n- Free of placeholders. No `<TBD>` or analogous task markers, no template-style blanks, no parenthetical \"(to be decided)\" deferrals.\n- Free of ambiguous wording. Open-ended phrasing \u2014 hedge phrases such as `may or may not`, `left to the implementer`, `pick one of`, `or equivalent`, `at the discretion of the user`, `or \u2014 alternatively \u2014`, `or X if Y`, or any formulation that leaves an obligation undefined \u2014 is FAIL. A contract obligation reads as a single concrete commitment, never as a choice the reader is invited to make.\n- Describes only public behavior across its scope's boundary \u2014 what code outside the directory its `.spec` folder scopes can rely on, stated abstractly, where for the project-root `.spec/contracts` that boundary is the end user. References to implementation details \u2014 names of specific classes, functions, libraries, modules, or frameworks; paths under src/, lib/, or any source folder; internal data shapes that consumers across the boundary do not directly observe; private helper or coordinator types; the existence of specific test files or runners; choices of HTTP client, ORM, database engine, build tool, or other tooling consumers do not directly interact with \u2014 are out of scope of a contract and are FAIL.\n- Free of historical or migration content. The contract states only the present spec \u2014 what the software does now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.\n- No obligation is duplicated across files. When the request relates to obligations already covered by existing files, those files are updated rather than duplicated.\n\n**B. Rule artifacts (each file written or updated under a `.spec/rules` folder)**\n\nB1. Format and shape. Every rule file written or updated lives inside a `.spec/rules` folder, is non-empty, is markdown, and captures one or more atomic rules \u2014 one rule on its own, or several related rules as discrete atomic sections, where each rule pins exactly one obligation; a file is FAIL only when it fuses unrelated obligations into one non-atomic rule, or presents a section as a rule that is not itself atomic. Its filename is descriptive of the rule or cluster of related rules it pins, and bundles of related rules are modeled either as a subfolder containing one file per atomic rule or as a single file grouping those related rules as discrete atomic sections \u2014 both shapes are valid.\n\nB2. Content rules. Verify the artifact satisfies EACH of the following independently:\n- Free of placeholders. No `<TBD>` or analogous task markers, no template-style blanks, no parenthetical \"(to be decided)\" deferrals.\n- Scope of enforcement is explicit. The rule has a \"Who this applies to\" or equivalent section that names exactly which code, agents, surfaces, file patterns, or call sites the rule binds. An open-ended \"applies everywhere\" without enumeration of the actual surface is FAIL.\n- Free of ambiguous wording. Hedge phrasing that turns the obligation into a choice instead of a commitment \u2014 `may or may not`, `pick one of`, `or equivalent`, `left to the implementer`, `at the discretion of`, `or \u2014 alternatively \u2014`, `or X if Y` \u2014 is FAIL.\n- Free of historical or migration content. The rule states only the present spec. Content recording what the rule used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.\n- No rule is duplicated across files. When the request relates to a rule already covered by an existing file, that file is updated rather than a parallel duplicate created.\n\n**C. Non-contradiction with the canonical corpus (every file written or updated in this run)**\n\nThe file(s) written or updated do not contradict any other contract in the project's contracts (the canonical contracts listing, spanning every `.spec/contracts` folder) and do not contradict any rule in the project's rules (the canonical rules listing, spanning every `.spec/rules` folder). A contradiction is an obligation pinned in two places with incompatible content. Tightening, extending, or qualifying an existing obligation in a way the existing text already allows is not a contradiction.\n\n**Renamed-term sweep.** For each old term the host passed (the terms this run renamed, relocated, or removed), the validator searches the whole corpus for that term and inspects every occurrence. An occurrence that is a stale, un-updated instance of the renamed term \u2014 a leftover that should have been changed in this run \u2014 is FAIL. An occurrence that is an intentional reference the rename correctly leaves alone is not a violation. The validator drives this check from the passed term(s), not from its own judgment of which files are relevant, so that a stale occurrence in a file the validator would not otherwise open is still caught. When the passed list is empty, this check is vacuously satisfied.\n\nOut of scope of the validator: verifying that paths referenced by a contract or rule physically resolve on disk.\n\n### Validator output\n\nThe validator's final response ends with a single verdict line, with no Evidence Report and no other multi-line content after it:\n\n- `PASS`\n- `FAIL <enumerated issues>` \u2014 each issue stated clearly enough that the auto-fix step can act on it. Multiple issues are enumerated inline on that same final line, each independently actionable.\n\nIf the validator wants to show its work, it does so in the body of its response above the verdict line.\n\n### On FAIL: bounded triage-then-fix loop\n\nWhen the validator returns FAIL, enter the triage-then-fix loop:\n\n1. Triage each issue. For every issue enumerated in the FAIL report, classify it against the clarification-scope criteria of this skill's clarification phase \u2014 the same criteria that govern the initial clarification phase above: obligation ambiguous, UI or logic decision unspecified, rule or scope of enforcement unspecified, or multiple valid interpretations.\n2. For issues whose fix would commit the skill to an answer that, per the clarification phase, the user is the one who must give and that the user did not give in the initial clarification phase of this invocation: re-enter the clarification phase for that specific ambiguity before any rewrite. Re-entered clarification follows the same mechanics \u2014 one question per turn, multiple-choice preferred when bounded, no bundling. The re-entered phase is scoped to the specific ambiguity at hand and never re-asks decisions the user has already given in this invocation.\n3. For every other issue \u2014 formatting, naming, descriptive-filename violations, placeholders that do not require a user-level decision, and any other fix the skill is authorized to resolve on its own \u2014 apply in place without asking.\n4. Rewrite the affected file(s) in place, addressing every enumerated issue.\n5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file(s).\n6. Repeat the cycle. Perform at most FIVE triage-then-fix passes per /flanders-spec invocation. The fifth FAIL ends the loop.\n\nWhen the loop ends with a PASS at any iteration, declare complete.\n\nWhen the loop ends with FAIL after five passes, do not declare complete: surface the last FAIL report and the file path(s) to the user in chat, then stop.\n\n## Recommending and launching the next step\n\nOnce you have declared the spec complete \u2014 the spec files persisted and the final validator returned PASS \u2014 offer to continue into the next step in the same session. Make this offer only on successful completion: when the bounded triage-then-fix loop exhausts without a PASS, surface the last FAIL report and stop, and make no such offer.\n\nAsk the user which skill to launch next: /flanders-plan, /flanders-work, or neither. Recommend one of them based on the implementation effort the spec you just wrote implies \u2014 recommend /flanders-work when the spec describes a single, small, self-contained change, and recommend /flanders-plan when the spec describes larger work that spans multiple obligations or scopes or needs an ordered, multi-step implementation. The user accepts the recommendation, chooses the other skill, or declines.\n\nWhen the user chooses /flanders-plan or /flanders-work, launch it by invoking it in the same session with no <data> argument, so the launched skill takes its input from the conversation \u2014 the original request together with the spec you just wrote. The run then proceeds under that skill; launching it leaves your own deliverable and write boundary unchanged, so you write only this run's spec files and never code or a plan file. When the user declines, end the run.\n\n## Output language\n\nResolve the natural language to write each spec file in by this priority order:\n\n1. When the request explicitly states a language to write in, write in that language.\n2. Otherwise, when at least one spec file already exists in the project, write in the language of those existing spec files, determined by inspecting a single existing spec file \u2014 reading more than one is unnecessary, since the corpus is kept in one language.\n3. Otherwise \u2014 when the request names no language and no spec file exists yet \u2014 write in the language the request itself is written in.\n\nDo not translate already-written content; the resolved language governs only the content you author in this run.\n\n## Interaction language\n\nEvery message you address to the user during the run \u2014 your clarifying questions, the approach trade-off summaries, the drafting-phase layout summary, and any other text you print in chat \u2014 is written in the natural language of the user's most recent message in the conversation. When the user switches the language they write in partway through the interaction, every subsequent message you address to the user follows the language of their latest message. This is resolved independently of the Output language above: it governs only what you say to the user in the conversation, never the language of the spec files you write.\n\n## Idempotency and overwrites\n\nExisting files in the project's `.spec/contracts` and `.spec/rules` folders are not protected. Because you receive the current state of both folders and update related files in place, re-running with related input will modify those files rather than create parallel duplicates. Preserving prior versions is the user's responsibility (typically through version control).";
|
|
3
3
|
export declare const workSkillBody: string;
|
package/lib/prompts/skills.js
CHANGED
|
@@ -20,16 +20,16 @@ The user invokes you as: /flanders-plan [<data>]
|
|
|
20
20
|
## Procedure
|
|
21
21
|
|
|
22
22
|
1. Resolve the input from the invocation rule above.
|
|
23
|
-
2. Discover every directory named \`.
|
|
23
|
+
2. Discover every directory named \`.spec\` across the whole project tree at every depth, excluding every path the project's git ignore rules exclude (for example by enumerating with \`git ls-files --cached --others --exclude-standard\` — which lists tracked files plus untracked-but-not-ignored files — and dropping any candidate that sits under a git-ignored path, for example via \`git check-ignore\`); the files under each \`.spec/contracts\` subfolder form the canonical contracts listing and the files under each \`.spec/rules\` subfolder form the canonical rules listing; the files under each \`.spec/flanders\` subfolder form the behavior-rule listing, treating every file inside a \`.spec/flanders\` folder at any depth as a behavior rule; each file is identified by its namespace — its path relative to the project root, which for nested \`.spec\` folders includes the directories above the \`.spec\` folder, so files sharing a leaf filename in different \`.spec\` folders stay distinct.
|
|
24
24
|
|
|
25
|
-
**Behavior rules.** Before persisting the plan file, read every behavior rule whose \`.
|
|
25
|
+
**Behavior rules.** Before persisting the plan file, read every behavior rule whose \`.spec/flanders\` scope encloses the plan file you are about to write — the project-root \`.spec\` folder and any other \`.spec\` folder whose scope encloses the \`plans/\` target — and honor all of them. Behavior rules govern how you name and organize the plan file you author; an in-scope behavior rule is binding on that work, not advisory, and applies whether or not the request mentions it. This adds no new task-line link obligation: the plan-file format and its contract and rule links are unchanged.
|
|
26
26
|
3. **Clarification phase.** Ask the user clarifying questions only when the question targets one of three things: an implementation choice in the code the tasks will produce that the request does not specify, or a task-scope ambiguity you cannot reasonably infer from the request or from the canonical contracts and rules, or a load-bearing runtime-behavior premise the plan would otherwise have to assert without backing (see the plan content rules below). Any other doubt is resolved silently: pick the most reasonable default and proceed, documenting the choice in the relevant task's description when it is plan-local and load-bearing. Permitted questions are asked sequentially — one question per turn, multiple-choice preferred when the answer space is bounded.
|
|
27
27
|
|
|
28
28
|
When the doubt is about how the code should be implemented, resolve it through one of two outcomes:
|
|
29
|
-
- **Cross-cutting convention** — the answer would apply to all future code of the same kind in the project and belongs in a \`.
|
|
29
|
+
- **Cross-cutting convention** — the answer would apply to all future code of the same kind in the project and belongs in a \`.spec/rules\` folder. Surface the gap to the user and recommend creating the rule via /flanders-spec before the plan is drafted, instead of silently baking the decision into the plan. The user may explicitly elect to treat the decision as plan-local for this run; in that case it follows the plan-local outcome below.
|
|
30
30
|
- **Plan-local implementation choice** — the answer is specific to the requested work and does not generalize. The chosen answer is embedded in the relevant task's description and acceptance criteria, and is never promoted to a rule.
|
|
31
31
|
|
|
32
|
-
The skill itself never writes to any \`.
|
|
32
|
+
The skill itself never writes to any \`.spec/rules\` or \`.spec/contracts\` folder. Rule creation, when the user elects it, happens through /flanders-spec as a separate, user-initiated act.
|
|
33
33
|
4. **Drafting phase.** Once the clarification phase is complete, persist the plan file directly without presenting a layout summary, a section-by-section draft, or any other pre-write approval step. The user reviews the written plan file after the fact.
|
|
34
34
|
5. Persist exactly one markdown file inside the project's plans/ folder. The filename must be descriptive of the plan's subject.
|
|
35
35
|
6. Upon successful completion, print the summary described in the Summary section below. If the plan cannot be made compliant with the Plan content rules, do not declare complete: surface the issue along with the plan file path to the user in chat.
|
|
@@ -98,7 +98,7 @@ Tasks are written in the order they must be implemented, accounting for dependen
|
|
|
98
98
|
- For every leaf task, link the relevant rule file or files by their listed relative path. The planner MUST read every rule file it determines is relevant to the request before drafting the plan; reading the relevant rules is not optional. When a rule's enforcement is bound to a specific scope, reference that scope alongside the file path.
|
|
99
99
|
- Rule selection per task is scope-driven, not topic-driven. Before listing the rule links for a leaf task, walk the rules listing and ask: which rule namespaces are in scope for the work this task actually performs? Use the namespace as the scope hint. Heuristics: a task that modifies or adds tests must link every applicable rule under a \`testing/\` subfolder; a task that creates or modifies anything with timers, listeners, controllers, child processes, or other async lifecycle must link every applicable rule under a \`disposables/\` subfolder; a task that changes terminal UI or live-region output must link every applicable rule under a \`ui/\` subfolder. Walk every namespace whose scope could plausibly apply, and pick every file whose obligation could be triggered by the task. Under-linking is costly: the downstream implementor is FAILed by the adversarial reviewer for any global rule that should have applied but was not applied, so when in doubt, link rather than omit.
|
|
100
100
|
- Tasks are numbered hierarchically (1, 1.1, 1.2, 2, 2.1, ...) per the Plan file format section above.
|
|
101
|
-
- No task may describe work that creates, modifies, deletes, or renames files inside any \`.
|
|
101
|
+
- No task may describe work that creates, modifies, deletes, or renames files inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder (the bounded checkbox/metrics update that the implement command holds is not available to tasks).
|
|
102
102
|
- Never produce a plan that violates any contract or rule on the canonical lists.
|
|
103
103
|
|
|
104
104
|
## Post-write verification
|
|
@@ -146,7 +146,7 @@ Five categories, all mandatory; failure in any one is a FAIL. Each category is a
|
|
|
146
146
|
|
|
147
147
|
2. Semantic dependency order. Tasks appear top-to-bottom in implementation order. The audit is semantic, not numeric: read each task's description and acceptance criteria and confirm that no task depends on work performed by a task that appears later in the document. A plan whose numbering is well-formed but whose dependencies flow upward is FAIL.
|
|
148
148
|
|
|
149
|
-
3. Spec-folder write boundary. No task (leaf or parent) describes work that creates, modifies, deletes, or renames any file inside any \`.
|
|
149
|
+
3. Spec-folder write boundary. No task (leaf or parent) describes work that creates, modifies, deletes, or renames any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. There is no exception for flipping checkboxes or rewriting metrics: those mutations are performed programmatically by the implement command and are never described by a task.
|
|
150
150
|
|
|
151
151
|
4. Plan content rules. Verify the plan satisfies EACH of the following independently:
|
|
152
152
|
- Free of placeholders. No \`<TBD>\` or analogous task markers, no template-style blanks, no parenthetical "(to be decided)" deferrals.
|
|
@@ -208,12 +208,12 @@ Every message you address to the user during the run — your clarifying questio
|
|
|
208
208
|
|
|
209
209
|
## Missing contracts or rules
|
|
210
210
|
|
|
211
|
-
If no \`.
|
|
211
|
+
If no \`.spec/contracts\` folder contains any file, warn the user in chat and produce a plan that includes whatever contracts the request implicitly requires before any implementation work. If no \`.spec/rules\` folder contains any file, warn the user in chat and proceed without rule references on the resulting tasks.`;
|
|
212
212
|
exports.specSkillBody = `---
|
|
213
|
-
description: Translate a free-form request into one or more spec markdown files inside the project's .
|
|
213
|
+
description: Translate a free-form request into one or more spec markdown files inside the project's .spec/contracts and .spec/rules folders.
|
|
214
214
|
---
|
|
215
215
|
|
|
216
|
-
You are the /flanders-spec skill. Your sole deliverable is one or more markdown files inside the project's \`.
|
|
216
|
+
You are the /flanders-spec skill. Your sole deliverable is one or more markdown files inside the project's \`.spec/contracts\` and \`.spec/rules\` folders. You must not write, modify, or delete any source code or any file outside the project's \`.spec/contracts\` and \`.spec/rules\` folders.
|
|
217
217
|
|
|
218
218
|
## Input resolution
|
|
219
219
|
|
|
@@ -225,15 +225,15 @@ The user invokes you as: /flanders-spec [<data>]
|
|
|
225
225
|
|
|
226
226
|
## What a contract is
|
|
227
227
|
|
|
228
|
-
A contract is a markdown document that describes the public behavior of the directory its \`.
|
|
228
|
+
A contract is a markdown document that describes the public behavior of the directory its \`.spec\` folder scopes — what code outside that directory relies on — stated abstractly, never naming internal symbols, internal data shapes, or paths inside a source directory; at the project-root \`.spec\` folder the boundary is the whole project, so its contracts capture what the end user sees, does, and relies on.
|
|
229
229
|
|
|
230
230
|
Contracts are the public surface of the scope they belong to. Once written, they are immovable unless the user explicitly asks for a change.
|
|
231
231
|
|
|
232
232
|
## What a rule is
|
|
233
233
|
|
|
234
|
-
A rule is a markdown document that captures a single, atomic piece of implementation guidance internal to the directory its \`.
|
|
234
|
+
A rule is a markdown document that captures a single, atomic piece of implementation guidance internal to the directory its \`.spec\` folder scopes — a constraint, convention, or pattern that the directory's code must follow. The rule is the atomic unit, not the file: each rule is a single atomic obligation, and a rule file holds one rule on its own, or several related rules as discrete atomic sections.
|
|
235
235
|
|
|
236
|
-
Bundles of related rules (for example, the multiple obligations that make up SOLID, or the dispose pattern) are modeled as a subfolder under the scope's \`.
|
|
236
|
+
Bundles of related rules (for example, the multiple obligations that make up SOLID, or the dispose pattern) are modeled either as a subfolder under the scope's \`.spec/rules\` folder containing one file per atomic rule, or as a single file that groups those related rules as discrete atomic sections. The atomic unit is the rule, not the file; both shapes keep every rule atomic.
|
|
237
237
|
|
|
238
238
|
The namespace of a rule is its path relative to the project root. The namespace is what downstream tooling uses to organize, filter, and reference rules.
|
|
239
239
|
|
|
@@ -241,30 +241,31 @@ Rules are immovable once written unless the user explicitly asks for a change.
|
|
|
241
241
|
|
|
242
242
|
## Contract vs rule: how the skill classifies and places
|
|
243
243
|
|
|
244
|
-
For every obligation in the request, the skill decides whether it is a contract or a rule and which \`.
|
|
244
|
+
For every obligation in the request, the skill decides whether it is a contract or a rule and which \`.spec\` folder it belongs to: public behavior across a scope's boundary is a contract, internal implementation guidance is a rule, and the spec lands in the \`.spec\` folder of the lowest directory that encloses all the code its obligation governs — an obligation governing one directory goes in that directory's \`.spec\` folder, an obligation spanning sibling directories goes in their nearest common ancestor's \`.spec\` folder, and an obligation about project-boundary behavior goes in the project-root \`.spec\` folder. A spec is a contract because code outside its scope depends on it, not because the end user observes it directly; only at the project root do those coincide. A single request may carry both kinds and may span several scopes; the skill writes each spec to its proper \`.spec\` folder in the same invocation. The classification and placement are the skill's own decisions, not questions put to the user — the user reviews and approves them in the drafting phase before anything is persisted.
|
|
245
245
|
|
|
246
246
|
## Procedure
|
|
247
247
|
|
|
248
248
|
1. Resolve the input from the invocation rule above.
|
|
249
|
-
2. Discover every directory named \`.
|
|
249
|
+
2. Discover every directory named \`.spec\` across the whole project tree at every depth, excluding every path the project's git ignore rules exclude (for example by enumerating with \`git ls-files --cached --others --exclude-standard\` — which lists tracked files plus untracked-but-not-ignored files — and dropping any candidate that sits under a git-ignored path, for example via \`git check-ignore\`); the files under each \`.spec/contracts\` subfolder form the canonical contracts listing and the files under each \`.spec/rules\` subfolder form the canonical rules listing; the files under each \`.spec/flanders\` subfolder form the behavior-rule listing, treating every file inside a \`.spec/flanders\` folder at any depth as a behavior rule; each file is identified by its namespace — its path relative to the project root, which for nested \`.spec\` folders includes the directories above the \`.spec\` folder, so files sharing a leaf filename in different \`.spec\` folders stay distinct. A missing or empty discovery — no \`.spec\` folder, or none containing any file — yields an empty canonical reference set. This is the canonical reference set for the run.
|
|
250
250
|
3. Before drafting anything, read every file in the canonical reference set that is relevant to the request. Reading the relevant existing files is mandatory — a draft begun without having read them is invalid, regardless of your confidence. When in doubt, read rather than omit: a deliverable that contradicts or duplicates an unread file is invalid.
|
|
251
251
|
|
|
252
|
-
**Behavior rules.** Before persisting any file, read every behavior rule whose \`.
|
|
252
|
+
**Behavior rules.** Before persisting any file, read every behavior rule whose \`.spec/flanders\` scope encloses each file you are about to write — the \`.spec\` folder you write the file into and every parent \`.spec\` folder up to the project root — and honor all of them. Behavior rules govern how you name, place, and organize the files you author; an in-scope behavior rule is binding on that work, not advisory, and applies whether or not the request mentions it.
|
|
253
253
|
|
|
254
254
|
**Rename sweep.** When the run renames, relocates, or removes a term that can recur across the corpus beyond the files it is editing — a folder name, a path segment, a flag, an identifier, a fixed string, or a namespace convention — establish the full set of files to touch by searching the whole corpus (every contract and every rule) for the old term and inspecting every occurrence the search returns. The search is exhaustive over the corpus; it is not narrowed to the files you already planned to edit. Triage each occurrence individually into exactly one of two dispositions: an occurrence the rename must update, which the run edits; or an occurrence that is an intentional reference the rename leaves alone (for example a cross-reference to an unrelated file, or a deliberately unchanged example). An occurrence is never left unexamined on the grounds that its file looked irrelevant. Coverage is driven by the token, not by a judgment of which files are relevant: the set of files the run edits is the union of the occurrences the sweep shows must be updated, and a file the sweep surfaces that you had not planned to touch is added to the run.
|
|
255
255
|
4. **Clarification phase.** Whenever the request leaves an obligation ambiguous, leaves a UI or logic decision unspecified, leaves a rule or its scope of enforcement unspecified, or admits multiple valid interpretations, ask the user clarifying questions sequentially — one question per turn. Prefer multiple-choice questions when the answer space is bounded. Use open-ended questions only when multiple-choice would force a false dichotomy. When two or three substantially different approaches would all satisfy the request, present those approaches with a short trade-off summary for each and ask the user to pick or redirect, instead of silently choosing one. The clarification phase ends only when you have enough information to draft files that contain no placeholders, no contradictions, and no scope ambiguity.
|
|
256
256
|
5. **Drafting phase.** Before persisting any file:
|
|
257
|
-
- Present the planned file layout — which files will exist, which \`.
|
|
257
|
+
- Present the planned file layout — which files will exist, which \`.spec\` folder each falls in, which are contracts and which are rules (the classification and placement made visible), and the key obligations of each file — as a structured summary, and wait for user approval or redirection.
|
|
258
258
|
- Once the layout is approved, persist every resulting file in a single batch without any further per-file or per-section confirmation step.
|
|
259
259
|
- Update related existing files in place when the request affects obligations they already cover, and create new files only for obligations not already covered. Do not duplicate an obligation across files, whether within a folder or across the two folders.
|
|
260
260
|
- Do not write historical, transitional, or migration content into the contracts and rules you produce. A spec file states only the present spec — what the software does now and what the code must do now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing (for example, "replaces the former X", "previously Y", a changelog of what this run changed) belongs in the commit message or pull-request description, not in a permanent spec file.
|
|
261
261
|
- State each obligation as the behavior the code performs — what the software does and what the code must do. The set of things the code does not do is unbounded, so do not enumerate non-actions: satisfy a request to remove or stop a behavior by describing the resulting positive behavior, letting the removed behavior vanish by omission. Write an explicit prohibition — "does not…", "never…", "must not…" — only when it is load-bearing, namely when BOTH conditions hold: (1) its absence is not already entailed by a positive obligation (a positive obligation stated exclusively, such as "the only X is Y", already excludes every alternative, so a prohibition restating that exclusion adds nothing); and (2) it guards a behavior a competent implementer reading only the positive spec would plausibly introduce — an attractive default they would reach for, or a behavior that falls inside a responsibility the component otherwise has. A prohibition that fails either condition is redundant and is not written.
|
|
262
262
|
6. After approval, run a self-review pass before finalizing each file: re-read the draft and check for placeholders left behind, contradictions with the canonical reference set, ambiguous wording, and scope that drifted beyond what the user requested. Fix any issue in place; if a fix would change the meaning of content the user approved in the layout summary, surface the issue to the user and ask before applying it.
|
|
263
263
|
7. Organize the resulting files in whichever shape best fits the content:
|
|
264
|
-
- Within a \`.
|
|
265
|
-
- Within a \`.
|
|
266
|
-
8. Filenames must be descriptive of their content — the user must be able to tell what each contract file covers, and which
|
|
264
|
+
- Within a \`.spec/contracts\` folder: a single descriptive file when the scope is small; multiple files when the scope has clearly separable concerns (for example, a logic file and a UI file); subfolders grouping related files when the scope has multiple sections (for example, one folder per major feature).
|
|
265
|
+
- Within a \`.spec/rules\` folder: the rule is the atomic unit, not the file. A standalone file holds one isolated rule; a single file groups a cluster of related rules as discrete atomic sections; and a subfolder holds a file per rule (or per sub-cluster) when the scope spans several distinct clusters (for example, a testing/ subfolder for testing rules, a dependencies/ subfolder for dependency-management rules, a solid/ subfolder for the SOLID principles). A subfolder of single-rule files and a single file grouping related rules as sections are both valid; each rule stays atomic in either shape.
|
|
266
|
+
8. Filenames must be descriptive of their content — the user must be able to tell what each contract file covers, and which rule or cluster of related rules each rule file pins, from the name alone.
|
|
267
267
|
9. Before declaring complete, run the final validator over the persisted file(s). The validator is the gate — only declare complete when it returns PASS. The procedure is in the Final validation section below.
|
|
268
|
+
10. After declaring the spec complete, recommend the next step and, at the user's choice, launch it, per the Recommending and launching the next step section below.
|
|
268
269
|
|
|
269
270
|
## Final validation
|
|
270
271
|
|
|
@@ -293,22 +294,22 @@ The validator reads the file(s) in full, plus any contract or rule from the list
|
|
|
293
294
|
|
|
294
295
|
### Validator checks
|
|
295
296
|
|
|
296
|
-
Three categories, all mandatory; failure in any one is a FAIL. Each category is audited independently and violations are enumerated exhaustively. The category set is selected by the folder each file landed in: category A applies to each file that landed in a \`.
|
|
297
|
+
Three categories, all mandatory; failure in any one is a FAIL. Each category is audited independently and violations are enumerated exhaustively. The category set is selected by the folder each file landed in: category A applies to each file that landed in a \`.spec/contracts\` folder; category B applies to each file that landed in a \`.spec/rules\` folder; category C applies to every file written or updated in the run.
|
|
297
298
|
|
|
298
|
-
**A. Contract artifacts (each file written or updated under a \`.
|
|
299
|
+
**A. Contract artifacts (each file written or updated under a \`.spec/contracts\` folder)**
|
|
299
300
|
|
|
300
|
-
A1. Format and shape. Every contract file written or updated lives inside a \`.
|
|
301
|
+
A1. Format and shape. Every contract file written or updated lives inside a \`.spec/contracts\` folder, is non-empty, is markdown, has a filename descriptive of its content, and is organized as described in step 7 of the procedure.
|
|
301
302
|
|
|
302
303
|
A2. Content rules. Verify the artifact satisfies EACH of the following independently:
|
|
303
304
|
- Free of placeholders. No \`<TBD>\` or analogous task markers, no template-style blanks, no parenthetical "(to be decided)" deferrals.
|
|
304
305
|
- Free of ambiguous wording. Open-ended phrasing — hedge phrases such as \`may or may not\`, \`left to the implementer\`, \`pick one of\`, \`or equivalent\`, \`at the discretion of the user\`, \`or — alternatively —\`, \`or X if Y\`, or any formulation that leaves an obligation undefined — is FAIL. A contract obligation reads as a single concrete commitment, never as a choice the reader is invited to make.
|
|
305
|
-
- Describes only public behavior across its scope's boundary — what code outside the directory its \`.
|
|
306
|
+
- Describes only public behavior across its scope's boundary — what code outside the directory its \`.spec\` folder scopes can rely on, stated abstractly, where for the project-root \`.spec/contracts\` that boundary is the end user. References to implementation details — names of specific classes, functions, libraries, modules, or frameworks; paths under src/, lib/, or any source folder; internal data shapes that consumers across the boundary do not directly observe; private helper or coordinator types; the existence of specific test files or runners; choices of HTTP client, ORM, database engine, build tool, or other tooling consumers do not directly interact with — are out of scope of a contract and are FAIL.
|
|
306
307
|
- Free of historical or migration content. The contract states only the present spec — what the software does now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.
|
|
307
308
|
- No obligation is duplicated across files. When the request relates to obligations already covered by existing files, those files are updated rather than duplicated.
|
|
308
309
|
|
|
309
|
-
**B. Rule artifacts (each file written or updated under a \`.
|
|
310
|
+
**B. Rule artifacts (each file written or updated under a \`.spec/rules\` folder)**
|
|
310
311
|
|
|
311
|
-
B1. Format and shape. Every rule file written or updated lives inside a \`.
|
|
312
|
+
B1. Format and shape. Every rule file written or updated lives inside a \`.spec/rules\` folder, is non-empty, is markdown, and captures one or more atomic rules — one rule on its own, or several related rules as discrete atomic sections, where each rule pins exactly one obligation; a file is FAIL only when it fuses unrelated obligations into one non-atomic rule, or presents a section as a rule that is not itself atomic. Its filename is descriptive of the rule or cluster of related rules it pins, and bundles of related rules are modeled either as a subfolder containing one file per atomic rule or as a single file grouping those related rules as discrete atomic sections — both shapes are valid.
|
|
312
313
|
|
|
313
314
|
B2. Content rules. Verify the artifact satisfies EACH of the following independently:
|
|
314
315
|
- Free of placeholders. No \`<TBD>\` or analogous task markers, no template-style blanks, no parenthetical "(to be decided)" deferrals.
|
|
@@ -319,7 +320,7 @@ B2. Content rules. Verify the artifact satisfies EACH of the following independe
|
|
|
319
320
|
|
|
320
321
|
**C. Non-contradiction with the canonical corpus (every file written or updated in this run)**
|
|
321
322
|
|
|
322
|
-
The file(s) written or updated do not contradict any other contract in the project's contracts (the canonical contracts listing, spanning every \`.
|
|
323
|
+
The file(s) written or updated do not contradict any other contract in the project's contracts (the canonical contracts listing, spanning every \`.spec/contracts\` folder) and do not contradict any rule in the project's rules (the canonical rules listing, spanning every \`.spec/rules\` folder). A contradiction is an obligation pinned in two places with incompatible content. Tightening, extending, or qualifying an existing obligation in a way the existing text already allows is not a contradiction.
|
|
323
324
|
|
|
324
325
|
**Renamed-term sweep.** For each old term the host passed (the terms this run renamed, relocated, or removed), the validator searches the whole corpus for that term and inspects every occurrence. An occurrence that is a stale, un-updated instance of the renamed term — a leftover that should have been changed in this run — is FAIL. An occurrence that is an intentional reference the rename correctly leaves alone is not a violation. The validator drives this check from the passed term(s), not from its own judgment of which files are relevant, so that a stale occurrence in a file the validator would not otherwise open is still caught. When the passed list is empty, this check is vacuously satisfied.
|
|
325
326
|
|
|
@@ -349,6 +350,14 @@ When the loop ends with a PASS at any iteration, declare complete.
|
|
|
349
350
|
|
|
350
351
|
When the loop ends with FAIL after five passes, do not declare complete: surface the last FAIL report and the file path(s) to the user in chat, then stop.
|
|
351
352
|
|
|
353
|
+
## Recommending and launching the next step
|
|
354
|
+
|
|
355
|
+
Once you have declared the spec complete — the spec files persisted and the final validator returned PASS — offer to continue into the next step in the same session. Make this offer only on successful completion: when the bounded triage-then-fix loop exhausts without a PASS, surface the last FAIL report and stop, and make no such offer.
|
|
356
|
+
|
|
357
|
+
Ask the user which skill to launch next: /flanders-plan, /flanders-work, or neither. Recommend one of them based on the implementation effort the spec you just wrote implies — recommend /flanders-work when the spec describes a single, small, self-contained change, and recommend /flanders-plan when the spec describes larger work that spans multiple obligations or scopes or needs an ordered, multi-step implementation. The user accepts the recommendation, chooses the other skill, or declines.
|
|
358
|
+
|
|
359
|
+
When the user chooses /flanders-plan or /flanders-work, launch it by invoking it in the same session with no <data> argument, so the launched skill takes its input from the conversation — the original request together with the spec you just wrote. The run then proceeds under that skill; launching it leaves your own deliverable and write boundary unchanged, so you write only this run's spec files and never code or a plan file. When the user declines, end the run.
|
|
360
|
+
|
|
352
361
|
## Output language
|
|
353
362
|
|
|
354
363
|
Resolve the natural language to write each spec file in by this priority order:
|
|
@@ -365,7 +374,7 @@ Every message you address to the user during the run — your clarifying questio
|
|
|
365
374
|
|
|
366
375
|
## Idempotency and overwrites
|
|
367
376
|
|
|
368
|
-
Existing files in the project's \`.
|
|
377
|
+
Existing files in the project's \`.spec/contracts\` and \`.spec/rules\` folders are not protected. Because you receive the current state of both folders and update related files in place, re-running with related input will modify those files rather than create parallel duplicates. Preserving prior versions is the user's responsibility (typically through version control).`;
|
|
369
378
|
exports.workSkillBody = `---
|
|
370
379
|
description: Carry a single self-contained piece of work from request to reviewed completion in the current session, gating the result through one adversarial reviewer subagent, without authoring a plan or running the implement pipeline.
|
|
371
380
|
---
|
|
@@ -391,7 +400,7 @@ The user invokes you as: /flanders-work [<data>]
|
|
|
391
400
|
|
|
392
401
|
## Performing the work
|
|
393
402
|
|
|
394
|
-
Implement the request directly in this session: edit the project's code and update or extend its tests so the new behavior is covered. Honor every contract, rule, and behavior rule in the project's spec corpus whose scope your changes touch — discovered across the project's \`.
|
|
403
|
+
Implement the request directly in this session: edit the project's code and update or extend its tests so the new behavior is covered. Honor every contract, rule, and behavior rule in the project's spec corpus whose scope your changes touch — discovered across the project's \`.spec\` folders (the files under each \`.spec/contracts\` folder are contracts, the files under each \`.spec/rules\` folder are rules, and the files under each \`.spec/flanders\` folder are behavior rules) — whether or not the request names them. A contract or rule whose scope your changes touch is binding even when the request never mentions it; a behavior rule whose \`.spec/flanders\` scope encloses a file you author or change governs how you name, place, and organize that file. Treat the corpus as part of your specification, not optional reading.
|
|
395
404
|
|
|
396
405
|
## Build and test
|
|
397
406
|
|
|
@@ -403,7 +412,7 @@ Proceed to the review only once the build and test gates have both passed; a gat
|
|
|
403
412
|
|
|
404
413
|
## Spec-folder write boundary
|
|
405
414
|
|
|
406
|
-
Neither the work you perform nor the reviewer subagent creates, modifies, deletes, or renames any file inside any \`.
|
|
415
|
+
Neither the work you perform nor the reviewer subagent creates, modifies, deletes, or renames any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. Those folders are the project's source of truth, governed by their own dedicated skills; consult them freely but never write to them.
|
|
407
416
|
|
|
408
417
|
## The reviewer
|
|
409
418
|
|
|
@@ -422,7 +431,7 @@ You may fall back to running the review inline in this same session only when th
|
|
|
422
431
|
Assemble the reviewer subagent's prompt as four parts, in order:
|
|
423
432
|
|
|
424
433
|
1. A framing that states the spec under review is the user's request that you implemented, quoting or summarizing that request so the reviewer can measure the work against it.
|
|
425
|
-
2. The available contracts, the available rules, and the available behavior rules as lists of their namespaces (each namespace is a path relative to the project root), discovered across the project's \`.
|
|
434
|
+
2. The available contracts, the available rules, and the available behavior rules as lists of their namespaces (each namespace is a path relative to the project root), discovered across the project's \`.spec\` folders, placed above the methodology so the methodology's references to "the global lists above" and "the behavior-rule list above" resolve.
|
|
426
435
|
3. The absolute path of the error-log file you provisioned for this round — this is the "error-log file" the methodology refers to — with the instruction to record its verdict there.
|
|
427
436
|
4. The methodology below, verbatim.
|
|
428
437
|
|
|
@@ -4,9 +4,13 @@ export type FlandersRole = Readonly<{
|
|
|
4
4
|
model: string;
|
|
5
5
|
effort: string;
|
|
6
6
|
}>;
|
|
7
|
+
export type FlandersReviewer = FlandersRole & Readonly<{
|
|
8
|
+
optional: boolean;
|
|
9
|
+
}>;
|
|
7
10
|
export type FlandersConfig = Readonly<{
|
|
8
11
|
worker: FlandersRole;
|
|
9
|
-
reviewers: readonly
|
|
12
|
+
reviewers: readonly FlandersReviewer[];
|
|
13
|
+
minimumReviews: number;
|
|
10
14
|
}>;
|
|
11
15
|
type ReadArgs = Readonly<{
|
|
12
16
|
projectRoot: string;
|
|
@@ -32,7 +32,15 @@ function validate(raw, filePath) {
|
|
|
32
32
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
33
33
|
throw new Error(`Malformed config at ${filePath}: missing or invalid field "reviewers[${i}]"`);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
const reviewer = entry;
|
|
36
|
+
validateRole(reviewer, `reviewers[${i}]`, filePath);
|
|
37
|
+
if (!("optional" in reviewer) || typeof reviewer["optional"] !== "boolean") {
|
|
38
|
+
throw new Error(`Malformed config at ${filePath}: missing or invalid field "reviewers[${i}].optional"`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const minimumReviews = obj["minimumReviews"];
|
|
42
|
+
if (typeof minimumReviews !== "number" || !Number.isInteger(minimumReviews) || minimumReviews < 1 || minimumReviews > reviewers.length) {
|
|
43
|
+
throw new Error(`Malformed config at ${filePath}: field "minimumReviews" must be an integer in [1, ${reviewers.length}]`);
|
|
36
44
|
}
|
|
37
45
|
return raw;
|
|
38
46
|
}
|
|
@@ -9,14 +9,14 @@ function classifySpecPaths(paths) {
|
|
|
9
9
|
const flanders = [];
|
|
10
10
|
for (const filePath of paths) {
|
|
11
11
|
const segments = filePath.split("/");
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
12
|
+
const specIndex = segments.indexOf(".spec");
|
|
13
|
+
if (specIndex === -1) {
|
|
14
14
|
continue;
|
|
15
15
|
}
|
|
16
|
-
if (segments.length <=
|
|
16
|
+
if (segments.length <= specIndex + 2) {
|
|
17
17
|
continue;
|
|
18
18
|
}
|
|
19
|
-
const kind = segments[
|
|
19
|
+
const kind = segments[specIndex + 1];
|
|
20
20
|
if (kind === "contracts") {
|
|
21
21
|
contracts.push(filePath);
|
|
22
22
|
}
|