flanders 0.8.0 → 0.10.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.
@@ -3,6 +3,7 @@ import type { FlandersConfig } from "../workspace/FlandersConfig";
3
3
  import type { Activity } from "../ui/BottomBlock";
4
4
  import { PlatformContext } from "../workspace/Workspace";
5
5
  export type { Activity };
6
+ export declare function completedPlanPath(planPath: string): string;
6
7
  export type ImplementContexts = Readonly<{
7
8
  claude: ScriptContext;
8
9
  script: ScriptContext;
@@ -27,6 +28,7 @@ export declare class Implement {
27
28
  private _block;
28
29
  private _buffered;
29
30
  private _currentWorkerSessionId;
31
+ private _workerSessionTokens;
30
32
  private _activeSession;
31
33
  private _activeScript;
32
34
  private _activeReviewerSessions;
@@ -41,6 +43,7 @@ export declare class Implement {
41
43
  private _taskRateLimitMs;
42
44
  private _taskRateLimitStartedAt;
43
45
  private _taskTokens;
46
+ private _otherTasksSeconds;
44
47
  private _runPromise;
45
48
  get config(): FlandersConfig | null;
46
49
  constructor(rawArgs: readonly string[], _options: ImplementOptions, _contexts: ImplementContexts);
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Implement = void 0;
4
+ exports.completedPlanPath = completedPlanPath;
4
5
  const AiSession_1 = require("../ai/AiSession");
5
6
  const ClaudeAdapter_1 = require("../ai/ClaudeAdapter");
6
7
  const CodexAdapter_1 = require("../ai/CodexAdapter");
@@ -16,6 +17,16 @@ const formatters_1 = require("../ui/formatters");
16
17
  const voiceVariants_1 = require("../voiceVariants");
17
18
  const Workspace_1 = require("../workspace/Workspace");
18
19
  const MAX_ITER = 5;
20
+ const COMPLETED_PLAN_MARKER = "V-";
21
+ function completedPlanPath(planPath) {
22
+ const slash = Math.max(planPath.lastIndexOf("/"), planPath.lastIndexOf("\\"));
23
+ const dir = planPath.slice(0, slash + 1);
24
+ const base = planPath.slice(slash + 1);
25
+ if (base.startsWith(COMPLETED_PLAN_MARKER)) {
26
+ return planPath;
27
+ }
28
+ return `${dir}${COMPLETED_PLAN_MARKER}${base}`;
29
+ }
19
30
  class LineBufferedBlock {
20
31
  constructor(_block) {
21
32
  this._block = _block;
@@ -62,6 +73,7 @@ class Implement {
62
73
  this._workspace = null;
63
74
  this._block = null;
64
75
  this._currentWorkerSessionId = null;
76
+ this._workerSessionTokens = { inputTokens: 0, outputTokens: 0 };
65
77
  this._activeSession = null;
66
78
  this._activeScript = null;
67
79
  this._activeReviewerSessions = new Set();
@@ -76,6 +88,7 @@ class Implement {
76
88
  this._taskRateLimitMs = 0;
77
89
  this._taskRateLimitStartedAt = null;
78
90
  this._taskTokens = { it: 0, ot: 0 };
91
+ this._otherTasksSeconds = 0;
79
92
  this._runPromise = this._run(rawArgs);
80
93
  this._runPromise.catch(() => { });
81
94
  }
@@ -141,13 +154,15 @@ class Implement {
141
154
  const planTotals = plan.planTotals();
142
155
  if (initialParse.tasks.every(t => t.done)) {
143
156
  this._block.setHeader({ indexLabel: `${totalTasks}/${totalTasks}` });
144
- this._block.setMetrics({ plan: { tokens: planTotals.it + planTotals.ot, seconds: planTotals.t } });
157
+ this._block.setMetrics({ plan: { tokens: planTotals.it + planTotals.ot, baseSeconds: planTotals.t } });
145
158
  this._buffered.write(`${(0, voiceVariants_1.pickVariant)(voiceVariants_1.tasksCompletedPool, this._contexts.random)}\n`);
146
159
  this._finalizeBlock("Done");
147
160
  return 0;
148
161
  }
149
- this._block.setHeader({ indexLabel: `0/${totalTasks}` });
150
- this._block.setMetrics({ plan: { tokens: planTotals.it + planTotals.ot, seconds: planTotals.t } });
162
+ const completedAtStartup = initialParse.tasks.filter(t => t.done).length;
163
+ const startupIndexLabel = `${completedAtStartup}/${totalTasks}`;
164
+ this._block.setHeader({ indexLabel: startupIndexLabel });
165
+ this._block.setMetrics({ plan: { tokens: planTotals.it + planTotals.ot, baseSeconds: planTotals.t } });
151
166
  const gitAvailable = await (0, Git_1.isGitAvailable)(this._contexts.script, this._contexts.time);
152
167
  const insideWorkTree = gitAvailable && await (0, Git_1.isInsideWorkTree)(this._contexts.script, this._contexts.time, this._options.projectRoot);
153
168
  if (!insideWorkTree) {
@@ -167,6 +182,7 @@ class Implement {
167
182
  this._behaviorRuleList = specs.flanders;
168
183
  this._workspace = new Workspace_1.Workspace(this._contexts.fs, this._contexts.platform);
169
184
  const wsPaths = await this._workspace.setup(this._config.reviewers.length);
185
+ this._block.setHeader({ indexLabel: startupIndexLabel, phaseMessage: "preparing build and test scripts" });
170
186
  await this._detectBuildAndTest(wsPaths);
171
187
  if (this._disposed) {
172
188
  this._finalizeBlock("Interrupted");
@@ -241,7 +257,7 @@ class Implement {
241
257
  .split("<BUILD_SCRIPT_PATH>").join(ws.buildScript)
242
258
  .split("<TEST_SCRIPT_PATH>").join(ws.testScript)
243
259
  .split("<RULE_LIST>").join(this._formatPathList(this._ruleList));
244
- await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, prompt);
260
+ await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, this._config.worker.fast, prompt);
245
261
  }
246
262
  _setActivity(activity) {
247
263
  if (!this._currentTask)
@@ -269,9 +285,10 @@ class Implement {
269
285
  if (!this._currentTask || this._taskRateLimitStartedAt !== null)
270
286
  return;
271
287
  const planTotals = plan.planTotals();
288
+ const anchorMs = this._taskStartedAt + this._taskRateLimitMs;
272
289
  this._block.setMetrics({
273
- task: { tokens: this._taskTokens.it + this._taskTokens.ot, seconds: this._activeSeconds() },
274
- plan: { tokens: planTotals.it + planTotals.ot, seconds: planTotals.t }
290
+ task: { tokens: this._taskTokens.it + this._taskTokens.ot, anchorMs, baseSeconds: 0 },
291
+ plan: { tokens: planTotals.it + planTotals.ot, anchorMs, baseSeconds: this._otherTasksSeconds }
275
292
  });
276
293
  }
277
294
  _gitOutputContext() {
@@ -287,10 +304,12 @@ class Implement {
287
304
  this._currentTask = task;
288
305
  this._currentIndexLabel = indexLabel;
289
306
  this._currentWorkerSessionId = null;
307
+ this._workerSessionTokens = { inputTokens: 0, outputTokens: 0 };
290
308
  this._taskStartedAt = this._contexts.time.now();
291
309
  this._taskRateLimitMs = 0;
292
310
  this._taskRateLimitStartedAt = null;
293
311
  this._taskTokens = { it: 0, ot: 0 };
312
+ this._otherTasksSeconds = plan.planTotals().t - task.metrics.t;
294
313
  this._updateMetrics(plan);
295
314
  let iteration = 0;
296
315
  for (;;) {
@@ -343,17 +362,32 @@ class Implement {
343
362
  continue;
344
363
  }
345
364
  await plan.markDone(task.line, { it: this._taskTokens.it, ot: this._taskTokens.ot, t: this._activeSeconds() });
365
+ const originalPlanPath = plan.path;
366
+ let completionRenamed = false;
367
+ if (plan.nextOpenTask() === null) {
368
+ const completedPath = completedPlanPath(originalPlanPath);
369
+ if (completedPath !== originalPlanPath) {
370
+ await plan.rename(completedPath);
371
+ completionRenamed = true;
372
+ }
373
+ }
374
+ const revertCompletion = async () => {
375
+ if (completionRenamed) {
376
+ await plan.rename(originalPlanPath);
377
+ }
378
+ await plan.markOpen(task.line, { it: this._taskTokens.it, ot: this._taskTokens.ot, t: this._activeSeconds() });
379
+ };
346
380
  const commitMessage = task.taskNumber ? `${task.taskNumber} ${task.title}` : task.title;
347
381
  const addResult = await (0, Git_1.addAll)(this._contexts.script, this._contexts.time, this._gitOutputContext(), this._options.projectRoot);
348
382
  if (addResult.code !== 0) {
349
383
  await this._writeErrorLog(ws, `git add -A failed (exit ${addResult.code})\n--- stdout ---\n${addResult.stdout}\n--- stderr ---\n${addResult.stderr}`);
350
- await plan.markOpen(task.line, { it: this._taskTokens.it, ot: this._taskTokens.ot, t: this._activeSeconds() });
384
+ await revertCompletion();
351
385
  continue;
352
386
  }
353
387
  const commitResult = await (0, Git_1.commit)(this._contexts.script, this._contexts.time, this._gitOutputContext(), this._options.projectRoot, commitMessage);
354
388
  if (commitResult.code !== 0) {
355
389
  await this._writeErrorLog(ws, `git commit failed (exit ${commitResult.code})\n--- stdout ---\n${commitResult.stdout}\n--- stderr ---\n${commitResult.stderr}`);
356
- await plan.markOpen(task.line, { it: this._taskTokens.it, ot: this._taskTokens.ot, t: this._activeSeconds() });
390
+ await revertCompletion();
357
391
  continue;
358
392
  }
359
393
  const planTotals = plan.planTotals();
@@ -394,10 +428,17 @@ class Implement {
394
428
  prompt = `${prompt}\n\n${(0, prompts_1.linkedReferenceDirective)(ws.specFile)}`;
395
429
  }
396
430
  const { result, capturedOutput } = iteration === 1
397
- ? await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, prompt)
398
- : await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, prompt, this._currentWorkerSessionId);
399
- if (result.sessionId !== null) {
431
+ ? await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, this._config.worker.fast, prompt, null, this._workerSessionTokens)
432
+ : await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, this._config.worker.fast, prompt, this._currentWorkerSessionId, this._workerSessionTokens);
433
+ if (result.sessionId !== null && result.sessionId !== this._currentWorkerSessionId) {
400
434
  this._currentWorkerSessionId = result.sessionId;
435
+ this._workerSessionTokens = { inputTokens: result.inputTokens, outputTokens: result.outputTokens };
436
+ }
437
+ else {
438
+ this._workerSessionTokens = {
439
+ inputTokens: this._workerSessionTokens.inputTokens + result.inputTokens,
440
+ outputTokens: this._workerSessionTokens.outputTokens + result.outputTokens
441
+ };
401
442
  }
402
443
  this._taskTokens.it += result.inputTokens;
403
444
  this._taskTokens.ot += result.outputTokens;
@@ -604,7 +645,7 @@ class Implement {
604
645
  };
605
646
  let runResult;
606
647
  try {
607
- runResult = await this._runAiWith(reviewer.tool, reviewer.model, reviewer.effort, prompt, null, callbacks);
648
+ runResult = await this._runAiWith(reviewer.tool, reviewer.model, reviewer.effort, reviewer.fast, prompt, null, callbacks);
608
649
  }
609
650
  catch (e) {
610
651
  if (this._cancelledReviewers.has(idx)) {
@@ -652,7 +693,8 @@ class Implement {
652
693
  }
653
694
  return new ClaudeAdapter_1.ClaudeAdapter({
654
695
  claude: this._contexts.claude,
655
- time: this._contexts.time
696
+ time: this._contexts.time,
697
+ random: this._contexts.random
656
698
  });
657
699
  }
658
700
  _defaultRunAiCallbacks() {
@@ -685,10 +727,10 @@ class Implement {
685
727
  }
686
728
  };
687
729
  }
688
- _runAi(tool, model, effort, prompt, initialSessionId) {
689
- return this._runAiWith(tool, model, effort, prompt, initialSessionId !== null && initialSessionId !== void 0 ? initialSessionId : null, this._defaultRunAiCallbacks());
730
+ _runAi(tool, model, effort, fast, prompt, initialSessionId, priorSessionUsage) {
731
+ return this._runAiWith(tool, model, effort, fast, prompt, initialSessionId !== null && initialSessionId !== void 0 ? initialSessionId : null, this._defaultRunAiCallbacks(), priorSessionUsage);
690
732
  }
691
- async _runAiWith(tool, model, effort, prompt, initialSessionId, callbacks) {
733
+ async _runAiWith(tool, model, effort, fast, prompt, initialSessionId, callbacks, priorSessionUsage) {
692
734
  if (this._disposed) {
693
735
  throw new Error("Implement disposed");
694
736
  }
@@ -712,7 +754,9 @@ class Implement {
712
754
  prompt,
713
755
  model,
714
756
  effort,
757
+ fast,
715
758
  ...(initialSessionId != null ? { resumeSessionId: initialSessionId } : null),
759
+ ...(priorSessionUsage != null ? { priorSessionUsage } : null),
716
760
  onLongWaitStart: callbacks.onLongWaitStart,
717
761
  onLongWaitEnd: callbacks.onLongWaitEnd
718
762
  }, {
@@ -1,5 +1,6 @@
1
1
  import type { AskContext, FsContext, OutputContext, ScriptContext } from "../contexts";
2
2
  import type { PlatformContext } from "../workspace/Workspace";
3
+ import type { ToolName } from "../ai/ToolAdapter";
3
4
  export type InstallContexts = Readonly<{
4
5
  fs: FsContext;
5
6
  ask: AskContext;
@@ -11,18 +12,20 @@ export type InstallOptions = Readonly<{
11
12
  projectRoot: string;
12
13
  }>;
13
14
  type ReviewerFlagAnswers = Readonly<{
14
- tool?: "claude" | "codex";
15
+ tool?: ToolName;
15
16
  model?: string;
16
17
  effort?: string;
17
18
  }>;
18
19
  export type ResolvedAnswers = Readonly<{
19
20
  scope?: "project" | "global";
20
- skillsTool?: "claude" | "codex" | "both";
21
- workerTool?: "claude" | "codex";
21
+ skillsTools?: readonly ToolName[];
22
+ workerTool?: ToolName;
22
23
  workerModel?: string;
23
24
  workerEffort?: string;
25
+ workerFast?: boolean;
24
26
  reviewers?: readonly ReviewerFlagAnswers[];
25
27
  optionalReviewerIndices?: readonly number[];
28
+ fastReviewerIndices?: readonly number[];
26
29
  reviewerMinimum?: number;
27
30
  }>;
28
31
  export declare function parseInstallFlags(rawArgs: readonly string[]): Readonly<{
@@ -39,9 +42,11 @@ export declare class Install {
39
42
  constructor(rawArgs: readonly string[], _options: InstallOptions, _contexts: InstallContexts);
40
43
  result(): Promise<number>;
41
44
  private _resolveCuratedChoice;
42
- private _resolveClaudeModel;
45
+ private _resolveGroupedModel;
46
+ private _resolveFreeTextModel;
43
47
  private _resolveRoleModel;
44
48
  private _resolveRoleEffort;
49
+ private _resolveRoleFast;
45
50
  private _resolveReviewer;
46
51
  private _run;
47
52
  dispose(): Promise<void>;