huddle-cli 0.0.0-stage → 0.1.1

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 (75) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/QUICKSTART.md +102 -0
  4. package/README.md +376 -2
  5. package/dist/adapters/amp.js +40 -0
  6. package/dist/adapters/antigravity.js +54 -0
  7. package/dist/adapters/claude.js +86 -0
  8. package/dist/adapters/codex.js +64 -0
  9. package/dist/adapters/copilot.js +69 -0
  10. package/dist/adapters/cursor.js +92 -0
  11. package/dist/adapters/custom.js +342 -0
  12. package/dist/adapters/droid.js +58 -0
  13. package/dist/adapters/gemini.js +61 -0
  14. package/dist/adapters/goose.js +57 -0
  15. package/dist/adapters/index.js +161 -0
  16. package/dist/adapters/kiro.js +41 -0
  17. package/dist/adapters/opencode.js +63 -0
  18. package/dist/adapters/qwen.js +67 -0
  19. package/dist/adapters/types.js +6 -0
  20. package/dist/adapters/usage.js +141 -0
  21. package/dist/attach/connect.js +412 -0
  22. package/dist/attach/guard.js +83 -0
  23. package/dist/attach/live.js +226 -0
  24. package/dist/attach/plug.js +185 -0
  25. package/dist/attach/screen.js +150 -0
  26. package/dist/attach/session.js +177 -0
  27. package/dist/cli.js +244 -0
  28. package/dist/mcp/context-tools.js +79 -0
  29. package/dist/mcp/server.js +267 -0
  30. package/dist/orchestrator/ask.js +54 -0
  31. package/dist/orchestrator/brain.js +174 -0
  32. package/dist/orchestrator/council.js +615 -0
  33. package/dist/orchestrator/goal.js +196 -0
  34. package/dist/orchestrator/handoff.js +117 -0
  35. package/dist/orchestrator/launcher.js +171 -0
  36. package/dist/orchestrator/learn.js +105 -0
  37. package/dist/orchestrator/planner.js +212 -0
  38. package/dist/orchestrator/qa.js +398 -0
  39. package/dist/orchestrator/radar.js +719 -0
  40. package/dist/orchestrator/scheduler.js +1207 -0
  41. package/dist/orchestrator/takeover.js +240 -0
  42. package/dist/platform.js +273 -0
  43. package/dist/preflight.js +31 -0
  44. package/dist/relay/server.js +113 -0
  45. package/dist/room/context.js +1285 -0
  46. package/dist/room/contract.js +132 -0
  47. package/dist/room/db.js +680 -0
  48. package/dist/room/followup.js +233 -0
  49. package/dist/room/identity.js +122 -0
  50. package/dist/room/lessons.js +275 -0
  51. package/dist/room/live.js +348 -0
  52. package/dist/room/mail.js +212 -0
  53. package/dist/room/progress.js +149 -0
  54. package/dist/room/relaylink.js +85 -0
  55. package/dist/room/server.js +882 -0
  56. package/dist/room/sqlite.js +156 -0
  57. package/dist/room/transport.js +107 -0
  58. package/dist/room/tunnel.js +86 -0
  59. package/dist/sandbox/sync.js +104 -0
  60. package/dist/sandbox/worktree.js +392 -0
  61. package/dist/types.js +4 -0
  62. package/dist/ui/app.js +1955 -0
  63. package/dist/ui/catalog.js +113 -0
  64. package/dist/ui/dashboard.js +335 -0
  65. package/dist/ui/doctor.js +58 -0
  66. package/dist/ui/lessons.js +81 -0
  67. package/dist/ui/logo.js +40 -0
  68. package/dist/ui/paste.js +515 -0
  69. package/dist/ui/remote.js +338 -0
  70. package/dist/ui/setup.js +152 -0
  71. package/dist/ui/story.js +1529 -0
  72. package/dist/ui/term.js +249 -0
  73. package/dist/ui/watch.js +271 -0
  74. package/install.sh +44 -0
  75. package/package.json +63 -4
@@ -0,0 +1,132 @@
1
+ // The CONTRACT: the exact cross-task names every task codes to — files and who
2
+ // owns them, exported functions with signatures, data shapes, CLI flags,
3
+ // routes, error strings. The design council ratifies it before work starts;
4
+ // agents amend it mid-task (huddle_amend_contract) instead of inventing a name
5
+ // a teammate depends on. Found live: two agents built the same app with
6
+ // different names for the same thing, and the mismatch only surfaced at merge.
7
+ //
8
+ // Stored as Room events (contract.set / contract.amended), scoped to the
9
+ // current session, so no schema change and `huddle story` replays it for free.
10
+ import crypto from "node:crypto";
11
+ /** The contract's text is capped: it rides in every task's launch prompt. */
12
+ export const CONTRACT_CAP = 3000;
13
+ /** Cap at a line break, so no name is cut in half (a half name is worse than none). */
14
+ export function capContract(text, cap = CONTRACT_CAP) {
15
+ const t = text.trim();
16
+ if (t.length <= cap)
17
+ return t;
18
+ const cut = t.slice(0, cap - 12);
19
+ const nl = cut.lastIndexOf("\n");
20
+ return (nl > cap / 2 ? cut.slice(0, nl) : cut.replace(/\s+\S*$/, "")) + "\n…(capped)";
21
+ }
22
+ const sessionId = (db) => db.currentSession()?.id ?? null;
23
+ function parse(p) {
24
+ try {
25
+ return JSON.parse(p || "{}");
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ /** The current session's contract with its amendments, or null when none was set. */
32
+ export function getContract(db) {
33
+ const sid = sessionId(db);
34
+ const evs = db.eventsOfType(["contract.set", "contract.amended"], 0, 2000).map((e) => ({ type: e.type, ts: e.ts, p: parse(e.payload) }));
35
+ const sets = evs.filter((e) => e.type === "contract.set" && (e.p.session ?? null) === sid);
36
+ const base = sets[sets.length - 1];
37
+ if (!base)
38
+ return null;
39
+ // Amendments name the contract they amend, so a same-millisecond tie can't misorder them.
40
+ const amendments = evs
41
+ .filter((e) => e.type === "contract.amended" && e.p.base === base.p.id)
42
+ .map((e) => ({ section: String(e.p.section ?? ""), change: String(e.p.change ?? ""), why: String(e.p.why ?? ""), by: String(e.p.by ?? ""), version: Number(e.p.version) || 0, ts: e.ts }))
43
+ .sort((a, b) => a.version - b.version);
44
+ return {
45
+ text: String(base.p.text ?? ""),
46
+ version: amendments.length ? amendments[amendments.length - 1].version : typeof base.p.version === "number" ? base.p.version : 1,
47
+ by: base.p.by ?? null,
48
+ amendments,
49
+ };
50
+ }
51
+ /** Set (or replace) this session's contract: version 1, no amendments. */
52
+ export function setContract(db, text, by) {
53
+ const t = capContract(text);
54
+ db.publish("contract.set", { agent_id: by, payload: { id: crypto.randomUUID(), session: sessionId(db), text: t, by, version: t ? 1 : 0 } });
55
+ }
56
+ /**
57
+ * Amend the contract: bumps its version and publishes "contract.amended".
58
+ * With no contract yet (a small goal skips the council), it starts an empty
59
+ * one, so agents can still agree names mid-task.
60
+ */
61
+ export function amendContract(db, a) {
62
+ if (!getContract(db))
63
+ setContract(db, "", null);
64
+ const c = getContract(db);
65
+ const section = String(a.section ?? "").trim().slice(0, 200);
66
+ const change = String(a.change ?? "").trim().slice(0, 1500);
67
+ // A retried tool call is the same amendment, not a new version (and not a second broadcast).
68
+ const last = c.amendments[c.amendments.length - 1];
69
+ if (last && last.by === a.by && last.section === section && last.change === change)
70
+ return { version: last.version, duplicate: true };
71
+ const sets = db.eventsOfType(["contract.set"], 0, 2000).map((e) => parse(e.payload)).filter((p) => (p.session ?? null) === sessionId(db));
72
+ const version = c.version + 1;
73
+ db.publish("contract.amended", {
74
+ agent_id: a.by,
75
+ payload: { base: sets[sets.length - 1].id, version, section, change, why: String(a.why ?? "").trim().slice(0, 500), by: a.by },
76
+ });
77
+ return { version };
78
+ }
79
+ /** The contract as one prompt section for a task's launch; "" when there is none. */
80
+ export function contractBrief(db) {
81
+ const c = getContract(db);
82
+ if (!c || (!c.text && !c.amendments.length))
83
+ return "";
84
+ const text = capContract(c.text);
85
+ const am = c.amendments.slice(-8).map((x) => `- v${x.version} by ${x.by}: ${x.section}: ${x.change}${x.why ? ` (${x.why})` : ""}`);
86
+ return [
87
+ // Not "see HUDDLE_CONTRACT.md": its commit can fail, and it never carries amendments.
88
+ `CONTRACT v${c.version} — the exact names every task codes to (huddle_contract shows the latest):`,
89
+ ...(text ? [text] : []),
90
+ ...(am.length ? ["Amendments:", ...am] : []),
91
+ "Use these exact names. Never invent a name another task depends on that isn't here: call huddle_amend_contract(section, change, why) first — it tells every teammate.",
92
+ "It never overrides the goal: where they differ on an exact output, message or format, the goal wins (amend this).",
93
+ ].join("\n");
94
+ }
95
+ /** The contract as a file for the repo root, so every worktree has it. */
96
+ export function contractFile(text, by, members) {
97
+ return `# HUDDLE CONTRACT
98
+
99
+ The exact cross-task names every task codes to, ratified by the design council (${members.join(", ")})${by ? `, written by ${by}` : ""}.
100
+ To change a name, call huddle_amend_contract — don't edit this file. huddle_contract shows the latest version with amendments.
101
+
102
+ ${text.trim()}
103
+ `;
104
+ }
105
+ const NOT_NAMES = new Set(["function", "const", "export", "import", "return", "string", "number", "boolean", "null", "undefined", "void", "async", "await", "interface", "type", "class", "true", "false", "Promise", "Array", "Record", "Object", "JSON", "HUDDLE_CONTRACT"]);
106
+ /**
107
+ * The code names a contract fixes (for the radar's drift check): backticked
108
+ * identifiers, camelCase / snake_case / PascalCase-with-a-hump words, and
109
+ * anything called like `name(`. Plain English words are left out — a false
110
+ * "did you mean" sends an agent renaming code that works.
111
+ */
112
+ export function contractNames(c) {
113
+ if (!c)
114
+ return [];
115
+ const text = [c.text, ...c.amendments.map((a) => a.change)].join("\n");
116
+ const out = new Set();
117
+ const add = (n) => {
118
+ if (n.length >= 3 && !NOT_NAMES.has(n) && /^[A-Za-z_$][\w$]*$/.test(n))
119
+ out.add(n);
120
+ };
121
+ for (const m of text.matchAll(/`([^`\n]{1,200})`/g))
122
+ for (const w of m[1].match(/[A-Za-z_$][\w$]*/g) ?? [])
123
+ if (/[a-z][A-Z]|_|^[A-Z][a-z]+[A-Z]/.test(w) || m[1].trim() === w)
124
+ add(w);
125
+ for (const m of text.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g))
126
+ add(m[1]);
127
+ for (const w of text.match(/\b[A-Za-z_$][\w$]*\b/g) ?? [])
128
+ if (/[a-z][A-Z]|[a-z]_[a-z]|^[A-Z][a-z]+[A-Z]/.test(w))
129
+ add(w);
130
+ return [...out].slice(0, 300);
131
+ }
132
+ //# sourceMappingURL=contract.js.map