flanders 0.1.0 → 0.2.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 (60) 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 +649 -49
  16. package/lib/commands/InstallAvailabilityCheck.d.ts +8 -0
  17. package/lib/commands/InstallAvailabilityCheck.js +45 -0
  18. package/lib/commands/InstallModelProbe.d.ts +2 -0
  19. package/lib/commands/InstallModelProbe.js +74 -0
  20. package/lib/contexts.d.ts +5 -4
  21. package/lib/index.d.ts +5 -3
  22. package/lib/{PlanFile.d.ts → plan/PlanFile.d.ts} +8 -1
  23. package/lib/{PlanFile.js → plan/PlanFile.js} +44 -5
  24. package/lib/prompts/prompts.d.ts +48 -0
  25. package/lib/prompts/prompts.js +328 -0
  26. package/lib/prompts/skills.d.ts +3 -0
  27. package/lib/prompts/skills.js +463 -0
  28. package/lib/{Git.d.ts → system/Git.d.ts} +4 -2
  29. package/lib/{Git.js → system/Git.js} +63 -3
  30. package/lib/{ScriptRunner.d.ts → system/ScriptRunner.d.ts} +1 -1
  31. package/lib/system/ShellScriptContext.d.ts +36 -0
  32. package/lib/system/ShellScriptContext.js +70 -0
  33. package/lib/{fsUtils.d.ts → system/fsUtils.d.ts} +1 -1
  34. package/lib/{wait.d.ts → system/wait.d.ts} +1 -1
  35. package/lib/ui/BottomBlock.d.ts +9 -1
  36. package/lib/ui/BottomBlock.js +30 -14
  37. package/lib/ui/PromptHelper.d.ts +12 -0
  38. package/lib/ui/PromptHelper.js +32 -0
  39. package/lib/ui/TerminalSizeSource.d.ts +19 -0
  40. package/lib/ui/TerminalSizeSource.js +77 -0
  41. package/lib/ui/formatters.d.ts +14 -0
  42. package/lib/ui/formatters.js +65 -2
  43. package/lib/workspace/FlandersConfig.d.ts +23 -0
  44. package/lib/workspace/FlandersConfig.js +90 -0
  45. package/lib/workspace/SpecDiscovery.d.ts +12 -0
  46. package/lib/workspace/SpecDiscovery.js +40 -0
  47. package/lib/{Workspace.d.ts → workspace/Workspace.d.ts} +10 -2
  48. package/lib/{Workspace.js → workspace/Workspace.js} +52 -6
  49. package/package.json +3 -2
  50. package/lib/Claude.d.ts +0 -129
  51. package/lib/Claude.js +0 -387
  52. package/lib/ClaudeSession.d.ts +0 -34
  53. package/lib/ClaudeSession.js +0 -292
  54. package/lib/prompts.d.ts +0 -18
  55. package/lib/prompts.js +0 -221
  56. package/lib/skills.d.ts +0 -3
  57. package/lib/skills.js +0 -267
  58. /package/lib/{ScriptRunner.js → system/ScriptRunner.js} +0 -0
  59. /package/lib/{fsUtils.js → system/fsUtils.js} +0 -0
  60. /package/lib/{wait.js → system/wait.js} +0 -0
@@ -0,0 +1,8 @@
1
+ import type { ScriptContext } from "../contexts";
2
+ export type ToolAvailabilityEntry = Readonly<{
3
+ tool: "claude" | "codex";
4
+ available: boolean;
5
+ reason: string | null;
6
+ }>;
7
+ export type ToolAvailabilityReport = readonly ToolAvailabilityEntry[];
8
+ export declare function verifyToolAvailability(tools: Set<"claude" | "codex">, script: ScriptContext): Promise<ToolAvailabilityReport>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyToolAvailability = verifyToolAvailability;
4
+ function probeTool(tool, script) {
5
+ const binary = tool === "claude" ? "claude" : "codex";
6
+ return new Promise(resolve => {
7
+ let proc;
8
+ try {
9
+ proc = script.spawn(binary, ["--version"], { stdio: "pipe" });
10
+ }
11
+ catch (e) {
12
+ resolve({ tool, available: false, reason: `${binary}: spawn failed (${e instanceof Error ? e.message : String(e)})` });
13
+ return;
14
+ }
15
+ let settled = false;
16
+ const settle = (entry) => {
17
+ if (settled)
18
+ return;
19
+ settled = true;
20
+ resolve(entry);
21
+ };
22
+ proc.on("error", e => {
23
+ const msg = e instanceof Error ? e.message : String(e);
24
+ settle({ tool, available: false, reason: `${binary}: spawn failed (${msg})` });
25
+ });
26
+ proc.on("exit", (code, signal) => {
27
+ if (code === 0) {
28
+ settle({ tool, available: true, reason: null });
29
+ }
30
+ else if (signal) {
31
+ settle({ tool, available: false, reason: `${binary}: terminated by signal ${signal}` });
32
+ }
33
+ else {
34
+ settle({ tool, available: false, reason: `${binary}: exited with code ${code}` });
35
+ }
36
+ });
37
+ });
38
+ }
39
+ async function verifyToolAvailability(tools, script) {
40
+ const probes = [];
41
+ for (const tool of tools) {
42
+ probes.push(probeTool(tool, script));
43
+ }
44
+ return Promise.all(probes);
45
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScriptContext } from "../contexts";
2
+ export declare function probeModelList(script: ScriptContext): Promise<readonly string[] | null>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.probeModelList = probeModelList;
4
+ function probeModelList(script) {
5
+ return new Promise(resolve => {
6
+ let proc;
7
+ try {
8
+ proc = script.spawn("codex", ["debug", "models"], { stdio: "pipe" });
9
+ }
10
+ catch {
11
+ resolve(null);
12
+ return;
13
+ }
14
+ const chunks = [];
15
+ let settled = false;
16
+ const settle = (result) => {
17
+ if (settled)
18
+ return;
19
+ settled = true;
20
+ resolve(result);
21
+ };
22
+ if (proc.stdout) {
23
+ proc.stdout.on("data", (chunk) => {
24
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
25
+ });
26
+ }
27
+ proc.on("error", () => {
28
+ settle(null);
29
+ });
30
+ proc.on("exit", (code) => {
31
+ if (code !== 0) {
32
+ settle(null);
33
+ return;
34
+ }
35
+ let parsed;
36
+ try {
37
+ parsed = JSON.parse(chunks.join(""));
38
+ }
39
+ catch {
40
+ settle(null);
41
+ return;
42
+ }
43
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
44
+ settle(null);
45
+ return;
46
+ }
47
+ const models = parsed.models;
48
+ if (!Array.isArray(models)) {
49
+ settle(null);
50
+ return;
51
+ }
52
+ const slugs = [];
53
+ for (const entry of models) {
54
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
55
+ settle(null);
56
+ return;
57
+ }
58
+ const record = entry;
59
+ if (typeof record.slug !== "string" || typeof record.visibility !== "string") {
60
+ settle(null);
61
+ return;
62
+ }
63
+ if (record.visibility === "list") {
64
+ slugs.push(record.slug);
65
+ }
66
+ }
67
+ if (slugs.length === 0) {
68
+ settle(null);
69
+ return;
70
+ }
71
+ settle(slugs);
72
+ });
73
+ });
74
+ }
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
+ };