@ouro.bot/cli 0.1.0-alpha.17 → 0.1.0-alpha.171

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 (168) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +3 -2
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +3 -3
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +187 -191
  5. package/changelog.json +1152 -0
  6. package/dist/heart/active-work.js +633 -0
  7. package/dist/heart/bridges/manager.js +358 -0
  8. package/dist/heart/bridges/state-machine.js +135 -0
  9. package/dist/heart/bridges/store.js +123 -0
  10. package/dist/heart/commitments.js +105 -0
  11. package/dist/heart/config.js +40 -30
  12. package/dist/heart/core.js +608 -123
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/agent-service.js +360 -0
  16. package/dist/heart/daemon/auth-flow.js +477 -0
  17. package/dist/heart/daemon/cadence.js +70 -0
  18. package/dist/heart/daemon/daemon-cli.js +2473 -206
  19. package/dist/heart/daemon/daemon-entry.js +140 -4
  20. package/dist/heart/daemon/daemon-health.js +136 -0
  21. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  22. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  23. package/dist/heart/daemon/daemon.js +446 -17
  24. package/dist/heart/daemon/habit-migration.js +181 -0
  25. package/dist/heart/daemon/habit-parser.js +119 -0
  26. package/dist/heart/daemon/habit-scheduler.js +369 -0
  27. package/dist/heart/daemon/hatch-animation.js +10 -3
  28. package/dist/heart/daemon/hatch-flow.js +30 -111
  29. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  30. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  31. package/dist/heart/daemon/inner-status.js +89 -0
  32. package/dist/heart/daemon/launchd.js +161 -0
  33. package/dist/heart/daemon/log-tailer.js +4 -3
  34. package/dist/heart/daemon/mcp-server.js +591 -0
  35. package/dist/heart/daemon/message-router.js +17 -8
  36. package/dist/heart/daemon/os-cron-deps.js +134 -0
  37. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  38. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  39. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  40. package/dist/heart/daemon/outlook-http.js +399 -0
  41. package/dist/heart/daemon/outlook-read.js +1291 -0
  42. package/dist/heart/daemon/outlook-render.js +1032 -0
  43. package/dist/heart/daemon/outlook-types.js +7 -0
  44. package/dist/heart/daemon/outlook-view.js +194 -0
  45. package/dist/heart/daemon/process-manager.js +75 -0
  46. package/dist/heart/daemon/run-hooks.js +39 -0
  47. package/dist/heart/daemon/runtime-logging.js +58 -15
  48. package/dist/heart/daemon/runtime-metadata.js +219 -0
  49. package/dist/heart/daemon/runtime-mode.js +67 -0
  50. package/dist/heart/daemon/safe-mode.js +161 -0
  51. package/dist/heart/daemon/sense-manager.js +50 -2
  52. package/dist/heart/daemon/session-id-resolver.js +131 -0
  53. package/dist/heart/daemon/skill-management-installer.js +94 -0
  54. package/dist/heart/daemon/socket-client.js +202 -0
  55. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  56. package/dist/heart/daemon/specialist-prompt.js +13 -8
  57. package/dist/heart/daemon/specialist-tools.js +53 -4
  58. package/dist/heart/daemon/staged-restart.js +114 -0
  59. package/dist/heart/daemon/task-scheduler.js +3 -25
  60. package/dist/heart/daemon/thoughts.js +507 -0
  61. package/dist/heart/daemon/update-checker.js +111 -0
  62. package/dist/heart/daemon/update-hooks.js +138 -0
  63. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  64. package/dist/heart/delegation.js +62 -0
  65. package/dist/heart/identity.js +136 -55
  66. package/dist/heart/kicks.js +2 -20
  67. package/dist/heart/migrate-config.js +127 -0
  68. package/dist/heart/model-capabilities.js +48 -0
  69. package/dist/heart/obligations.js +197 -0
  70. package/dist/heart/progress-story.js +42 -0
  71. package/dist/heart/provider-failover.js +88 -0
  72. package/dist/heart/provider-ping.js +172 -0
  73. package/dist/heart/providers/anthropic-token.js +163 -0
  74. package/dist/heart/providers/anthropic.js +196 -36
  75. package/dist/heart/providers/azure.js +116 -10
  76. package/dist/heart/providers/github-copilot.js +157 -0
  77. package/dist/heart/providers/minimax.js +33 -3
  78. package/dist/heart/providers/openai-codex.js +51 -16
  79. package/dist/heart/session-activity.js +172 -0
  80. package/dist/heart/session-recall.js +216 -0
  81. package/dist/heart/streaming.js +62 -32
  82. package/dist/heart/target-resolution.js +127 -0
  83. package/dist/heart/tool-activity-callbacks.js +36 -0
  84. package/dist/heart/tool-description.js +135 -0
  85. package/dist/heart/tool-loop.js +200 -0
  86. package/dist/heart/turn-coordinator.js +28 -0
  87. package/dist/mind/associative-recall.js +122 -32
  88. package/dist/mind/bundle-manifest.js +14 -0
  89. package/dist/mind/context.js +125 -14
  90. package/dist/mind/{memory.js → diary.js} +70 -56
  91. package/dist/mind/first-impressions.js +16 -2
  92. package/dist/mind/friends/channel.js +56 -0
  93. package/dist/mind/friends/group-context.js +144 -0
  94. package/dist/mind/friends/store-file.js +19 -0
  95. package/dist/mind/friends/trust-explanation.js +74 -0
  96. package/dist/mind/friends/types.js +8 -0
  97. package/dist/mind/journal-index.js +161 -0
  98. package/dist/mind/obligation-steering.js +221 -0
  99. package/dist/mind/obligations.js +134 -0
  100. package/dist/mind/pending.js +76 -9
  101. package/dist/mind/phrases.js +1 -0
  102. package/dist/mind/prompt.js +649 -89
  103. package/dist/mind/token-estimate.js +8 -12
  104. package/dist/nerves/cli-logging.js +15 -2
  105. package/dist/nerves/coverage/run-artifacts.js +1 -1
  106. package/dist/nerves/event-buffer.js +111 -0
  107. package/dist/nerves/index.js +63 -3
  108. package/dist/nerves/observation.js +20 -0
  109. package/dist/nerves/runtime.js +5 -1
  110. package/dist/outlook-ui/assets/index-CBueh0d3.css +1 -0
  111. package/dist/outlook-ui/assets/index-rMPfWjDm.js +61 -0
  112. package/dist/outlook-ui/index.html +15 -0
  113. package/dist/repertoire/ado-semantic.js +6 -0
  114. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  115. package/dist/repertoire/coding/context-pack.js +254 -0
  116. package/dist/repertoire/coding/feedback.js +197 -30
  117. package/dist/repertoire/coding/manager.js +149 -2
  118. package/dist/repertoire/coding/spawner.js +55 -9
  119. package/dist/repertoire/coding/tools.js +135 -2
  120. package/dist/repertoire/guardrails.js +291 -0
  121. package/dist/repertoire/mcp-client.js +254 -0
  122. package/dist/repertoire/mcp-manager.js +198 -0
  123. package/dist/repertoire/skills.js +14 -23
  124. package/dist/repertoire/tasks/board.js +43 -5
  125. package/dist/repertoire/tasks/fix.js +182 -0
  126. package/dist/repertoire/tasks/index.js +28 -10
  127. package/dist/repertoire/tasks/lifecycle.js +2 -2
  128. package/dist/repertoire/tasks/parser.js +3 -2
  129. package/dist/repertoire/tasks/scanner.js +194 -37
  130. package/dist/repertoire/tasks/transitions.js +11 -28
  131. package/dist/repertoire/tools-base.js +991 -272
  132. package/dist/repertoire/tools-bluebubbles.js +94 -0
  133. package/dist/repertoire/tools-github.js +1 -6
  134. package/dist/repertoire/tools-teams.js +12 -59
  135. package/dist/repertoire/tools.js +281 -122
  136. package/dist/scripts/claude-code-hook.js +41 -0
  137. package/dist/scripts/claude-code-stop-hook.js +47 -0
  138. package/dist/senses/attention-queue.js +97 -0
  139. package/dist/senses/bluebubbles-client.js +102 -3
  140. package/dist/senses/bluebubbles-entry.js +2 -0
  141. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  142. package/dist/senses/bluebubbles-media.js +118 -23
  143. package/dist/senses/bluebubbles-model.js +12 -4
  144. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  145. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  146. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  147. package/dist/senses/bluebubbles.js +1008 -102
  148. package/dist/senses/cli-entry.js +1 -1
  149. package/dist/senses/cli-layout.js +187 -0
  150. package/dist/senses/cli.js +419 -152
  151. package/dist/senses/commands.js +38 -1
  152. package/dist/senses/continuity.js +94 -0
  153. package/dist/senses/habit-turn-message.js +108 -0
  154. package/dist/senses/inner-dialog-worker.js +112 -19
  155. package/dist/senses/inner-dialog.js +592 -84
  156. package/dist/senses/pipeline.js +502 -0
  157. package/dist/senses/shared-turn.js +196 -0
  158. package/dist/senses/surface-tool.js +95 -0
  159. package/dist/senses/teams.js +667 -120
  160. package/dist/senses/trust-gate.js +112 -2
  161. package/package.json +13 -5
  162. package/skills/configure-dev-tools.md +81 -0
  163. package/subagents/README.md +4 -70
  164. package/dist/heart/daemon/subagent-installer.js +0 -134
  165. package/dist/senses/debug-activity.js +0 -107
  166. package/subagents/work-doer.md +0 -235
  167. package/subagents/work-merger.md +0 -618
  168. package/subagents/work-planner.md +0 -382
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.McpManager = void 0;
4
+ exports.getSharedMcpManager = getSharedMcpManager;
5
+ exports.shutdownSharedMcpManager = shutdownSharedMcpManager;
6
+ exports.resetSharedMcpManager = resetSharedMcpManager;
7
+ const mcp_client_1 = require("./mcp-client");
8
+ const identity_1 = require("../heart/identity");
9
+ const runtime_1 = require("../nerves/runtime");
10
+ const MAX_RESTART_RETRIES = 5;
11
+ const RESTART_DELAY_MS = 1000;
12
+ class McpManager {
13
+ servers = new Map();
14
+ shuttingDown = false;
15
+ async start(servers) {
16
+ (0, runtime_1.emitNervesEvent)({
17
+ event: "mcp.manager_start",
18
+ component: "repertoire",
19
+ message: "starting MCP manager",
20
+ meta: { serverCount: Object.keys(servers).length },
21
+ });
22
+ const entries = Object.entries(servers);
23
+ for (const [name, config] of entries) {
24
+ await this.connectServer(name, config);
25
+ }
26
+ }
27
+ listAllTools() {
28
+ const result = [];
29
+ for (const [name, entry] of this.servers) {
30
+ result.push({ server: name, tools: entry.cachedTools });
31
+ }
32
+ return result;
33
+ }
34
+ async callTool(server, tool, args) {
35
+ const entry = this.servers.get(server);
36
+ if (!entry) {
37
+ throw new Error(`Unknown server: ${server}`);
38
+ }
39
+ if (!entry.client.isConnected()) {
40
+ throw new Error(`Server "${server}" is disconnected`);
41
+ }
42
+ return entry.client.callTool(tool, args);
43
+ }
44
+ shutdown() {
45
+ this.shuttingDown = true;
46
+ (0, runtime_1.emitNervesEvent)({
47
+ event: "mcp.manager_stop",
48
+ component: "repertoire",
49
+ message: "shutting down MCP manager",
50
+ meta: { serverCount: this.servers.size },
51
+ });
52
+ for (const [, entry] of this.servers) {
53
+ entry.client.shutdown();
54
+ }
55
+ this.servers.clear();
56
+ }
57
+ async connectServer(name, config) {
58
+ const client = new mcp_client_1.McpClient(config);
59
+ const entry = {
60
+ name,
61
+ config,
62
+ client,
63
+ cachedTools: [],
64
+ consecutiveFailures: 0,
65
+ };
66
+ this.servers.set(name, entry);
67
+ client.onClose(() => {
68
+ if (this.shuttingDown)
69
+ return;
70
+ this.handleServerCrash(name);
71
+ });
72
+ try {
73
+ await client.connect();
74
+ const tools = await client.listTools();
75
+ entry.cachedTools = tools;
76
+ entry.consecutiveFailures = 0;
77
+ }
78
+ catch (error) {
79
+ const reason = error instanceof Error ? error.message : String(error);
80
+ (0, runtime_1.emitNervesEvent)({
81
+ level: "error",
82
+ event: "mcp.connect_error",
83
+ component: "repertoire",
84
+ message: `failed to connect MCP server "${name}" (command: ${config.command}). Check that the command exists and is properly configured. Reason: ${reason}`,
85
+ meta: {
86
+ server: name,
87
+ command: config.command,
88
+ args: config.args,
89
+ reason,
90
+ },
91
+ });
92
+ }
93
+ }
94
+ handleServerCrash(name) {
95
+ const entry = this.servers.get(name);
96
+ /* v8 ignore next -- defensive: entry removed between close event and handler @preserve */
97
+ if (!entry)
98
+ return;
99
+ entry.consecutiveFailures++;
100
+ if (entry.consecutiveFailures > MAX_RESTART_RETRIES) {
101
+ (0, runtime_1.emitNervesEvent)({
102
+ level: "error",
103
+ event: "mcp.connect_error",
104
+ component: "repertoire",
105
+ message: `MCP server "${name}" exceeded max restart retries (${MAX_RESTART_RETRIES}). Giving up — check that "${entry.config.command}" exists and is properly configured in agent.json mcpServers.`,
106
+ meta: { server: name, command: entry.config.command, failures: entry.consecutiveFailures },
107
+ });
108
+ return;
109
+ }
110
+ (0, runtime_1.emitNervesEvent)({
111
+ level: "warn",
112
+ event: "mcp.server_restart",
113
+ component: "repertoire",
114
+ message: `restarting crashed MCP server: ${name}`,
115
+ meta: { server: name, attempt: entry.consecutiveFailures },
116
+ });
117
+ /* v8 ignore start -- timer callback: covered by mcp-manager.test.ts via fake timers but v8 can't trace @preserve */
118
+ setTimeout(() => {
119
+ if (this.shuttingDown)
120
+ return;
121
+ this.restartServer(name).catch(() => {
122
+ // Error handling is inside restartServer
123
+ });
124
+ }, RESTART_DELAY_MS);
125
+ /* v8 ignore stop */
126
+ }
127
+ /* v8 ignore start -- called from timer callback: covered by mcp-manager.test.ts via fake timers but v8 can't trace @preserve */
128
+ async restartServer(name) {
129
+ const entry = this.servers.get(name);
130
+ if (!entry)
131
+ return;
132
+ // Remove old entry and reconnect
133
+ this.servers.delete(name);
134
+ await this.connectServer(name, entry.config);
135
+ // Preserve failure count
136
+ const newEntry = this.servers.get(name);
137
+ if (newEntry) {
138
+ newEntry.consecutiveFailures = entry.consecutiveFailures;
139
+ }
140
+ }
141
+ }
142
+ exports.McpManager = McpManager;
143
+ let _sharedManager = null;
144
+ let _sharedManagerPromise = null;
145
+ /**
146
+ * Get or create a shared McpManager instance from the agent's config.
147
+ * Returns null if no mcpServers are configured.
148
+ * Safe to call from multiple senses — will only create one instance.
149
+ */
150
+ async function getSharedMcpManager() {
151
+ if (_sharedManager)
152
+ return _sharedManager;
153
+ /* v8 ignore next -- race guard: deduplicates concurrent initialization calls @preserve */
154
+ if (_sharedManagerPromise)
155
+ return _sharedManagerPromise;
156
+ _sharedManagerPromise = (async () => {
157
+ try {
158
+ const config = (0, identity_1.loadAgentConfig)();
159
+ const servers = config.mcpServers;
160
+ if (!servers || Object.keys(servers).length === 0)
161
+ return null;
162
+ const manager = new McpManager();
163
+ await manager.start(servers);
164
+ _sharedManager = manager;
165
+ return manager;
166
+ }
167
+ catch (error) {
168
+ (0, runtime_1.emitNervesEvent)({
169
+ level: "error",
170
+ event: "mcp.manager_start",
171
+ component: "repertoire",
172
+ message: "failed to initialize shared MCP manager",
173
+ /* v8 ignore next -- both branches tested: Error in wiring test, non-Error is defensive @preserve */
174
+ meta: { reason: error instanceof Error ? error.message : String(error) },
175
+ });
176
+ return null;
177
+ }
178
+ finally {
179
+ _sharedManagerPromise = null;
180
+ }
181
+ })();
182
+ return _sharedManagerPromise;
183
+ }
184
+ /**
185
+ * Shut down the shared MCP manager and clear the singleton.
186
+ * Called during daemon/agent shutdown.
187
+ */
188
+ function shutdownSharedMcpManager() {
189
+ if (_sharedManager) {
190
+ _sharedManager.shutdown();
191
+ _sharedManager = null;
192
+ }
193
+ }
194
+ /** Reset for testing only */
195
+ function resetSharedMcpManager() {
196
+ _sharedManager = null;
197
+ _sharedManagerPromise = null;
198
+ }
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getSkillsDir = getSkillsDir;
37
+ exports.getHarnessSkillsDir = getHarnessSkillsDir;
37
38
  exports.listSkills = listSkills;
38
39
  exports.loadSkill = loadSkill;
39
40
  exports.getLoadedSkills = getLoadedSkills;
@@ -50,9 +51,9 @@ function getSkillsDir() {
50
51
  function getProtocolMirrorDir() {
51
52
  return path.join(getSkillsDir(), "protocols");
52
53
  }
53
- // Canonical protocol source lives in {repoRoot}/subagents/.
54
- function getCanonicalProtocolsDir() {
55
- return path.join((0, identity_1.getRepoRoot)(), "subagents");
54
+ // Harness-level skills live in {repoRoot}/skills/ directory.
55
+ function getHarnessSkillsDir() {
56
+ return path.join((0, identity_1.getRepoRoot)(), "skills");
56
57
  }
57
58
  function listMarkdownBasenames(dir) {
58
59
  if (!fs.existsSync(dir))
@@ -74,8 +75,10 @@ function listSkills() {
74
75
  });
75
76
  const baseSkills = listMarkdownBasenames(getSkillsDir());
76
77
  const protocolMirrors = listMarkdownBasenames(getProtocolMirrorDir());
77
- const canonicalProtocols = listMarkdownBasenames(getCanonicalProtocolsDir());
78
- const skills = [...new Set([...baseSkills, ...protocolMirrors, ...canonicalProtocols])].sort();
78
+ const harnessSkills = listMarkdownBasenames(getHarnessSkillsDir());
79
+ // Agent skills (base + protocol) come first; harness skills are fallback.
80
+ // Set deduplicates by name — agent overrides harness.
81
+ const skills = [...new Set([...baseSkills, ...protocolMirrors, ...harnessSkills])].sort();
79
82
  (0, runtime_1.emitNervesEvent)({
80
83
  event: "repertoire.load_end",
81
84
  component: "repertoire",
@@ -93,7 +96,7 @@ function loadSkill(skillName) {
93
96
  });
94
97
  const directSkillPath = path.join(getSkillsDir(), `${skillName}.md`);
95
98
  const protocolMirrorPath = path.join(getProtocolMirrorDir(), `${skillName}.md`);
96
- const canonicalProtocolPath = path.join(getCanonicalProtocolsDir(), `${skillName}.md`);
99
+ const harnessSkillPath = path.join(getHarnessSkillsDir(), `${skillName}.md`);
97
100
  let resolvedPath = null;
98
101
  // 1) Direct agent skill.
99
102
  if (fs.existsSync(directSkillPath)) {
@@ -103,21 +106,9 @@ function loadSkill(skillName) {
103
106
  else if (fs.existsSync(protocolMirrorPath)) {
104
107
  resolvedPath = protocolMirrorPath;
105
108
  }
106
- // 3) Canonical protocol fallback.
107
- else if (fs.existsSync(canonicalProtocolPath)) {
108
- (0, runtime_1.emitNervesEvent)({
109
- level: "warn",
110
- event: "repertoire.error",
111
- component: "repertoire",
112
- message: "protocol mirror missing; using canonical fallback",
113
- meta: {
114
- operation: "loadSkill",
115
- skill: skillName,
116
- mirrorPath: protocolMirrorPath,
117
- canonicalPath: canonicalProtocolPath,
118
- },
119
- });
120
- resolvedPath = canonicalProtocolPath;
109
+ // 3) Harness-level skill (ships with npm package).
110
+ else if (fs.existsSync(harnessSkillPath)) {
111
+ resolvedPath = harnessSkillPath;
121
112
  }
122
113
  if (!resolvedPath) {
123
114
  (0, runtime_1.emitNervesEvent)({
@@ -128,13 +119,13 @@ function loadSkill(skillName) {
128
119
  meta: {
129
120
  operation: "loadSkill",
130
121
  skill: skillName,
131
- checkedPaths: [directSkillPath, protocolMirrorPath, canonicalProtocolPath],
122
+ checkedPaths: [directSkillPath, protocolMirrorPath, harnessSkillPath],
132
123
  },
133
124
  });
134
125
  throw new Error(`skill '${skillName}' not found in:\n` +
135
126
  `- ${directSkillPath}\n` +
136
127
  `- ${protocolMirrorPath}\n` +
137
- `- ${canonicalProtocolPath}`);
128
+ `- ${harnessSkillPath}`);
138
129
  }
139
130
  const content = fs.readFileSync(resolvedPath, "utf-8");
140
131
  if (!loadedSkills.includes(skillName)) {
@@ -7,14 +7,21 @@ exports.boardDeps = boardDeps;
7
7
  exports.boardSessions = boardSessions;
8
8
  const runtime_1 = require("../../nerves/runtime");
9
9
  const transitions_1 = require("./transitions");
10
- const BOARD_STATUS_ORDER = [
10
+ const BOARD_ACTIVE_ORDER = [
11
11
  "blocked",
12
12
  "processing",
13
13
  "collaborating",
14
14
  "drafting",
15
15
  "validating",
16
16
  "paused",
17
+ ];
18
+ const BOARD_TERMINAL_ORDER = [
17
19
  "done",
20
+ "cancelled",
21
+ ];
22
+ const BOARD_STATUS_ORDER = [
23
+ ...BOARD_ACTIVE_ORDER,
24
+ ...BOARD_TERMINAL_ORDER,
18
25
  ];
19
26
  function compactName(task) {
20
27
  const suffix = task.stem.replace(/^\d{4}-\d{2}-\d{2}-\d{4}-/, "");
@@ -29,6 +36,7 @@ function groupByStatus(tasks) {
29
36
  paused: [],
30
37
  blocked: [],
31
38
  done: [],
39
+ cancelled: [],
32
40
  };
33
41
  for (const task of tasks) {
34
42
  grouped[task.status].push(compactName(task));
@@ -59,8 +67,26 @@ function activeSessionLines(tasks) {
59
67
  });
60
68
  return active.map((task) => task.stem).sort();
61
69
  }
70
+ function activeBridgeLines(tasks) {
71
+ return tasks
72
+ .filter((task) => typeof task.frontmatter.active_bridge === "string" && String(task.frontmatter.active_bridge).trim())
73
+ .map((task) => `${task.stem} -> ${String(task.frontmatter.active_bridge).trim()}`)
74
+ .sort();
75
+ }
76
+ function buildHealthLine(issues) {
77
+ if (issues.length === 0)
78
+ return "health: clean";
79
+ const liveCount = issues.filter((i) => i.category === "live").length;
80
+ const migrationCount = issues.filter((i) => i.category === "migration").length;
81
+ const parts = [];
82
+ if (liveCount > 0)
83
+ parts.push(`${liveCount} live`);
84
+ if (migrationCount > 0)
85
+ parts.push(`${migrationCount} migration`);
86
+ return `health: ${parts.join(", ")}`;
87
+ }
62
88
  function actionRequired(index, byStatus) {
63
- const actions = [...index.parseErrors, ...index.invalidFilenames.map((filePath) => `bad filename: ${filePath}`)];
89
+ const actions = index.issues.map((issue) => `${issue.code}: ${issue.description} [${issue.target}]`);
64
90
  if (byStatus.blocked.length > 0) {
65
91
  actions.push(`blocked tasks: ${byStatus.blocked.join(", ")}`);
66
92
  }
@@ -77,14 +103,19 @@ function buildTaskBoard(index) {
77
103
  meta: { taskCount: index.tasks.length },
78
104
  });
79
105
  const byStatus = groupByStatus(index.tasks);
80
- const counts = transitions_1.TASK_VALID_STATUSES.map((status) => `${status}:${byStatus[status].length}`).join(" ");
106
+ const activeCounts = BOARD_ACTIVE_ORDER.map((status) => `${status}:${byStatus[status].length}`).join(" ");
81
107
  const processing = byStatus.processing.length > 0 ? `\n processing: ${byStatus.processing.join(", ")}` : "";
82
108
  const blocked = byStatus.blocked.length > 0 ? `\n blocked: ${byStatus.blocked.join(", ")}` : "";
83
- const compact = `[Tasks] ${counts}${processing}${blocked}`;
109
+ const terminalParts = BOARD_TERMINAL_ORDER
110
+ .filter((status) => byStatus[status].length > 0)
111
+ .map((status) => `${status}:${byStatus[status].length}`);
112
+ const terminalLine = terminalParts.length > 0 ? `\n terminal: ${terminalParts.join(" ")}` : "";
113
+ const healthLine = `\n ${buildHealthLine(index.issues)}`;
114
+ const compact = `[Tasks] ${activeCounts}${processing}${blocked}${terminalLine}${healthLine}`;
84
115
  const fullLines = [];
85
116
  for (const status of BOARD_STATUS_ORDER) {
86
117
  const names = byStatus[status];
87
- if (status === "done" && names.length === 0)
118
+ if ((status === "done" || status === "cancelled") && names.length === 0)
88
119
  continue;
89
120
  fullLines.push(`## ${status}`);
90
121
  fullLines.push(names.length > 0 ? names.map((name) => `- ${name}`).join("\n") : "- (none)");
@@ -99,13 +130,20 @@ function buildTaskBoard(index) {
99
130
  fullLines.push("## active sessions");
100
131
  fullLines.push(active.map((line) => `- ${line}`).join("\n"));
101
132
  }
133
+ const activeBridges = activeBridgeLines(index.tasks);
134
+ if (activeBridges.length > 0) {
135
+ fullLines.push("## active bridges");
136
+ fullLines.push(activeBridges.map((line) => `- ${line}`).join("\n"));
137
+ }
102
138
  return {
103
139
  compact,
104
140
  full: fullLines.join("\n\n"),
105
141
  byStatus,
142
+ issues: index.issues,
106
143
  actionRequired: actionRequired(index, byStatus),
107
144
  unresolvedDependencies: unresolved,
108
145
  activeSessions: active,
146
+ activeBridges,
109
147
  };
110
148
  }
111
149
  function boardStatus(board, status) {
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.addKindToLegacyCard = addKindToLegacyCard;
37
+ exports.applyFixes = applyFixes;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ const scanner_1 = require("./scanner");
42
+ const parser_1 = require("./parser");
43
+ function issueId(issue) {
44
+ return `${issue.code}:${issue.target}`;
45
+ }
46
+ function addKindToLegacyCard(root, issue) {
47
+ (0, runtime_1.emitNervesEvent)({
48
+ event: "repertoire.fix_apply_start",
49
+ component: "repertoire",
50
+ message: "adding kind: task to legacy card",
51
+ meta: { target: issue.target },
52
+ });
53
+ const filePath = path.join(root, issue.target);
54
+ if (!fs.existsSync(filePath))
55
+ return false;
56
+ const content = fs.readFileSync(filePath, "utf-8");
57
+ const frontmatter = (0, scanner_1.tryExtractFrontmatter)(content);
58
+ if (!frontmatter)
59
+ return false;
60
+ // Add kind: task to frontmatter
61
+ frontmatter.kind = "task";
62
+ // Extract body (everything after the second ---)
63
+ const lines = content.split(/\r?\n/);
64
+ const firstDelim = lines.findIndex((line) => line.trim() === "---");
65
+ const secondDelim = lines.findIndex((line, idx) => idx > firstDelim && line.trim() === "---");
66
+ const body = lines.slice(secondDelim + 1).join("\n").replace(/^\n/, "");
67
+ const rendered = (0, parser_1.renderTaskFile)(frontmatter, body);
68
+ fs.writeFileSync(filePath, rendered, "utf-8");
69
+ return true;
70
+ }
71
+ function applySingleFix(root, issue) {
72
+ switch (issue.code) {
73
+ case "schema-missing-kind":
74
+ return addKindToLegacyCard(root, issue);
75
+ default:
76
+ return false;
77
+ }
78
+ }
79
+ function applyFixes(options, root) {
80
+ (0, runtime_1.emitNervesEvent)({
81
+ event: "repertoire.fix_start",
82
+ component: "repertoire",
83
+ message: `applying fixes in ${options.mode} mode`,
84
+ meta: { mode: options.mode, root },
85
+ });
86
+ (0, scanner_1.clearTaskScanCache)();
87
+ const index = (0, scanner_1.scanTasks)(root);
88
+ const issues = index.issues;
89
+ if (options.mode === "dry-run") {
90
+ (0, runtime_1.emitNervesEvent)({
91
+ event: "repertoire.fix_complete",
92
+ component: "repertoire",
93
+ message: "dry-run complete, no changes made",
94
+ meta: { issueCount: issues.length },
95
+ });
96
+ const health = issues.length === 0 ? "clean" : buildHealthSummary(issues);
97
+ return {
98
+ applied: [],
99
+ remaining: issues,
100
+ skipped: [],
101
+ health,
102
+ };
103
+ }
104
+ if (options.mode === "single") {
105
+ const targetId = options.issueId ?? "";
106
+ const match = issues.find((i) => issueId(i) === targetId);
107
+ if (!match) {
108
+ (0, runtime_1.emitNervesEvent)({
109
+ event: "repertoire.fix_complete",
110
+ component: "repertoire",
111
+ message: "single fix: issue not found",
112
+ meta: { issueId: targetId },
113
+ });
114
+ const health = issues.length === 0 ? "clean" : buildHealthSummary(issues);
115
+ return {
116
+ applied: [],
117
+ remaining: issues,
118
+ skipped: [],
119
+ health,
120
+ };
121
+ }
122
+ const success = applySingleFix(root, match);
123
+ (0, scanner_1.clearTaskScanCache)();
124
+ const afterIndex = (0, scanner_1.scanTasks)(root);
125
+ const afterIssues = afterIndex.issues;
126
+ const health = afterIssues.length === 0 ? "clean" : buildHealthSummary(afterIssues);
127
+ (0, runtime_1.emitNervesEvent)({
128
+ event: "repertoire.fix_complete",
129
+ component: "repertoire",
130
+ message: `single fix ${success ? "applied" : "failed"}`,
131
+ meta: { issueId: targetId, success },
132
+ });
133
+ return {
134
+ applied: success ? [match] : [],
135
+ remaining: afterIssues,
136
+ skipped: success ? [] : [match],
137
+ health,
138
+ };
139
+ }
140
+ // safe mode: apply all safe-confidence fixes
141
+ const applied = [];
142
+ const skipped = [];
143
+ for (const issue of issues) {
144
+ if (issue.confidence !== "safe") {
145
+ skipped.push(issue);
146
+ continue;
147
+ }
148
+ const success = applySingleFix(root, issue);
149
+ if (success) {
150
+ applied.push(issue);
151
+ }
152
+ else {
153
+ skipped.push(issue);
154
+ }
155
+ }
156
+ (0, scanner_1.clearTaskScanCache)();
157
+ const afterIndex = (0, scanner_1.scanTasks)(root);
158
+ const afterIssues = afterIndex.issues;
159
+ const health = afterIssues.length === 0 ? "clean" : buildHealthSummary(afterIssues);
160
+ (0, runtime_1.emitNervesEvent)({
161
+ event: "repertoire.fix_complete",
162
+ component: "repertoire",
163
+ message: `safe fixes complete: ${applied.length} applied, ${skipped.length} skipped`,
164
+ meta: { applied: applied.length, skipped: skipped.length },
165
+ });
166
+ return {
167
+ applied,
168
+ remaining: afterIssues,
169
+ skipped,
170
+ health,
171
+ };
172
+ }
173
+ function buildHealthSummary(issues) {
174
+ const liveCount = issues.filter((i) => i.category === "live").length;
175
+ const migrationCount = issues.filter((i) => i.category === "migration").length;
176
+ const parts = [];
177
+ if (liveCount > 0)
178
+ parts.push(`${liveCount} live`);
179
+ if (migrationCount > 0)
180
+ parts.push(`${migrationCount} migration`);
181
+ return parts.join(", ");
182
+ }
@@ -37,8 +37,10 @@ exports.getTaskModule = getTaskModule;
37
37
  exports.resetTaskModule = resetTaskModule;
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
+ const config_1 = require("../../heart/config");
40
41
  const runtime_1 = require("../../nerves/runtime");
41
42
  const board_1 = require("./board");
43
+ const fix_1 = require("./fix");
42
44
  const lifecycle_1 = require("./lifecycle");
43
45
  const parser_1 = require("./parser");
44
46
  const scanner_1 = require("./scanner");
@@ -55,14 +57,6 @@ function formatStemTimestamp(now = new Date()) {
55
57
  const minutes = String(now.getMinutes()).padStart(2, "0");
56
58
  return `${year}-${month}-${day}-${hours}${minutes}`;
57
59
  }
58
- function slugify(input) {
59
- return input
60
- .toLowerCase()
61
- .replace(/[^a-z0-9]+/g, "-")
62
- .replace(/^-+/, "")
63
- .replace(/-+$/, "")
64
- .slice(0, 64);
65
- }
66
60
  function findTask(index, nameOrStem) {
67
61
  return (index.tasks.find((task) => task.stem === nameOrStem || task.name === nameOrStem) ??
68
62
  index.tasks.find((task) => task.stem.endsWith(nameOrStem)) ??
@@ -98,12 +92,13 @@ class FileTaskModule {
98
92
  throw new Error(`invalid task status: ${String(input.status)}`);
99
93
  }
100
94
  const collection = (0, transitions_1.canonicalCollectionForTaskType)(type);
101
- const stem = `${formatStemTimestamp()}-${slugify(input.title) || "task"}`;
95
+ const stem = `${formatStemTimestamp()}-${(0, config_1.slugify)(input.title).slice(0, 64) || "task"}`;
102
96
  const filename = `${stem}.md`;
103
97
  const root = (0, scanner_1.getTaskRoot)();
104
98
  const filePath = path.join(root, collection, filename);
105
99
  const today = formatDate();
106
100
  const frontmatter = {
101
+ kind: "task",
107
102
  type,
108
103
  category: input.category || "infrastructure",
109
104
  title: input.title,
@@ -115,13 +110,18 @@ class FileTaskModule {
115
110
  lastRun: input.lastRun ?? null,
116
111
  created: today,
117
112
  updated: today,
118
- child_tasks: [],
119
113
  artifacts: [],
120
114
  };
121
115
  if (type === "one-shot") {
122
116
  frontmatter.parent_task = null;
123
117
  frontmatter.depends_on = [];
124
118
  }
119
+ if (input.activeBridge && input.activeBridge.trim()) {
120
+ frontmatter.active_bridge = input.activeBridge.trim();
121
+ }
122
+ if (Array.isArray(input.bridgeSessions) && input.bridgeSessions.length > 0) {
123
+ frontmatter.bridge_sessions = input.bridgeSessions.filter((value) => typeof value === "string" && value.trim());
124
+ }
125
125
  const content = (0, parser_1.renderTaskFile)(frontmatter, input.body);
126
126
  const validation = (0, middleware_1.validateWrite)(filePath, content);
127
127
  if (!validation.ok) {
@@ -132,6 +132,21 @@ class FileTaskModule {
132
132
  (0, scanner_1.clearTaskScanCache)();
133
133
  return filePath;
134
134
  }
135
+ bindBridge(name, input) {
136
+ const task = this.getTask(name);
137
+ if (!task) {
138
+ return { ok: false, reason: `task not found: ${name}` };
139
+ }
140
+ const content = fs.readFileSync(task.path, "utf-8");
141
+ const parsed = (0, parser_1.parseTaskFile)(content, task.path);
142
+ const frontmatter = removeRuntimeFrontmatter(parsed.frontmatter);
143
+ frontmatter.active_bridge = input.bridgeId.trim();
144
+ frontmatter.bridge_sessions = input.sessionRefs.filter((value) => value.trim().length > 0);
145
+ frontmatter.updated = formatDate();
146
+ fs.writeFileSync(task.path, (0, parser_1.renderTaskFile)(frontmatter, parsed.body), "utf-8");
147
+ (0, scanner_1.clearTaskScanCache)();
148
+ return { ok: true, path: task.path };
149
+ }
135
150
  updateStatus(name, toStatus) {
136
151
  const normalized = (0, transitions_1.normalizeTaskStatus)(toStatus);
137
152
  if (!normalized) {
@@ -178,6 +193,9 @@ class FileTaskModule {
178
193
  }
179
194
  return (0, middleware_1.validateSpawn)(task, spawnType);
180
195
  }
196
+ fix(options) {
197
+ return (0, fix_1.applyFixes)(options, (0, scanner_1.getTaskRoot)());
198
+ }
181
199
  detectStale(thresholdDays) {
182
200
  return (0, lifecycle_1.detectStaleTasks)(this.scan(), thresholdDays);
183
201
  }
@@ -48,7 +48,7 @@ function archiveCompletedTasks(index) {
48
48
  const archived = [];
49
49
  const failures = [];
50
50
  for (const task of index.tasks) {
51
- if (task.status !== "done")
51
+ if (task.status !== "done" && task.status !== "cancelled")
52
52
  continue;
53
53
  const archiveDir = path.join(index.root, "archive", task.collection);
54
54
  const archiveFile = path.join(archiveDir, task.name);
@@ -73,7 +73,7 @@ function detectStaleTasks(index, thresholdDays, now = new Date()) {
73
73
  const updated = Date.parse(task.updated);
74
74
  if (Number.isNaN(updated))
75
75
  return false;
76
- if (task.status === "done")
76
+ if (task.status === "done" || task.status === "cancelled")
77
77
  return false;
78
78
  return updated < staleCutoffMs;
79
79
  });