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.
Files changed (58) hide show
  1. package/lib/ai/AiRunner.d.ts +24 -0
  2. package/lib/ai/AiRunner.js +100 -0
  3. package/lib/ai/AiSession.d.ts +32 -0
  4. package/lib/ai/AiSession.js +143 -0
  5. package/lib/ai/ClaudeAdapter.d.ts +12 -0
  6. package/lib/ai/ClaudeAdapter.js +345 -0
  7. package/lib/ai/CodexAdapter.d.ts +13 -0
  8. package/lib/ai/CodexAdapter.js +354 -0
  9. package/lib/ai/ToolAdapter.d.ts +52 -0
  10. package/lib/ai/ToolAdapter.js +2 -0
  11. package/lib/cli.js +56 -35
  12. package/lib/commands/Implement.d.ts +22 -8
  13. package/lib/commands/Implement.js +296 -171
  14. package/lib/commands/Install.d.ts +31 -3
  15. package/lib/commands/Install.js +625 -49
  16. package/lib/commands/InstallModelProbe.d.ts +11 -0
  17. package/lib/commands/InstallModelProbe.js +99 -0
  18. package/lib/contexts.d.ts +5 -4
  19. package/lib/index.d.ts +5 -3
  20. package/lib/{PlanFile.d.ts → plan/PlanFile.d.ts} +8 -1
  21. package/lib/{PlanFile.js → plan/PlanFile.js} +44 -5
  22. package/lib/prompts/prompts.d.ts +48 -0
  23. package/lib/prompts/prompts.js +328 -0
  24. package/lib/prompts/skills.d.ts +3 -0
  25. package/lib/prompts/skills.js +464 -0
  26. package/lib/{Git.d.ts → system/Git.d.ts} +4 -2
  27. package/lib/{Git.js → system/Git.js} +63 -3
  28. package/lib/{ScriptRunner.d.ts → system/ScriptRunner.d.ts} +1 -1
  29. package/lib/system/ShellScriptContext.d.ts +36 -0
  30. package/lib/system/ShellScriptContext.js +70 -0
  31. package/lib/{fsUtils.d.ts → system/fsUtils.d.ts} +1 -1
  32. package/lib/{wait.d.ts → system/wait.d.ts} +1 -1
  33. package/lib/ui/BottomBlock.d.ts +9 -1
  34. package/lib/ui/BottomBlock.js +30 -14
  35. package/lib/ui/PromptHelper.d.ts +12 -0
  36. package/lib/ui/PromptHelper.js +32 -0
  37. package/lib/ui/TerminalSizeSource.d.ts +19 -0
  38. package/lib/ui/TerminalSizeSource.js +77 -0
  39. package/lib/ui/formatters.d.ts +14 -0
  40. package/lib/ui/formatters.js +65 -2
  41. package/lib/workspace/FlandersConfig.d.ts +23 -0
  42. package/lib/workspace/FlandersConfig.js +90 -0
  43. package/lib/workspace/SpecDiscovery.d.ts +12 -0
  44. package/lib/workspace/SpecDiscovery.js +40 -0
  45. package/lib/{Workspace.d.ts → workspace/Workspace.d.ts} +10 -2
  46. package/lib/{Workspace.js → workspace/Workspace.js} +52 -6
  47. package/package.json +3 -2
  48. package/lib/Claude.d.ts +0 -129
  49. package/lib/Claude.js +0 -387
  50. package/lib/ClaudeSession.d.ts +0 -34
  51. package/lib/ClaudeSession.js +0 -292
  52. package/lib/prompts.d.ts +0 -18
  53. package/lib/prompts.js +0 -221
  54. package/lib/skills.d.ts +0 -3
  55. package/lib/skills.js +0 -267
  56. /package/lib/{ScriptRunner.js → system/ScriptRunner.js} +0 -0
  57. /package/lib/{fsUtils.js → system/fsUtils.js} +0 -0
  58. /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 { AskAnswer, AskChoiceOptions, AskContext, ChoiceOption, ClaudeContext, ScriptContext, FsContext, FsDirEntry, OutputContext, TimeContext, TimeoutHandle, SpawnedProcess, SpawnedReadable } from "./contexts";
4
- export type { ClaudeEvent, ClaudeContentBlock, ClaudeControlRequestBody, ClaudeDelta, ClaudeResult, ClaudeRunOptions, PermissionRequest, PermissionResponse } from "./Claude";
5
- export type { PlatformContext } from "./Workspace";
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 "./contexts";
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
- const TASK_LINE = /^(\s*[-*+]\s+)\[([ xX])\](\{[^}]*\})(\s.*)?$/;
6
- const MALFORMED_TASK_LINE = /^\s*[-*+]\s+\[([^\]]*)\]/;
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
+ };