@stackmemoryai/stackmemory 1.12.0 → 1.14.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 (162) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +3 -1
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/codex-sm.js +51 -11
  11. package/dist/src/cli/commands/brain.js +206 -0
  12. package/dist/src/cli/commands/company-os.js +184 -0
  13. package/dist/src/cli/commands/context.js +5 -0
  14. package/dist/src/cli/commands/operator.js +127 -0
  15. package/dist/src/cli/commands/orchestrate.js +2 -0
  16. package/dist/src/cli/commands/orchestrator.js +3 -2
  17. package/dist/src/cli/commands/patterns.js +254 -0
  18. package/dist/src/cli/commands/portal.js +161 -0
  19. package/dist/src/cli/commands/scaffold.js +92 -0
  20. package/dist/src/cli/commands/setup.js +1 -4
  21. package/dist/src/cli/commands/sync.js +253 -0
  22. package/dist/src/cli/commands/tasks.js +130 -1
  23. package/dist/src/cli/commands/vision.js +221 -0
  24. package/dist/src/cli/hermes-sm.js +224 -0
  25. package/dist/src/cli/index.js +15 -10
  26. package/dist/src/cli/utils/real-cli-bin.js +72 -0
  27. package/dist/src/core/brain/brain-store.js +187 -0
  28. package/dist/src/core/brain/brain-sync.js +193 -0
  29. package/dist/src/core/brain/index.js +78 -0
  30. package/dist/src/core/brain/types.js +10 -0
  31. package/dist/src/core/cache/token-estimator.js +24 -1
  32. package/dist/src/core/config/feature-flags.js +2 -6
  33. package/dist/src/core/context/frame-database.js +44 -0
  34. package/dist/src/core/context/recursive-context-manager.js +1 -1
  35. package/dist/src/core/context/rehydration.js +2 -1
  36. package/dist/src/core/database/sqlite-adapter.js +14 -1
  37. package/dist/src/core/models/model-router.js +33 -1
  38. package/dist/src/core/models/provider-pricing.js +58 -4
  39. package/dist/src/core/patterns/index.js +22 -0
  40. package/dist/src/core/patterns/pattern-applier.js +39 -0
  41. package/dist/src/core/patterns/pattern-observer.js +157 -0
  42. package/dist/src/core/patterns/pattern-store.js +259 -0
  43. package/dist/src/core/patterns/types.js +19 -0
  44. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  45. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  46. package/dist/src/core/skill-packs/types.js +14 -1
  47. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  48. package/dist/src/core/storage/cloud-sync.js +574 -0
  49. package/dist/src/core/storage/two-tier-storage.js +5 -1
  50. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  51. package/dist/src/core/tasks/md-task-parser.js +138 -0
  52. package/dist/src/core/vision/index.js +27 -0
  53. package/dist/src/core/vision/signals.js +79 -0
  54. package/dist/src/core/vision/types.js +22 -0
  55. package/dist/src/core/vision/vision-file.js +146 -0
  56. package/dist/src/core/vision/vision-loop.js +220 -0
  57. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  58. package/dist/src/daemon/daemon-config.js +45 -0
  59. package/dist/src/daemon/services/desire-path-service.js +566 -0
  60. package/dist/src/daemon/services/research-stream-service.js +320 -0
  61. package/dist/src/daemon/services/telemetry-service.js +192 -0
  62. package/dist/src/daemon/unified-daemon.js +28 -1
  63. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  64. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  65. package/dist/src/features/operator/adapter-factory.js +62 -0
  66. package/dist/src/features/operator/browser-adapter.js +109 -0
  67. package/dist/src/features/operator/desktop-adapter.js +125 -0
  68. package/dist/src/features/operator/index.js +39 -0
  69. package/dist/src/features/operator/llm-decision.js +137 -0
  70. package/dist/src/features/operator/operator-logger.js +92 -0
  71. package/dist/src/features/operator/overnight-runner.js +327 -0
  72. package/dist/src/features/operator/screen-adapter.js +91 -0
  73. package/dist/src/features/operator/session-manager.js +127 -0
  74. package/dist/src/features/operator/state-machine.js +227 -0
  75. package/dist/src/features/operator/task-queue.js +81 -0
  76. package/dist/src/features/portal/index.js +26 -0
  77. package/dist/src/features/portal/server.js +240 -0
  78. package/dist/src/features/portal/types.js +14 -0
  79. package/dist/src/features/portal/ui.js +195 -0
  80. package/dist/src/features/tasks/task-aware-context.js +2 -1
  81. package/dist/src/features/tui/simple-monitor.js +0 -23
  82. package/dist/src/features/tui/swarm-monitor.js +8 -66
  83. package/dist/src/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
  84. package/dist/src/features/web/client/lib/utils.js +12 -0
  85. package/dist/src/features/web/client/stores/session-store.js +12 -0
  86. package/dist/src/features/web/server/gcp-billing.js +76 -0
  87. package/dist/src/features/web/server/index.js +10 -0
  88. package/dist/src/features/web/server/spend-calculator.js +228 -0
  89. package/dist/src/hooks/schemas.js +4 -1
  90. package/dist/src/integrations/anthropic/client.js +3 -2
  91. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  92. package/dist/src/integrations/claude-code/subagent-client.js +218 -11
  93. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  94. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  95. package/dist/src/integrations/linear/webhook-server.js +18 -22
  96. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  97. package/dist/src/integrations/mcp/handlers/index.js +27 -52
  98. package/dist/src/integrations/mcp/server.js +122 -335
  99. package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
  100. package/dist/src/integrations/mcp/tool-definitions.js +111 -510
  101. package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
  102. package/dist/src/orchestrators/multimodal/determinism.js +2 -1
  103. package/dist/src/orchestrators/multimodal/harness.js +2 -1
  104. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  105. package/dist/src/utils/process-cleanup.js +1 -7
  106. package/docs/README.md +42 -0
  107. package/docs/guides/README_INSTALL.md +208 -0
  108. package/package.json +18 -9
  109. package/scripts/claude-code-wrapper.sh +11 -0
  110. package/scripts/claude-sm-setup.sh +12 -1
  111. package/scripts/codex-wrapper.sh +11 -0
  112. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  113. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  114. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  115. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  116. package/scripts/hooks/cleanup-shell.sh +12 -1
  117. package/scripts/hooks/task-complete.sh +12 -1
  118. package/scripts/install-code-execution-hooks.sh +12 -1
  119. package/scripts/install-sweep-hook.sh +12 -0
  120. package/scripts/install.sh +11 -0
  121. package/scripts/opencode-wrapper.sh +11 -0
  122. package/scripts/portal/cloud-init.yaml +69 -0
  123. package/scripts/portal/setup.sh +69 -0
  124. package/scripts/portal/stackmemory-portal.service +34 -0
  125. package/scripts/setup-claude-integration.sh +12 -1
  126. package/scripts/smoke-init-db.sh +23 -0
  127. package/scripts/stackmemory-daemon.sh +11 -0
  128. package/scripts/verify-dist.cjs +11 -4
  129. package/dist/src/cli/commands/ralph.js +0 -1053
  130. package/dist/src/hooks/diffmem-hooks.js +0 -376
  131. package/dist/src/integrations/diffmem/client.js +0 -208
  132. package/dist/src/integrations/diffmem/config.js +0 -14
  133. package/dist/src/integrations/greptile/client.js +0 -101
  134. package/dist/src/integrations/greptile/config.js +0 -14
  135. package/dist/src/integrations/greptile/index.js +0 -11
  136. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +0 -188
  137. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  138. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  139. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  140. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  141. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  142. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -354
  143. package/dist/src/integrations/ralph/index.js +0 -17
  144. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
  145. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  146. package/dist/src/integrations/ralph/loopmax.js +0 -488
  147. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  148. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  149. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  150. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  151. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  152. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  153. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  154. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  155. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  156. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  157. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1005
  158. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  159. package/scripts/ralph-loop-implementation.js +0 -404
  160. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  161. /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
  162. /package/dist/src/{integrations/ralph/types.js → features/web/client/next-env.d.js} +0 -0
@@ -0,0 +1,138 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { readFileSync, writeFileSync } from "fs";
6
+ const HEADER_RE = /^\|\s*id\s*\|/i;
7
+ const SEPARATOR_RE = /^\|[\s-|]+\|$/;
8
+ const PRIORITIES = ["P0", "P1", "P2", "P3"];
9
+ const STATUSES = ["todo", "active", "done", "blocked", "cut"];
10
+ const SYNCS = ["local", "linear", "gh"];
11
+ function parseMasterTasks(content) {
12
+ const lines = content.split("\n");
13
+ const tasks = [];
14
+ let inTable = false;
15
+ for (const line of lines) {
16
+ const trimmed = line.trim();
17
+ if (!trimmed.startsWith("|")) {
18
+ if (inTable) break;
19
+ continue;
20
+ }
21
+ if (HEADER_RE.test(trimmed)) {
22
+ inTable = true;
23
+ continue;
24
+ }
25
+ if (SEPARATOR_RE.test(trimmed)) continue;
26
+ if (!inTable) continue;
27
+ const cells = trimmed.split("|").slice(1, -1).map((c) => c.trim());
28
+ if (cells.length < 8) continue;
29
+ const [id, priority, status, owner, sync, task, branchPr, notes] = cells;
30
+ if (!id || !PRIORITIES.includes(priority)) continue;
31
+ tasks.push({
32
+ id,
33
+ priority,
34
+ status: STATUSES.includes(status) ? status : "todo",
35
+ owner: owner || "@me",
36
+ sync: SYNCS.includes(sync) ? sync : "local",
37
+ task: task || "",
38
+ branchPr: branchPr || "",
39
+ notes: notes || ""
40
+ });
41
+ }
42
+ return tasks;
43
+ }
44
+ function serializeTaskRows(tasks) {
45
+ return tasks.map(
46
+ (t) => `| ${t.id} | ${t.priority} | ${t.status} | ${t.owner} | ${t.sync} | ${t.task} | ${t.branchPr} | ${t.notes} |`
47
+ ).join("\n");
48
+ }
49
+ function updateTaskInFile(filePath, taskId, updates) {
50
+ const content = readFileSync(filePath, "utf-8");
51
+ const lines = content.split("\n");
52
+ let found = false;
53
+ const updated = lines.map((line) => {
54
+ const trimmed = line.trim();
55
+ if (!trimmed.startsWith("|") || HEADER_RE.test(trimmed) || SEPARATOR_RE.test(trimmed)) {
56
+ return line;
57
+ }
58
+ const cells = trimmed.split("|").slice(1, -1).map((c) => c.trim());
59
+ if (cells.length < 8 || cells[0] !== taskId) return line;
60
+ found = true;
61
+ const task = {
62
+ id: cells[0],
63
+ priority: updates.priority ?? cells[1],
64
+ status: updates.status ?? cells[2],
65
+ owner: updates.owner ?? cells[3],
66
+ sync: updates.sync ?? cells[4],
67
+ task: updates.task ?? cells[5],
68
+ branchPr: updates.branchPr ?? cells[6],
69
+ notes: updates.notes ?? cells[7]
70
+ };
71
+ return `| ${task.id} | ${task.priority} | ${task.status} | ${task.owner} | ${task.sync} | ${task.task} | ${task.branchPr} | ${task.notes} |`;
72
+ });
73
+ if (!found) throw new Error(`Task ${taskId} not found in ${filePath}`);
74
+ writeFileSync(filePath, updated.join("\n"), "utf-8");
75
+ }
76
+ function addTaskToFile(filePath, task) {
77
+ const content = readFileSync(filePath, "utf-8");
78
+ const existing = parseMasterTasks(content);
79
+ const maxNum = existing.reduce((max, t) => {
80
+ const n = parseInt(t.id.replace(/^T/, ""), 10);
81
+ return isNaN(n) ? max : Math.max(max, n);
82
+ }, 0);
83
+ const id = `T${String(maxNum + 1).padStart(2, "0")}`;
84
+ const newRow = `| ${id} | ${task.priority} | ${task.status} | ${task.owner} | ${task.sync} | ${task.task} | ${task.branchPr} | ${task.notes} |`;
85
+ const lines = content.split("\n");
86
+ let insertIdx = -1;
87
+ let inTable = false;
88
+ for (let i = 0; i < lines.length; i++) {
89
+ const trimmed = lines[i].trim();
90
+ if (HEADER_RE.test(trimmed)) {
91
+ inTable = true;
92
+ continue;
93
+ }
94
+ if (inTable && trimmed.startsWith("|") && !SEPARATOR_RE.test(trimmed)) {
95
+ insertIdx = i;
96
+ }
97
+ if (inTable && !trimmed.startsWith("|") && trimmed !== "") {
98
+ break;
99
+ }
100
+ }
101
+ if (insertIdx === -1) {
102
+ for (let i = 0; i < lines.length; i++) {
103
+ if (SEPARATOR_RE.test(lines[i].trim())) {
104
+ insertIdx = i;
105
+ break;
106
+ }
107
+ }
108
+ }
109
+ lines.splice(insertIdx + 1, 0, newRow);
110
+ writeFileSync(filePath, lines.join("\n"), "utf-8");
111
+ return id;
112
+ }
113
+ function getNextTask(tasks) {
114
+ const actionable = tasks.filter(
115
+ (t) => t.status === "todo" || t.status === "active"
116
+ );
117
+ if (actionable.length === 0) return void 0;
118
+ const ownerRank = {
119
+ "@agent": 0,
120
+ "@me": 1,
121
+ "@defer": 2
122
+ };
123
+ actionable.sort((a, b) => {
124
+ const pDiff = PRIORITIES.indexOf(a.priority) - PRIORITIES.indexOf(b.priority);
125
+ if (pDiff !== 0) return pDiff;
126
+ const aRank = ownerRank[a.owner] ?? 1;
127
+ const bRank = ownerRank[b.owner] ?? 1;
128
+ return aRank - bRank;
129
+ });
130
+ return actionable[0];
131
+ }
132
+ export {
133
+ addTaskToFile,
134
+ getNextTask,
135
+ parseMasterTasks,
136
+ serializeTaskRows,
137
+ updateTaskInFile
138
+ };
@@ -0,0 +1,27 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ export * from "./types.js";
6
+ import {
7
+ parseVision,
8
+ loadVision,
9
+ setObjectiveDone,
10
+ scaffoldVision,
11
+ objectiveId,
12
+ VISION_TEMPLATE
13
+ } from "./vision-file.js";
14
+ import { SignalInbox } from "./signals.js";
15
+ import {
16
+ VisionLoop
17
+ } from "./vision-loop.js";
18
+ export {
19
+ SignalInbox,
20
+ VISION_TEMPLATE,
21
+ VisionLoop,
22
+ loadVision,
23
+ objectiveId,
24
+ parseVision,
25
+ scaffoldVision,
26
+ setObjectiveDone
27
+ };
@@ -0,0 +1,79 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import {
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ appendFileSync
11
+ } from "fs";
12
+ import { dirname } from "path";
13
+ import { randomUUID } from "crypto";
14
+ class SignalInbox {
15
+ constructor(path) {
16
+ this.path = path;
17
+ }
18
+ ensureDir() {
19
+ const dir = dirname(this.path);
20
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
21
+ }
22
+ add(input) {
23
+ this.ensureDir();
24
+ const signal = {
25
+ id: randomUUID(),
26
+ source: input.source ?? "manual",
27
+ severity: input.severity ?? "medium",
28
+ text: input.text,
29
+ createdAt: Date.now(),
30
+ ...input.refs ? { refs: input.refs } : {}
31
+ };
32
+ appendFileSync(this.path, JSON.stringify(signal) + "\n");
33
+ return signal;
34
+ }
35
+ all() {
36
+ if (!existsSync(this.path)) return [];
37
+ return readFileSync(this.path, "utf-8").split(/\r?\n/).filter(Boolean).map((l) => {
38
+ try {
39
+ return JSON.parse(l);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }).filter((s) => !!s);
44
+ }
45
+ /** Unresolved signals, most severe + oldest first. */
46
+ pending() {
47
+ const rank = {
48
+ low: 1,
49
+ medium: 2,
50
+ high: 3,
51
+ critical: 4
52
+ };
53
+ return this.all().filter((s) => !s.resolvedAt).sort(
54
+ (a, b) => rank[b.severity] - rank[a.severity] || a.createdAt - b.createdAt
55
+ );
56
+ }
57
+ /** Rewrite the file marking a signal resolved (compacts the log). */
58
+ resolve(id) {
59
+ const signals = this.all();
60
+ let changed = false;
61
+ for (const s of signals) {
62
+ if (s.id === id && !s.resolvedAt) {
63
+ s.resolvedAt = Date.now();
64
+ changed = true;
65
+ }
66
+ }
67
+ if (changed) {
68
+ this.ensureDir();
69
+ writeFileSync(
70
+ this.path,
71
+ signals.map((s) => JSON.stringify(s)).join("\n") + "\n"
72
+ );
73
+ }
74
+ return changed;
75
+ }
76
+ }
77
+ export {
78
+ SignalInbox
79
+ };
@@ -0,0 +1,22 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ const DEFAULT_LIMITS = {
6
+ maxIterations: 10,
7
+ maxIterationsPerDay: 50,
8
+ maxConsecutiveFailures: 3,
9
+ tickIntervalSec: 60,
10
+ requireApproval: false,
11
+ stopWhenComplete: true
12
+ };
13
+ const SEVERITY_RANK = {
14
+ low: 1,
15
+ medium: 2,
16
+ high: 3,
17
+ critical: 4
18
+ };
19
+ export {
20
+ DEFAULT_LIMITS,
21
+ SEVERITY_RANK
22
+ };
@@ -0,0 +1,146 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { existsSync, readFileSync, writeFileSync } from "fs";
6
+ import { createHash } from "crypto";
7
+ import {
8
+ DEFAULT_LIMITS
9
+ } from "./types.js";
10
+ function objectiveId(text) {
11
+ return createHash("sha1").update(text.trim()).digest("hex").slice(0, 10);
12
+ }
13
+ function splitSections(text) {
14
+ const lines = text.split(/\r?\n/);
15
+ const sections = /* @__PURE__ */ new Map();
16
+ const preamble = [];
17
+ let current = null;
18
+ for (const line of lines) {
19
+ const h2 = line.match(/^##\s+(.+?)\s*$/);
20
+ if (h2?.[1]) {
21
+ current = { body: [] };
22
+ sections.set(h2[1].toLowerCase(), current);
23
+ continue;
24
+ }
25
+ if (/^#\s+/.test(line)) continue;
26
+ if (current) {
27
+ current.body.push(line);
28
+ } else {
29
+ preamble.push(line);
30
+ }
31
+ }
32
+ return { preamble, sections };
33
+ }
34
+ function bulletLines(body) {
35
+ return body.map((l) => l.match(/^\s*[-*]\s+(.*\S)\s*$/)?.[1]?.trim()).filter((s) => !!s).filter((s) => !/^\[[ xX]\]/.test(s));
36
+ }
37
+ function parseObjectives(body) {
38
+ const objectives = [];
39
+ for (const line of body) {
40
+ const m = line.match(/^\s*[-*]\s+\[([ xX])\]\s+(.*\S)\s*$/);
41
+ if (!m?.[2]) continue;
42
+ const text = m[2].trim();
43
+ objectives.push({
44
+ id: objectiveId(text),
45
+ text,
46
+ done: (m[1] ?? "").toLowerCase() === "x"
47
+ });
48
+ }
49
+ return objectives;
50
+ }
51
+ function parseLimits(body) {
52
+ const limits = { ...DEFAULT_LIMITS };
53
+ for (const line of body) {
54
+ const m = line.match(/^\s*([a-zA-Z]+)\s*:\s*(.+?)\s*$/);
55
+ if (!m?.[1] || m[2] === void 0) continue;
56
+ const key = m[1];
57
+ const raw = m[2];
58
+ if (!(key in limits)) continue;
59
+ if (key === "requireApproval" || key === "stopWhenComplete") {
60
+ limits[key] = /^(true|yes|1)$/i.test(raw);
61
+ } else {
62
+ const n = parseInt(raw, 10);
63
+ if (Number.isFinite(n)) limits[key] = n;
64
+ }
65
+ }
66
+ return limits;
67
+ }
68
+ function parseVision(text) {
69
+ const { preamble, sections } = splitSections(text);
70
+ const body = (name) => sections.get(name)?.body ?? [];
71
+ const mission = preamble.map((l) => l.trim()).filter(Boolean).join(" ").trim();
72
+ return {
73
+ mission,
74
+ guardrails: bulletLines(body("guardrails")),
75
+ scope: bulletLines(body("scope")),
76
+ objectives: parseObjectives(body("objectives")),
77
+ limits: sections.has("limits") ? parseLimits(body("limits")) : { ...DEFAULT_LIMITS }
78
+ };
79
+ }
80
+ function loadVision(path) {
81
+ if (!existsSync(path)) return null;
82
+ return parseVision(readFileSync(path, "utf-8"));
83
+ }
84
+ function setObjectiveDone(path, objId, done) {
85
+ if (!existsSync(path)) return false;
86
+ const lines = readFileSync(path, "utf-8").split(/\r?\n/);
87
+ let changed = false;
88
+ for (let i = 0; i < lines.length; i++) {
89
+ const line = lines[i];
90
+ if (line === void 0) continue;
91
+ const m = line.match(/^(\s*[-*]\s+)\[([ xX])\]\s+(.*\S)\s*$/);
92
+ if (!m?.[3]) continue;
93
+ if (objectiveId(m[3].trim()) === objId) {
94
+ lines[i] = `${m[1] ?? ""}[${done ? "x" : " "}] ${m[3].trim()}`;
95
+ changed = true;
96
+ break;
97
+ }
98
+ }
99
+ if (changed) writeFileSync(path, lines.join("\n"));
100
+ return changed;
101
+ }
102
+ const VISION_TEMPLATE = `# Vision
103
+
104
+ State the single north-star mission this autonomous loop serves. Keep it to a
105
+ sentence or two \u2014 concrete enough to judge whether a piece of work belongs.
106
+
107
+ ## Guardrails
108
+
109
+ - Stay within the scope below; do not touch anything outside it.
110
+ - Never modify secrets, production credentials, or deploy/publish.
111
+ - Open a PR for review; never merge to the default branch autonomously.
112
+ - If an objective is ambiguous or risky, stop and ask a human.
113
+
114
+ ## Scope
115
+
116
+ - src/**
117
+ - docs/**
118
+
119
+ ## Objectives
120
+
121
+ - [ ] First concrete objective the loop should pursue
122
+ - [ ] Second objective
123
+ - [ ] Third objective
124
+
125
+ ## Limits
126
+
127
+ maxIterations: 10
128
+ maxIterationsPerDay: 50
129
+ maxConsecutiveFailures: 3
130
+ tickIntervalSec: 60
131
+ requireApproval: false
132
+ stopWhenComplete: true
133
+ `;
134
+ function scaffoldVision(path, force = false) {
135
+ if (existsSync(path) && !force) return false;
136
+ writeFileSync(path, VISION_TEMPLATE);
137
+ return true;
138
+ }
139
+ export {
140
+ VISION_TEMPLATE,
141
+ loadVision,
142
+ objectiveId,
143
+ parseVision,
144
+ scaffoldVision,
145
+ setObjectiveDone
146
+ };
@@ -0,0 +1,220 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { dirname } from "path";
7
+ import { loadVision, setObjectiveDone } from "./vision-file.js";
8
+ import { SignalInbox } from "./signals.js";
9
+ import {
10
+ SEVERITY_RANK
11
+ } from "./types.js";
12
+ function today() {
13
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
14
+ }
15
+ const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
16
+ class VisionLoop {
17
+ opts;
18
+ inbox;
19
+ constructor(options) {
20
+ this.opts = { sleep: realSleep, ...options };
21
+ this.inbox = new SignalInbox(options.signalsPath);
22
+ }
23
+ readState() {
24
+ const base = {
25
+ day: today(),
26
+ iterationsToday: 0,
27
+ consecutiveFailures: 0,
28
+ lastTickAt: 0
29
+ };
30
+ if (!existsSync(this.opts.statePath)) return base;
31
+ try {
32
+ const s = JSON.parse(
33
+ readFileSync(this.opts.statePath, "utf-8")
34
+ );
35
+ if (s.day !== today()) return { ...s, day: today(), iterationsToday: 0 };
36
+ return s;
37
+ } catch {
38
+ return base;
39
+ }
40
+ }
41
+ writeState(s) {
42
+ const dir = dirname(this.opts.statePath);
43
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
44
+ writeFileSync(this.opts.statePath, JSON.stringify(s, null, 2));
45
+ }
46
+ /** Pick the next unit of work: pending signals outrank pending objectives. */
47
+ selectCandidate(vision) {
48
+ const signal = this.inbox.pending()[0];
49
+ if (signal) {
50
+ return {
51
+ kind: "signal",
52
+ id: signal.id,
53
+ text: signal.text,
54
+ priority: 100 + SEVERITY_RANK[signal.severity],
55
+ refs: signal.refs ?? []
56
+ };
57
+ }
58
+ const idx = vision.objectives.findIndex((o2) => !o2.done);
59
+ const o = idx >= 0 ? vision.objectives[idx] : void 0;
60
+ if (o) {
61
+ return {
62
+ kind: "objective",
63
+ id: o.id,
64
+ text: o.text,
65
+ priority: 50 - idx,
66
+ refs: []
67
+ };
68
+ }
69
+ return null;
70
+ }
71
+ checkGuardrails(state, vision, iterationThisRun) {
72
+ const l = vision.limits;
73
+ if (iterationThisRun >= l.maxIterations) {
74
+ return {
75
+ ok: false,
76
+ reason: `reached maxIterations (${l.maxIterations}) for this run`
77
+ };
78
+ }
79
+ if (state.iterationsToday >= l.maxIterationsPerDay) {
80
+ return {
81
+ ok: false,
82
+ reason: `reached maxIterationsPerDay (${l.maxIterationsPerDay})`
83
+ };
84
+ }
85
+ if (state.consecutiveFailures >= l.maxConsecutiveFailures) {
86
+ return {
87
+ ok: false,
88
+ reason: `circuit breaker: ${state.consecutiveFailures} consecutive failures (limit ${l.maxConsecutiveFailures})`
89
+ };
90
+ }
91
+ return { ok: true };
92
+ }
93
+ /** Has the brain already concluded this exact piece of work? */
94
+ priorConclusion(text) {
95
+ const hits = this.brainRecall(text);
96
+ const match = hits.find(
97
+ (e) => e.title.trim() === text.trim() && e.conclusion.trim().length > 0
98
+ );
99
+ return match?.conclusion;
100
+ }
101
+ brainRecall(text) {
102
+ return this.opts.brain.recall({ text, limit: 5 });
103
+ }
104
+ async tick(iterationThisRun, dryRun = false) {
105
+ const vision = loadVision(this.opts.visionPath);
106
+ if (!vision) {
107
+ return {
108
+ candidate: null,
109
+ guardrail: { ok: false, reason: "no VISION.md found" },
110
+ skippedAsKnown: false,
111
+ delegated: false
112
+ };
113
+ }
114
+ const state = this.readState();
115
+ const guardrail = this.checkGuardrails(state, vision, iterationThisRun);
116
+ if (!guardrail.ok) {
117
+ return {
118
+ candidate: null,
119
+ guardrail,
120
+ skippedAsKnown: false,
121
+ delegated: false
122
+ };
123
+ }
124
+ const candidate = this.selectCandidate(vision);
125
+ if (!candidate) {
126
+ return {
127
+ candidate: null,
128
+ guardrail: { ok: true },
129
+ skippedAsKnown: false,
130
+ delegated: false
131
+ };
132
+ }
133
+ const prior = this.priorConclusion(candidate.text);
134
+ if (prior) {
135
+ if (candidate.kind === "objective") {
136
+ setObjectiveDone(this.opts.visionPath, candidate.id, true);
137
+ } else {
138
+ this.inbox.resolve(candidate.id);
139
+ }
140
+ return {
141
+ candidate,
142
+ guardrail: { ok: true },
143
+ skippedAsKnown: true,
144
+ priorConclusion: prior,
145
+ delegated: false
146
+ };
147
+ }
148
+ if (dryRun || vision.limits.requireApproval) {
149
+ return {
150
+ candidate,
151
+ guardrail: { ok: true },
152
+ skippedAsKnown: false,
153
+ delegated: false
154
+ };
155
+ }
156
+ const outcome = await this.opts.delegate(candidate, vision);
157
+ this.opts.brain.record({
158
+ title: candidate.text,
159
+ summary: `Vision loop handled a ${candidate.kind} toward: ${vision.mission}`,
160
+ conclusion: outcome.conclusion,
161
+ kind: "experiment",
162
+ agent: "vision",
163
+ tags: ["vision", candidate.kind, outcome.success ? "success" : "failure"],
164
+ refs: [...candidate.refs, ...outcome.refs ?? []],
165
+ confidence: outcome.success ? 0.8 : 0.4
166
+ });
167
+ const next = this.readState();
168
+ if (outcome.success) {
169
+ next.iterationsToday += 1;
170
+ next.consecutiveFailures = 0;
171
+ if (candidate.kind === "objective") {
172
+ setObjectiveDone(this.opts.visionPath, candidate.id, true);
173
+ } else {
174
+ this.inbox.resolve(candidate.id);
175
+ }
176
+ } else {
177
+ next.consecutiveFailures += 1;
178
+ }
179
+ next.lastTickAt = Date.now();
180
+ this.writeState(next);
181
+ return {
182
+ candidate,
183
+ guardrail: { ok: true },
184
+ skippedAsKnown: false,
185
+ delegated: true,
186
+ outcome
187
+ };
188
+ }
189
+ /** Run ticks until a guardrail stops the loop or there's nothing left. */
190
+ async run(opts = {}) {
191
+ const vision = loadVision(this.opts.visionPath);
192
+ const max = opts.maxIterations ?? vision?.limits.maxIterations ?? 1;
193
+ const tickInterval = (vision?.limits.tickIntervalSec ?? 60) * 1e3;
194
+ const decisions = [];
195
+ let delegated = 0;
196
+ let skipped = 0;
197
+ let stopped = "completed run";
198
+ for (let i = 0; i < max; i++) {
199
+ const d = await this.tick(i, opts.dryRun);
200
+ decisions.push(d);
201
+ if (!d.guardrail.ok) {
202
+ stopped = d.guardrail.reason ?? "guardrail stop";
203
+ break;
204
+ }
205
+ if (!d.candidate) {
206
+ stopped = "no work remaining";
207
+ break;
208
+ }
209
+ if (d.skippedAsKnown) skipped++;
210
+ if (d.delegated) delegated++;
211
+ if (!opts.dryRun && i < max - 1) {
212
+ await this.opts.sleep(tickInterval);
213
+ }
214
+ }
215
+ return { decisions, stopped, delegated, skipped };
216
+ }
217
+ }
218
+ export {
219
+ VisionLoop
220
+ };