projectinator 0.3.1 → 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.
Files changed (171) hide show
  1. package/README.md +7 -6
  2. package/bin/projectinator.mjs +22 -15
  3. package/dist/bakeoff.js +163 -0
  4. package/dist/bakeoff.js.map +1 -0
  5. package/dist/build-state.js +26 -0
  6. package/dist/build-state.js.map +1 -0
  7. package/dist/burndown.js +19 -0
  8. package/dist/burndown.js.map +1 -0
  9. package/dist/calibration.js +86 -0
  10. package/dist/calibration.js.map +1 -0
  11. package/dist/cli.js +289 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/cost.js +35 -0
  14. package/dist/cost.js.map +1 -0
  15. package/dist/council.js +147 -0
  16. package/dist/council.js.map +1 -0
  17. package/dist/demo.js +93 -0
  18. package/dist/demo.js.map +1 -0
  19. package/dist/estimate.js +98 -0
  20. package/dist/estimate.js.map +1 -0
  21. package/dist/executor.js +118 -0
  22. package/dist/executor.js.map +1 -0
  23. package/dist/git.js +82 -0
  24. package/dist/git.js.map +1 -0
  25. package/dist/intake.js +98 -0
  26. package/dist/intake.js.map +1 -0
  27. package/dist/models.js +171 -0
  28. package/dist/models.js.map +1 -0
  29. package/dist/narrate.js +76 -0
  30. package/dist/narrate.js.map +1 -0
  31. package/dist/openrouter.js +112 -0
  32. package/dist/openrouter.js.map +1 -0
  33. package/dist/orchestrator.js +273 -0
  34. package/dist/orchestrator.js.map +1 -0
  35. package/dist/pm.js +245 -0
  36. package/dist/pm.js.map +1 -0
  37. package/dist/preview.js +194 -0
  38. package/dist/preview.js.map +1 -0
  39. package/dist/registry-store.js +38 -0
  40. package/dist/registry-store.js.map +1 -0
  41. package/dist/registry.js +115 -0
  42. package/dist/registry.js.map +1 -0
  43. package/dist/research.js +98 -0
  44. package/dist/research.js.map +1 -0
  45. package/dist/retro.js +83 -0
  46. package/dist/retro.js.map +1 -0
  47. package/dist/roles.js +357 -0
  48. package/dist/roles.js.map +1 -0
  49. package/dist/router.js +95 -0
  50. package/dist/router.js.map +1 -0
  51. package/dist/run-bakeoff.js +68 -0
  52. package/dist/run-bakeoff.js.map +1 -0
  53. package/dist/run-build.js +193 -0
  54. package/dist/run-build.js.map +1 -0
  55. package/{src/run-dev.ts → dist/run-dev.js} +23 -39
  56. package/dist/run-dev.js.map +1 -0
  57. package/{src/run-pm.ts → dist/run-pm.js} +34 -46
  58. package/dist/run-pm.js.map +1 -0
  59. package/{src/run-research.ts → dist/run-research.js} +19 -30
  60. package/dist/run-research.js.map +1 -0
  61. package/{src/run-scout.ts → dist/run-scout.js} +17 -27
  62. package/dist/run-scout.js.map +1 -0
  63. package/dist/run-web.js +93 -0
  64. package/dist/run-web.js.map +1 -0
  65. package/dist/scout.js +81 -0
  66. package/dist/scout.js.map +1 -0
  67. package/dist/session-cost.js +15 -0
  68. package/dist/session-cost.js.map +1 -0
  69. package/dist/stack.js +34 -0
  70. package/dist/stack.js.map +1 -0
  71. package/dist/stuck.js +21 -0
  72. package/dist/stuck.js.map +1 -0
  73. package/dist/tui/App.js +1269 -0
  74. package/dist/tui/App.js.map +1 -0
  75. package/dist/tui/BakeOff.js +95 -0
  76. package/dist/tui/BakeOff.js.map +1 -0
  77. package/dist/tui/BoardEditor.js +183 -0
  78. package/dist/tui/BoardEditor.js.map +1 -0
  79. package/dist/tui/EditableBoard.js +140 -0
  80. package/dist/tui/EditableBoard.js.map +1 -0
  81. package/dist/tui/Frame.js +78 -0
  82. package/dist/tui/Frame.js.map +1 -0
  83. package/dist/tui/Intake.js +59 -0
  84. package/dist/tui/Intake.js.map +1 -0
  85. package/dist/tui/Kanban.js +75 -0
  86. package/dist/tui/Kanban.js.map +1 -0
  87. package/dist/tui/Settings.js +299 -0
  88. package/dist/tui/Settings.js.map +1 -0
  89. package/dist/tui/StackPick.js +44 -0
  90. package/dist/tui/StackPick.js.map +1 -0
  91. package/dist/tui/WebAccounts.js +131 -0
  92. package/dist/tui/WebAccounts.js.map +1 -0
  93. package/dist/tui/components.js +178 -0
  94. package/dist/tui/components.js.map +1 -0
  95. package/dist/tui/config.js +129 -0
  96. package/dist/tui/config.js.map +1 -0
  97. package/dist/tui/deploy.js +114 -0
  98. package/dist/tui/deploy.js.map +1 -0
  99. package/dist/tui/engine.js +710 -0
  100. package/dist/tui/engine.js.map +1 -0
  101. package/dist/tui/notify.js +47 -0
  102. package/dist/tui/notify.js.map +1 -0
  103. package/dist/tui/panels.js +42 -0
  104. package/dist/tui/panels.js.map +1 -0
  105. package/dist/tui/templates.js +106 -0
  106. package/dist/tui/templates.js.map +1 -0
  107. package/dist/tui/theme.js +42 -0
  108. package/dist/tui/theme.js.map +1 -0
  109. package/dist/tui/validate.js +51 -0
  110. package/dist/tui/validate.js.map +1 -0
  111. package/{src/tui.tsx → dist/tui.js} +23 -33
  112. package/dist/tui.js.map +1 -0
  113. package/dist/types.js +19 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/web/oauth-anthropic.js +179 -0
  116. package/dist/web/oauth-anthropic.js.map +1 -0
  117. package/dist/web/session.js +274 -0
  118. package/dist/web/session.js.map +1 -0
  119. package/package.json +5 -4
  120. package/src/bakeoff.ts +0 -214
  121. package/src/build-state.ts +0 -47
  122. package/src/burndown.ts +0 -35
  123. package/src/calibration.ts +0 -88
  124. package/src/cli.ts +0 -263
  125. package/src/cost.ts +0 -43
  126. package/src/council.ts +0 -175
  127. package/src/demo.ts +0 -106
  128. package/src/estimate.ts +0 -111
  129. package/src/executor.ts +0 -169
  130. package/src/git.ts +0 -72
  131. package/src/intake.ts +0 -125
  132. package/src/models.ts +0 -175
  133. package/src/narrate.ts +0 -87
  134. package/src/openrouter.ts +0 -119
  135. package/src/orchestrator.ts +0 -310
  136. package/src/pm.ts +0 -302
  137. package/src/preview.ts +0 -206
  138. package/src/registry-store.ts +0 -41
  139. package/src/registry.ts +0 -132
  140. package/src/research.ts +0 -123
  141. package/src/retro.ts +0 -99
  142. package/src/roles.ts +0 -401
  143. package/src/router.ts +0 -120
  144. package/src/run-bakeoff.ts +0 -77
  145. package/src/run-build.ts +0 -203
  146. package/src/run-web.ts +0 -87
  147. package/src/scout.ts +0 -121
  148. package/src/session-cost.ts +0 -17
  149. package/src/stack.ts +0 -46
  150. package/src/tui/App.tsx +0 -1760
  151. package/src/tui/BakeOff.tsx +0 -190
  152. package/src/tui/BoardEditor.tsx +0 -260
  153. package/src/tui/EditableBoard.tsx +0 -179
  154. package/src/tui/Frame.tsx +0 -142
  155. package/src/tui/Intake.tsx +0 -111
  156. package/src/tui/Kanban.tsx +0 -158
  157. package/src/tui/Settings.tsx +0 -513
  158. package/src/tui/StackPick.tsx +0 -79
  159. package/src/tui/WebAccounts.tsx +0 -197
  160. package/src/tui/components.tsx +0 -340
  161. package/src/tui/config.ts +0 -150
  162. package/src/tui/deploy.ts +0 -137
  163. package/src/tui/engine.ts +0 -749
  164. package/src/tui/notify.ts +0 -21
  165. package/src/tui/panels.tsx +0 -89
  166. package/src/tui/templates.ts +0 -119
  167. package/src/tui/theme.ts +0 -44
  168. package/src/tui/validate.ts +0 -53
  169. package/src/types.ts +0 -208
  170. package/src/web/oauth-anthropic.ts +0 -206
  171. package/src/web/session.ts +0 -299
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm](https://img.shields.io/npm/v/projectinator.svg?color=e0a72d&label=npm)](https://www.npmjs.com/package/projectinator)
8
8
  ![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
9
9
  ![node](https://img.shields.io/badge/node-%E2%89%A522.19-brightgreen.svg)
10
- ![tests: 187 passing](https://img.shields.io/badge/tests-187%20passing-brightgreen.svg)
10
+ ![tests: 212 passing](https://img.shields.io/badge/tests-212%20passing-brightgreen.svg)
11
11
  ![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)
12
12
  ![built on Pi](https://img.shields.io/badge/built%20on-Pi%20agent%20harness-e0a72d.svg)
13
13
 
@@ -69,10 +69,10 @@ Type an idea → it plans → you approve → it builds, tests, and hands you wo
69
69
  place, every route updates. Run a **bake-off** to pick empirically.
70
70
  - **A real pipeline.** PM decomposes → Designer specs → Developer writes files → **Reviewer**
71
71
  (cheap, read-only) checks the wiring → Tester **runs the app headless and catches real
72
- bugs** → feedback loop re-runs the dev on failure.
72
+ bugs** → feedback loop re-runs the dev **one model tier up** on failure.
73
73
  - **Multi-file apps.** Vanilla HTML/CSS/JS or **React (CDN, no build)** — your choice.
74
74
  - **The cockpit.** A polished terminal UI: editable board, Kanban, standup, per-task cost,
75
- live budget bar, desktop notification when done.
75
+ live budget bar, desktop notification and optional webhook when done.
76
76
  - **Honest cost.** Live spend tracking, per-project budget cap + an alert before the cap,
77
77
  **per-task timeout and cost ceiling**, and predicted-vs-actual reporting that sharpens
78
78
  itself over real runs.
@@ -89,11 +89,12 @@ Type an idea → it plans → you approve → it builds, tests, and hands you wo
89
89
  | 👁 **Live preview** | Local server + auto-reload; ES modules and fetch resolve like production. |
90
90
  | 🚀 **Deploy** | One click to Cloudflare Pages, Vercel, or Netlify (their CLI + your login). |
91
91
  | 📤 **Export** | Backlog → Markdown, CSV, **Jira** CSV, **Trello** CSV. |
92
- | 📜 **Git per build** | The workspace is a git repo; one commit per task. History view + **undo a task**. |
92
+ | 📜 **Git per build** | The workspace is a git repo; one commit per task. History view with **per-commit diffs** + **undo a task**. **Transcripts**: read what every role said on every run. |
93
93
  | 📊 **Analytics** | Retro (with optional AI narrative), burndown, cost by epic/model, and estimate accuracy that self-calibrates from real runs. |
94
94
  | 💾 **Templates** | Save a project's brief as a reusable template; import/share as a file. |
95
95
  | ⛔ **Task limits** | Per-task timeout and cost ceiling (Settings → Preferences). A runaway task is aborted, billed for what it spent, and the build halts — resumable. |
96
96
  | ✎ **Task notes** | Annotate any task on the board (`n`). Yours only — never sent to a model; included in exports. |
97
+ | 📥 **Import a folder** | Bring an existing site or prototype in as a project; the PM plans changes against its real files. |
97
98
 
98
99
  ## How it works
99
100
 
@@ -103,7 +104,7 @@ idea
103
104
  └─ intake? PM asks clarifying questions if the request is vague
104
105
  └─ plan mode? Quick (one PM) or Deep (planning council → approve epics)
105
106
  └─ decompose → a routed, epic-tagged backlog with a cost estimate
106
- └─ build toposort deps · design → code → review → test · Reviewer/Tester→Dev feedback loop
107
+ └─ build toposort deps · design → code → review → test · Reviewer/Tester→Dev feedback loop (dev retries one tier up)
107
108
  └─ done working files + retro + deploy/export/preview
108
109
  ```
109
110
 
@@ -144,7 +145,7 @@ npm start # the cockpit
144
145
  npm run build -- --live --mini # fixed 4-task build, cheap end-to-end proof (~$0.10)
145
146
  npm run build -- --live --mini --resume # resume a halted run
146
147
  npm run bakeoff -- --capability design "Design a pricing page" # model bake-off
147
- npm test # 187 tests
148
+ npm test # 212 tests
148
149
  npm run typecheck
149
150
  ```
150
151
  </details>
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // Launcher for `projectinator` / `npx github:smanookian/projectinator`.
3
- // Runs the Ink TUI (TypeScript) through tsx no build step, no compiled dist.
3
+ // Runs the compiled app from dist/ (published), or the TypeScript sources via tsx (dev clone).
4
4
  import { spawnSync } from "node:child_process";
5
- import { readFileSync } from "node:fs";
6
- import { fileURLToPath } from "node:url";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  import { dirname, join } from "node:path";
8
8
 
9
9
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -44,16 +44,23 @@ if (args.length && !COMMANDS.has(args[0])) {
44
44
  process.exit(2);
45
45
  }
46
46
 
47
- // `node --import tsx <entry>` registers tsx's loader, then runs the TS entry.
48
- // No command the TUI; a command the headless CLI with the remaining args.
49
- const target = args.length ? join(root, "src", "cli.ts") : entry;
50
- const res = spawnSync(process.execPath, ["--import", "tsx", target, ...args], {
51
- stdio: "inherit",
52
- cwd: root,
53
- });
54
-
55
- if (res.error) {
56
- console.error("Failed to launch Projectinator:", res.error.message);
57
- process.exit(1);
47
+ // Published package: run the compiled JS in-process (no tsx, fast start).
48
+ // Dev clone without `npm run compile`: fall back to tsx on the TypeScript sources.
49
+ const dist = join(root, "dist");
50
+ if (existsSync(join(dist, "cli.js")) && existsSync(join(dist, "tui.js"))) {
51
+ process.chdir(root);
52
+ if (args.length) {
53
+ const { main } = await import(pathToFileURL(join(dist, "cli.js")).href);
54
+ process.exitCode = await main(args);
55
+ } else {
56
+ await import(pathToFileURL(join(dist, "tui.js")).href);
57
+ }
58
+ } else {
59
+ const target = args.length ? join(root, "src", "cli.ts") : entry;
60
+ const res = spawnSync(process.execPath, ["--import", "tsx", target, ...args], { stdio: "inherit", cwd: root });
61
+ if (res.error) {
62
+ console.error("Failed to launch Projectinator:", res.error.message, "\n(dev clone: run `npm install` and `npm run compile`, or keep tsx installed)");
63
+ process.exit(1);
64
+ }
65
+ process.exit(res.status ?? 0);
58
66
  }
59
- process.exit(res.status ?? 0);
@@ -0,0 +1,163 @@
1
+ // Model bake-off — the founding idea: run ONE task across several models, then
2
+ // compare cost, latency, and quality so you can pick the best model per role and
3
+ // feed that back into the routing registry.
4
+ //
5
+ // v1 covers TEXT roles (plan, design, test-reasoning) where the deliverable is
6
+ // text a judge can score. Code bake-off (per-candidate sandbox + real test
7
+ // scoring) is a later step.
8
+ import { createAgentSession, defineTool, } from "@earendil-works/pi-coding-agent";
9
+ import { Type } from "typebox";
10
+ import { piRuntime, resolvePiModel } from "./executor.js";
11
+ import { buildRolePrompt } from "./roles.js";
12
+ import { estimateTokens } from "./estimate.js";
13
+ import { addSessionCost } from "./session-cost.js";
14
+ function lastAssistantText(session) {
15
+ const msgs = session.messages;
16
+ for (let i = msgs.length - 1; i >= 0; i--) {
17
+ const m = msgs[i];
18
+ if (m?.role !== "assistant")
19
+ continue;
20
+ const c = m.content;
21
+ if (typeof c === "string")
22
+ return c;
23
+ if (Array.isArray(c)) {
24
+ return c
25
+ .map((p) => (typeof p === "string" ? p : p && typeof p === "object" && "text" in p ? String(p.text) : ""))
26
+ .join("")
27
+ .trim();
28
+ }
29
+ }
30
+ return "";
31
+ }
32
+ const id = (c) => `${c.provider}/${c.model}`;
33
+ /** Run one candidate on the task, capturing output, cost, and latency. */
34
+ async function runCandidate(task, cand) {
35
+ const base = { provider: cand.provider, model: cand.model, output: "", cost: 0, ms: 0, outputTokens: 0 };
36
+ try {
37
+ const runtime = await piRuntime();
38
+ const model = resolvePiModel(runtime, cand.provider, cand.model);
39
+ const { session } = await createAgentSession({
40
+ model,
41
+ modelRuntime: runtime,
42
+ thinkingLevel: "medium",
43
+ noTools: "all",
44
+ });
45
+ try {
46
+ const t0 = Date.now();
47
+ await session.prompt(buildRolePrompt(task, ""));
48
+ const ms = Date.now() - t0;
49
+ const stats = session.getSessionStats();
50
+ addSessionCost(stats.cost);
51
+ const out = {
52
+ ...base,
53
+ output: lastAssistantText(session),
54
+ cost: Math.round(stats.cost * 10000) / 10000,
55
+ ms,
56
+ outputTokens: stats.tokens.output,
57
+ };
58
+ if (stats.tokens.total === 0)
59
+ out.error = "returned 0 tokens (invalid key, no credit/balance, or no model access)";
60
+ return out;
61
+ }
62
+ finally {
63
+ session.dispose(); // dispose even when prompt() throws (expected for inaccessible models)
64
+ }
65
+ }
66
+ catch (e) {
67
+ return { ...base, error: e instanceof Error ? e.message : String(e) };
68
+ }
69
+ }
70
+ // ---- judge: score every output on one rubric, forced structured output ----
71
+ const JudgeSchema = Type.Object({
72
+ scores: Type.Array(Type.Object({
73
+ option: Type.String({ description: "the option letter, e.g. A" }),
74
+ score: Type.Number({ description: "0-10 quality for this deliverable" }),
75
+ reason: Type.String({ description: "one sentence" }),
76
+ })),
77
+ winner: Type.String({ description: "the option letter of the best output" }),
78
+ }, { additionalProperties: true });
79
+ function buildJudgeTool() {
80
+ let captured;
81
+ const tool = defineTool({
82
+ name: "submit_scores",
83
+ label: "Submit Scores",
84
+ description: "Submit a 0-10 quality score and one-sentence reason for every option, plus the winning option letter.",
85
+ parameters: JudgeSchema,
86
+ execute: async (_id, params) => {
87
+ captured = params;
88
+ return { content: [{ type: "text", text: `Scored ${params.scores.length} options; winner ${params.winner}.` }], details: {} };
89
+ },
90
+ });
91
+ return { tool, get: () => captured };
92
+ }
93
+ /** Judge anonymised outputs (A, B, C…) on one rubric for the task's capability. */
94
+ async function judge(task, entries, judgeCand) {
95
+ const scored = entries.filter((e) => !e.error && e.output);
96
+ if (scored.length < 2)
97
+ return { scores: [], winner: undefined, judgeId: id(judgeCand) };
98
+ const letters = scored.map((_, i) => String.fromCharCode(65 + i)); // A, B, C…
99
+ const blocks = scored.map((e, i) => `### Option ${letters[i]}\n${e.output}`).join("\n\n");
100
+ const runtime = await piRuntime();
101
+ const model = resolvePiModel(runtime, judgeCand.provider, judgeCand.model);
102
+ const { tool, get } = buildJudgeTool();
103
+ const { session } = await createAgentSession({
104
+ model,
105
+ modelRuntime: runtime,
106
+ thinkingLevel: "medium",
107
+ noTools: "all",
108
+ customTools: [tool],
109
+ tools: ["submit_scores"],
110
+ });
111
+ const prompt = [
112
+ `You are judging ${scored.length} anonymous attempts at the same ${task.capability} task. Be a strict, fair critic.`,
113
+ `Task: ${task.title}`,
114
+ "",
115
+ `Score each option 0-10 on how well it delivers a high-quality ${task.capability} result (correctness, completeness, clarity, usefulness). Then pick the single best.`,
116
+ "Call submit_scores exactly once with a score+reason for EVERY option letter and the winner.",
117
+ "",
118
+ blocks,
119
+ ].join("\n");
120
+ try {
121
+ await session.prompt(prompt);
122
+ let raw = get();
123
+ for (let i = 0; i < 2 && !raw; i++) {
124
+ await session.prompt("Call submit_scores now with a score for every option letter and the winner.");
125
+ raw = get();
126
+ }
127
+ addSessionCost(session.getSessionStats().cost);
128
+ if (!raw)
129
+ return { scores: [], winner: undefined, judgeId: id(judgeCand) };
130
+ const byLetter = new Map(letters.map((l, i) => [l, scored[i]]));
131
+ const scores = raw.scores
132
+ .map((s) => {
133
+ const e = byLetter.get(s.option.trim().toUpperCase().slice(0, 1));
134
+ return e ? { model: id(e), score: s.score, reason: s.reason } : undefined;
135
+ })
136
+ .filter((x) => !!x);
137
+ const winEntry = byLetter.get(String(raw.winner).trim().toUpperCase().slice(0, 1));
138
+ return { scores, winner: winEntry ? id(winEntry) : undefined, judgeId: id(judgeCand) };
139
+ }
140
+ finally {
141
+ session.dispose();
142
+ }
143
+ }
144
+ /** Run the full bake-off: every candidate on the task, then judge. */
145
+ export async function runBakeoff(task, candidates, opts = {}) {
146
+ const log = opts.onProgress ?? (() => { });
147
+ const entries = [];
148
+ for (const c of candidates) {
149
+ log(`running ${id(c)}…`);
150
+ const e = await runCandidate(task, c);
151
+ log(e.error ? ` ${id(c)}: ERROR ${e.error}` : ` ${id(c)}: $${e.cost.toFixed(4)} ${(e.ms / 1000).toFixed(1)}s ${e.outputTokens} tok`);
152
+ entries.push(e);
153
+ }
154
+ const judgeCand = opts.judge ?? candidates[0];
155
+ log(`judging with ${id(judgeCand)}…`);
156
+ const { scores, winner, judgeId } = await judge(task, entries, judgeCand);
157
+ return { task, entries, scores, winner, judge: judgeId };
158
+ }
159
+ /** Convenience: build a one-off Task for a capability/difficulty from a prompt. */
160
+ export function bakeoffTask(prompt, capability, difficulty = "medium") {
161
+ return { id: "BAKE", title: prompt, capability, difficulty, dependsOn: [], estTokens: estimateTokens(capability, difficulty) };
162
+ }
163
+ //# sourceMappingURL=bakeoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bakeoff.js","sourceRoot":"","sources":["../src/bakeoff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ // Build persistence — checkpoint a run so a halt/crash/cancel can resume without
2
+ // re-paying for finished tasks. The orchestrator itself stays fs-free; this module
3
+ // (and run-build) own the disk I/O.
4
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
5
+ export function newBuildState(id, tasks, idea, mode) {
6
+ return { id, idea, mode, tasks, outcomes: [], totalCost: 0, status: "running" };
7
+ }
8
+ export function saveState(state, path) {
9
+ writeFileSync(path, JSON.stringify(state, null, 2) + "\n");
10
+ }
11
+ export function loadState(path) {
12
+ if (!existsSync(path))
13
+ return undefined;
14
+ try {
15
+ return JSON.parse(readFileSync(path, "utf-8"));
16
+ }
17
+ catch (e) {
18
+ throw new Error(`Bad build state at ${path}: ${e instanceof Error ? e.message : e}`);
19
+ }
20
+ }
21
+ /** Which task ids are already finished (last outcome wins; a failed attempt does not
22
+ * count). Used to skip on resume. */
23
+ export function completedIds(state) {
24
+ return new Set(state.outcomes.filter((o) => !o.error).map((o) => o.taskId));
25
+ }
26
+ //# sourceMappingURL=build-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-state.js","sourceRoot":"","sources":["../src/build-state.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,mFAAmF;AACnF,oCAAoC;AAEpC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAqBlE,MAAM,UAAU,aAAa,CAAC,EAAU,EAAE,KAAa,EAAE,IAAa,EAAE,IAA0B;IAChG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAiB,EAAE,IAAY;IACvD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC/D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;AACH,CAAC;AAED;sCACsC;AACtC,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,19 @@
1
+ // Burndown — tasks remaining and cumulative spend across the build. There are no
2
+ // timestamps in build-state, so the X axis is task-completion order (step 1..N),
3
+ // which is the natural timeline for a build. Retries add a step (and cost) without
4
+ // burning down a task, so they show up as flat-remaining / rising-cost.
5
+ const round2 = (n) => Math.round(n * 100) / 100;
6
+ export function computeBurndown(state) {
7
+ const taskCount = state.tasks.length;
8
+ const done = new Set();
9
+ let cum = 0;
10
+ const steps = [];
11
+ for (const o of state.outcomes) {
12
+ const retry = done.has(o.taskId);
13
+ done.add(o.taskId);
14
+ cum += o.cost;
15
+ steps.push({ taskId: o.taskId, remaining: taskCount - done.size, cumCost: round2(cum), retry });
16
+ }
17
+ return { taskCount, totalCost: round2(cum), steps };
18
+ }
19
+ //# sourceMappingURL=burndown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burndown.js","sourceRoot":"","sources":["../src/burndown.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,iFAAiF;AACjF,mFAAmF;AACnF,wEAAwE;AAiBxE,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AAExD,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACnB,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,86 @@
1
+ // Self-calibrating token estimates. After each real task, we record its measured
2
+ // token usage per (capability, difficulty) AND per (capability, difficulty, model).
3
+ // The router prefers the per-model average (a Haiku and an Opus run of the same task
4
+ // differ a lot), then the generic bucket, then the static table. Persisted globally.
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { join } from "node:path";
8
+ const MIN_SAMPLES = 2; // trust calibration only after a couple of runs
9
+ const MAX_N = 20; // cap so old runs don't dominate
10
+ function calPath() {
11
+ return join(homedir(), ".projectinator", "calibration.json");
12
+ }
13
+ const key = (c, d, model) => (model ? `${c}/${d}/${model}` : `${c}/${d}`);
14
+ function load() {
15
+ try {
16
+ if (!existsSync(calPath()))
17
+ return {};
18
+ return JSON.parse(readFileSync(calPath(), "utf-8"));
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ function save(cal) {
25
+ try {
26
+ mkdirSync(join(homedir(), ".projectinator"), { recursive: true });
27
+ writeFileSync(calPath(), JSON.stringify(cal, null, 2) + "\n");
28
+ }
29
+ catch {
30
+ /* best effort — never break a build on a calibration write */
31
+ }
32
+ }
33
+ function fold(cal, k, inputTotal, output, cachedFraction, ms) {
34
+ const prev = cal[k];
35
+ if (!prev) {
36
+ cal[k] = { input: inputTotal, output, cachedFraction, n: 1, ...(ms ? { ms } : {}) };
37
+ return;
38
+ }
39
+ const n = Math.min(prev.n, MAX_N);
40
+ cal[k] = {
41
+ input: (prev.input * n + inputTotal) / (n + 1),
42
+ output: (prev.output * n + output) / (n + 1),
43
+ cachedFraction: (prev.cachedFraction * n + cachedFraction) / (n + 1),
44
+ n: prev.n + 1,
45
+ ...(ms ? { ms: prev.ms ? (prev.ms * n + ms) / (n + 1) : ms } : prev.ms ? { ms: prev.ms } : {}),
46
+ };
47
+ }
48
+ /** Fold a real measurement into the running averages for its bucket and its model. */
49
+ export function recordActual(capability, difficulty, inputTotal, output, cachedFraction, modelId, durationMs) {
50
+ if (!(inputTotal > 0))
51
+ return;
52
+ const cal = load();
53
+ fold(cal, key(capability, difficulty), inputTotal, output, cachedFraction, durationMs);
54
+ if (modelId)
55
+ fold(cal, key(capability, difficulty, modelId), inputTotal, output, cachedFraction, durationMs);
56
+ save(cal);
57
+ }
58
+ /** Typical wall-clock ms for a bucket (per-model if known), or undefined before any timed run. */
59
+ export function expectedDurationMs(capability, difficulty, modelId) {
60
+ const cal = load();
61
+ const s = (modelId && cal[key(capability, difficulty, modelId)]) || cal[key(capability, difficulty)];
62
+ return s?.ms;
63
+ }
64
+ /** All recorded samples keyed "capability/difficulty[/model]" (for the accuracy view). */
65
+ export function allSamples() {
66
+ return load();
67
+ }
68
+ function fromSample(s) {
69
+ if (!s || s.n < MIN_SAMPLES)
70
+ return undefined;
71
+ return {
72
+ input: Math.round(s.input),
73
+ output: Math.round(s.output),
74
+ cachedInputFraction: Math.min(0.95, Math.max(0, s.cachedFraction)),
75
+ };
76
+ }
77
+ /** Calibrated estimate for a bucket, once enough samples exist (model-agnostic). */
78
+ export function calibratedTokens(capability, difficulty) {
79
+ return fromSample(load()[key(capability, difficulty)]);
80
+ }
81
+ /** Calibrated estimate for a bucket ON A SPECIFIC MODEL, once enough samples exist.
82
+ * Undefined when that model hasn't run this bucket enough — callers keep their estimate. */
83
+ export function modelCalibratedTokens(capability, difficulty, modelId) {
84
+ return fromSample(load()[key(capability, difficulty, modelId)]);
85
+ }
86
+ //# sourceMappingURL=calibration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calibration.js","sourceRoot":"","sources":["../src/calibration.ts"],"names":[],"mappings":"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"}