flanders 0.1.0 → 0.3.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/ai/AiRunner.d.ts +24 -0
- package/lib/ai/AiRunner.js +100 -0
- package/lib/ai/AiSession.d.ts +32 -0
- package/lib/ai/AiSession.js +143 -0
- package/lib/ai/ClaudeAdapter.d.ts +12 -0
- package/lib/ai/ClaudeAdapter.js +345 -0
- package/lib/ai/CodexAdapter.d.ts +13 -0
- package/lib/ai/CodexAdapter.js +354 -0
- package/lib/ai/ToolAdapter.d.ts +52 -0
- package/lib/ai/ToolAdapter.js +2 -0
- package/lib/cli.js +56 -35
- package/lib/commands/Implement.d.ts +22 -8
- package/lib/commands/Implement.js +296 -171
- package/lib/commands/Install.d.ts +31 -3
- package/lib/commands/Install.js +625 -49
- package/lib/commands/InstallModelProbe.d.ts +11 -0
- package/lib/commands/InstallModelProbe.js +99 -0
- package/lib/contexts.d.ts +5 -4
- package/lib/index.d.ts +5 -3
- package/lib/{PlanFile.d.ts → plan/PlanFile.d.ts} +8 -1
- package/lib/{PlanFile.js → plan/PlanFile.js} +44 -5
- package/lib/prompts/prompts.d.ts +48 -0
- package/lib/prompts/prompts.js +328 -0
- package/lib/prompts/skills.d.ts +3 -0
- package/lib/prompts/skills.js +464 -0
- package/lib/{Git.d.ts → system/Git.d.ts} +4 -2
- package/lib/{Git.js → system/Git.js} +63 -3
- package/lib/{ScriptRunner.d.ts → system/ScriptRunner.d.ts} +1 -1
- package/lib/system/ShellScriptContext.d.ts +36 -0
- package/lib/system/ShellScriptContext.js +70 -0
- package/lib/{fsUtils.d.ts → system/fsUtils.d.ts} +1 -1
- package/lib/{wait.d.ts → system/wait.d.ts} +1 -1
- package/lib/ui/BottomBlock.d.ts +9 -1
- package/lib/ui/BottomBlock.js +30 -14
- package/lib/ui/PromptHelper.d.ts +12 -0
- package/lib/ui/PromptHelper.js +32 -0
- package/lib/ui/TerminalSizeSource.d.ts +19 -0
- package/lib/ui/TerminalSizeSource.js +77 -0
- package/lib/ui/formatters.d.ts +14 -0
- package/lib/ui/formatters.js +65 -2
- package/lib/workspace/FlandersConfig.d.ts +23 -0
- package/lib/workspace/FlandersConfig.js +90 -0
- package/lib/workspace/SpecDiscovery.d.ts +12 -0
- package/lib/workspace/SpecDiscovery.js +40 -0
- package/lib/{Workspace.d.ts → workspace/Workspace.d.ts} +10 -2
- package/lib/{Workspace.js → workspace/Workspace.js} +52 -6
- package/package.json +3 -2
- package/lib/Claude.d.ts +0 -129
- package/lib/Claude.js +0 -387
- package/lib/ClaudeSession.d.ts +0 -34
- package/lib/ClaudeSession.js +0 -292
- package/lib/prompts.d.ts +0 -18
- package/lib/prompts.js +0 -221
- package/lib/skills.d.ts +0 -3
- package/lib/skills.js +0 -267
- /package/lib/{ScriptRunner.js → system/ScriptRunner.js} +0 -0
- /package/lib/{fsUtils.js → system/fsUtils.js} +0 -0
- /package/lib/{wait.js → system/wait.js} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ScriptContext } from "../contexts";
|
|
2
|
+
export type ModelProbeResult = Readonly<{
|
|
3
|
+
kind: "list";
|
|
4
|
+
models: readonly string[];
|
|
5
|
+
}> | Readonly<{
|
|
6
|
+
kind: "no-list";
|
|
7
|
+
}> | Readonly<{
|
|
8
|
+
kind: "not-started";
|
|
9
|
+
reason: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function probeModelList(script: ScriptContext): Promise<ModelProbeResult>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.probeModelList = probeModelList;
|
|
4
|
+
const COMMAND_NOT_FOUND_MARKERS = ["not found", "not recognized", "no such file"];
|
|
5
|
+
function errorMessage(e) {
|
|
6
|
+
return e instanceof Error ? e.message : String(e);
|
|
7
|
+
}
|
|
8
|
+
function isCommandNotFound(combinedOutput) {
|
|
9
|
+
const lower = combinedOutput.toLowerCase();
|
|
10
|
+
return COMMAND_NOT_FOUND_MARKERS.some(marker => lower.includes(marker));
|
|
11
|
+
}
|
|
12
|
+
function probeModelList(script) {
|
|
13
|
+
return new Promise(resolve => {
|
|
14
|
+
const stdoutChunks = [];
|
|
15
|
+
const stderrChunks = [];
|
|
16
|
+
let settled = false;
|
|
17
|
+
const settle = (result) => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
resolve(result);
|
|
22
|
+
};
|
|
23
|
+
const notStarted = (spawnMessage) => {
|
|
24
|
+
const stderr = stderrChunks.join("");
|
|
25
|
+
const stdout = stdoutChunks.join("");
|
|
26
|
+
const reason = stderr !== "" ? stderr : stdout !== "" ? stdout : (spawnMessage !== null && spawnMessage !== void 0 ? spawnMessage : "");
|
|
27
|
+
settle({ kind: "not-started", reason });
|
|
28
|
+
};
|
|
29
|
+
let proc;
|
|
30
|
+
try {
|
|
31
|
+
proc = script.spawn("codex", ["debug", "models"], { stdio: "pipe" });
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
notStarted(errorMessage(e));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const capture = (chunks) => (chunk) => {
|
|
38
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
39
|
+
};
|
|
40
|
+
if (proc.stdout) {
|
|
41
|
+
proc.stdout.on("data", capture(stdoutChunks));
|
|
42
|
+
}
|
|
43
|
+
if (proc.stderr) {
|
|
44
|
+
proc.stderr.on("data", capture(stderrChunks));
|
|
45
|
+
}
|
|
46
|
+
proc.on("error", (e) => {
|
|
47
|
+
notStarted(errorMessage(e));
|
|
48
|
+
});
|
|
49
|
+
proc.on("exit", (code) => {
|
|
50
|
+
const stdout = stdoutChunks.join("");
|
|
51
|
+
const stderr = stderrChunks.join("");
|
|
52
|
+
if (code === 127 || isCommandNotFound(stderr + stdout)) {
|
|
53
|
+
notStarted(null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (code !== 0) {
|
|
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 });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
package/lib/contexts.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type SpawnedReadable = {
|
|
|
3
3
|
on(event: "data", listener: (chunk: Buffer | string) => void): void;
|
|
4
4
|
};
|
|
5
5
|
export type SpawnedProcess = {
|
|
6
|
-
on(event: "exit", listener: (code: number | null) => void): void;
|
|
6
|
+
on(event: "exit", listener: (code: number | null, signal: string | null) => void): void;
|
|
7
7
|
on(event: "error", listener: (e: unknown) => void): void;
|
|
8
8
|
kill(signal: "SIGINT" | "SIGTERM"): void;
|
|
9
9
|
stdout?: SpawnedReadable;
|
|
@@ -13,9 +13,6 @@ export type SpawnedProcess = {
|
|
|
13
13
|
end(): void;
|
|
14
14
|
}>;
|
|
15
15
|
};
|
|
16
|
-
export interface ClaudeContext {
|
|
17
|
-
spawn(command: string, args: readonly string[], options: SpawnOptions): SpawnedProcess;
|
|
18
|
-
}
|
|
19
16
|
export interface ScriptContext {
|
|
20
17
|
spawn(command: string, args: readonly string[], options: SpawnOptions): SpawnedProcess;
|
|
21
18
|
}
|
|
@@ -27,6 +24,7 @@ export type FsDirEntry = Readonly<{
|
|
|
27
24
|
export interface FsContext {
|
|
28
25
|
readFile(path: string): Promise<string>;
|
|
29
26
|
writeFile(path: string, content: string): Promise<void>;
|
|
27
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
30
28
|
readdir(path: string): Promise<readonly FsDirEntry[]>;
|
|
31
29
|
stat(path: string): Promise<Readonly<{
|
|
32
30
|
size: number;
|
|
@@ -50,6 +48,9 @@ export interface TimeContext {
|
|
|
50
48
|
now(): number;
|
|
51
49
|
setTimeout(handler: () => void, ms: number): TimeoutHandle;
|
|
52
50
|
}
|
|
51
|
+
export interface RandomContext {
|
|
52
|
+
random(): number;
|
|
53
|
+
}
|
|
53
54
|
export type ChoiceOption = Readonly<{
|
|
54
55
|
label: string;
|
|
55
56
|
description?: string;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Flanders } from "./Flanders";
|
|
2
2
|
export type { FlandersContexts, FlandersOptions } from "./Flanders";
|
|
3
|
-
export type {
|
|
4
|
-
export type {
|
|
5
|
-
export type {
|
|
3
|
+
export type { FlandersConfig } from "./workspace/FlandersConfig";
|
|
4
|
+
export type { AskAnswer, AskChoiceOptions, AskContext, ChoiceOption, ScriptContext, FsContext, FsDirEntry, OutputContext, TimeContext, TimeoutHandle, SpawnedProcess, SpawnedReadable } from "./contexts";
|
|
5
|
+
export type { AiSessionResult, AiSessionOptions, AiSessionContexts } from "./ai/AiSession";
|
|
6
|
+
export type { ToolAdapter, ToolAdapterInvokeArgs, ToolAdapterUsageCallback, ToolEvent, ToolEventDone, ToolEventError, ToolEventOutput, ToolEventRateLimit, ToolEventSession, ToolName } from "./ai/ToolAdapter";
|
|
7
|
+
export type { PlatformContext } from "./workspace/Workspace";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FsContext } from "
|
|
1
|
+
import type { FsContext } from "../contexts";
|
|
2
2
|
export type TaskMetrics = Readonly<{
|
|
3
3
|
it: number;
|
|
4
4
|
ot: number;
|
|
@@ -12,6 +12,10 @@ export type PlanTask = Readonly<{
|
|
|
12
12
|
done: boolean;
|
|
13
13
|
metrics: TaskMetrics;
|
|
14
14
|
}>;
|
|
15
|
+
export type TaskLinkedPaths = Readonly<{
|
|
16
|
+
contracts: readonly string[];
|
|
17
|
+
rules: readonly string[];
|
|
18
|
+
}>;
|
|
15
19
|
export type PlanParseResult = Readonly<{
|
|
16
20
|
tasks: readonly PlanTask[];
|
|
17
21
|
malformed: readonly Readonly<{
|
|
@@ -21,7 +25,9 @@ export type PlanParseResult = Readonly<{
|
|
|
21
25
|
lineCount: number;
|
|
22
26
|
size: number;
|
|
23
27
|
}>;
|
|
28
|
+
export declare const TASK_LINE: RegExp;
|
|
24
29
|
export declare function parsePlan(content: string): PlanParseResult;
|
|
30
|
+
export declare function parseLinkedPaths(content: string, taskLineNumber: number): TaskLinkedPaths;
|
|
25
31
|
export declare class PlanFile {
|
|
26
32
|
readonly path: string;
|
|
27
33
|
private _content;
|
|
@@ -33,6 +39,7 @@ export declare class PlanFile {
|
|
|
33
39
|
updateMetrics(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
|
34
40
|
markDone(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
|
35
41
|
markOpen(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
|
42
|
+
linkedPaths(task: PlanTask): TaskLinkedPaths;
|
|
36
43
|
planTotals(): TaskMetrics;
|
|
37
44
|
private _rewriteTaskLine;
|
|
38
45
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PlanFile = void 0;
|
|
3
|
+
exports.PlanFile = exports.TASK_LINE = void 0;
|
|
4
4
|
exports.parsePlan = parsePlan;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
exports.parseLinkedPaths = parseLinkedPaths;
|
|
6
|
+
exports.TASK_LINE = /^(\s*[-*+]\s+)\[([ xX])\](\{[^}]*\})(\s.*)?$/;
|
|
7
|
+
const MALFORMED_TASK_LINE = /^\s*[-*+]\s+\[[^\]]*\]\{/;
|
|
7
8
|
const HEADING_NUMBER = /^#{1,6}\s+(\d+(?:\.\d+)*)\b/;
|
|
8
9
|
function validateMetrics(jsonStr) {
|
|
9
10
|
let parsed;
|
|
@@ -49,7 +50,7 @@ function parsePlan(content) {
|
|
|
49
50
|
if (headingMatch) {
|
|
50
51
|
currentNumber = headingMatch[1];
|
|
51
52
|
}
|
|
52
|
-
const match = TASK_LINE.exec(raw);
|
|
53
|
+
const match = exports.TASK_LINE.exec(raw);
|
|
53
54
|
if (match) {
|
|
54
55
|
const checkbox = match[2];
|
|
55
56
|
if (checkbox !== " " && checkbox !== "x") {
|
|
@@ -87,6 +88,41 @@ function parsePlan(content) {
|
|
|
87
88
|
size: content.length
|
|
88
89
|
};
|
|
89
90
|
}
|
|
91
|
+
const BACKTICK_PATH = /`([^`]+)`/g;
|
|
92
|
+
function extractPathsFromLine(text) {
|
|
93
|
+
const paths = [];
|
|
94
|
+
let m;
|
|
95
|
+
while ((m = BACKTICK_PATH.exec(text)) !== null) {
|
|
96
|
+
paths.push(m[1]);
|
|
97
|
+
}
|
|
98
|
+
return paths;
|
|
99
|
+
}
|
|
100
|
+
function taskBodyLines(content, taskLineNumber) {
|
|
101
|
+
const lines = content.split(/\r?\n/);
|
|
102
|
+
const body = [];
|
|
103
|
+
for (let i = taskLineNumber; i < lines.length; i++) {
|
|
104
|
+
const line = lines[i];
|
|
105
|
+
if (exports.TASK_LINE.test(line))
|
|
106
|
+
break;
|
|
107
|
+
body.push(line);
|
|
108
|
+
}
|
|
109
|
+
return body;
|
|
110
|
+
}
|
|
111
|
+
function parseLinkedPaths(content, taskLineNumber) {
|
|
112
|
+
const body = taskBodyLines(content, taskLineNumber);
|
|
113
|
+
let contracts = [];
|
|
114
|
+
let rules = [];
|
|
115
|
+
for (const line of body) {
|
|
116
|
+
const trimmed = line.trimStart();
|
|
117
|
+
if (trimmed.startsWith("Linked contracts:")) {
|
|
118
|
+
contracts = extractPathsFromLine(trimmed.slice("Linked contracts:".length));
|
|
119
|
+
}
|
|
120
|
+
else if (trimmed.startsWith("Linked rules:")) {
|
|
121
|
+
rules = extractPathsFromLine(trimmed.slice("Linked rules:".length));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { contracts, rules };
|
|
125
|
+
}
|
|
90
126
|
class PlanFile {
|
|
91
127
|
constructor(path, _content, _fs) {
|
|
92
128
|
this.path = path;
|
|
@@ -117,6 +153,9 @@ class PlanFile {
|
|
|
117
153
|
async markOpen(lineNumber, metrics) {
|
|
118
154
|
return this._rewriteTaskLine(lineNumber, metrics, "open");
|
|
119
155
|
}
|
|
156
|
+
linkedPaths(task) {
|
|
157
|
+
return parseLinkedPaths(this._content, task.line);
|
|
158
|
+
}
|
|
120
159
|
planTotals() {
|
|
121
160
|
const { tasks } = parsePlan(this._content);
|
|
122
161
|
let it = 0, ot = 0, t = 0;
|
|
@@ -137,7 +176,7 @@ class PlanFile {
|
|
|
137
176
|
if (raw === undefined) {
|
|
138
177
|
throw new Error(`Plan line ${lineNumber} not found in ${this.path}`);
|
|
139
178
|
}
|
|
140
|
-
const match = TASK_LINE.exec(raw);
|
|
179
|
+
const match = exports.TASK_LINE.exec(raw);
|
|
141
180
|
if (!match) {
|
|
142
181
|
throw new Error(`Plan line ${lineNumber} is not a task line: ${raw}`);
|
|
143
182
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const enum Placeholders {
|
|
2
|
+
PLAN_PATH = "<PLAN_PATH>",
|
|
3
|
+
TASK_LINE = "<TASK_LINE>",
|
|
4
|
+
TASK_TITLE = "<TASK_TITLE>",
|
|
5
|
+
BUILD_SCRIPT_PATH = "<BUILD_SCRIPT_PATH>",
|
|
6
|
+
TEST_SCRIPT_PATH = "<TEST_SCRIPT_PATH>",
|
|
7
|
+
ERROR_LOG_PATH = "<ERROR_LOG_PATH>",
|
|
8
|
+
ITERATION = "<ITERATION>",
|
|
9
|
+
CONTRACT_LIST = "<CONTRACT_LIST>",
|
|
10
|
+
RULE_LIST = "<RULE_LIST>",
|
|
11
|
+
BEHAVIOR_RULE_LIST = "<BEHAVIOR_RULE_LIST>"
|
|
12
|
+
}
|
|
13
|
+
export interface ReviewerMethodologySurface {
|
|
14
|
+
changeSetIntro: string;
|
|
15
|
+
specRef: string;
|
|
16
|
+
ownerChanges: string;
|
|
17
|
+
ownerProducedNoDiff: string;
|
|
18
|
+
critRef: string;
|
|
19
|
+
critRefShort: string;
|
|
20
|
+
critRefShortPlural: string;
|
|
21
|
+
passObject: string;
|
|
22
|
+
errorLogInline: string;
|
|
23
|
+
emptyChangeSetCitation: string;
|
|
24
|
+
readOnlyParagraph: string;
|
|
25
|
+
failCondition1: string;
|
|
26
|
+
critProtocolName: string;
|
|
27
|
+
nextWorker: string;
|
|
28
|
+
critProtocolHeading: string;
|
|
29
|
+
ownerChangesEvidence: string;
|
|
30
|
+
taxonomy: string;
|
|
31
|
+
reviewProtocolIntro: string;
|
|
32
|
+
ownerChangesShort: string;
|
|
33
|
+
errorLogPath: string;
|
|
34
|
+
nextWorkerActor: string;
|
|
35
|
+
errorLogPlain: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function buildReviewerMethodology(s: ReviewerMethodologySurface): {
|
|
38
|
+
changeSet: string;
|
|
39
|
+
audit: string;
|
|
40
|
+
};
|
|
41
|
+
export declare const reviewerMethodologyCore: string;
|
|
42
|
+
export declare const prompts: {
|
|
43
|
+
detectBuildAndTest: string;
|
|
44
|
+
worker: string;
|
|
45
|
+
reviewer: string;
|
|
46
|
+
prep: string;
|
|
47
|
+
previousIterationBriefing: string;
|
|
48
|
+
};
|