@ouro.bot/cli 0.1.0-alpha.13 → 0.1.0-alpha.130

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 (126) hide show
  1. package/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
  2. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  3. package/README.md +147 -205
  4. package/changelog.json +808 -0
  5. package/dist/heart/active-work.js +622 -0
  6. package/dist/heart/bridges/manager.js +358 -0
  7. package/dist/heart/bridges/state-machine.js +135 -0
  8. package/dist/heart/bridges/store.js +123 -0
  9. package/dist/heart/commitments.js +105 -0
  10. package/dist/heart/config.js +66 -21
  11. package/dist/heart/core.js +518 -100
  12. package/dist/heart/cross-chat-delivery.js +146 -0
  13. package/dist/heart/daemon/agent-discovery.js +81 -0
  14. package/dist/heart/daemon/auth-flow.js +432 -0
  15. package/dist/heart/daemon/daemon-cli.js +1516 -195
  16. package/dist/heart/daemon/daemon-entry.js +43 -2
  17. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  18. package/dist/heart/daemon/daemon.js +261 -1
  19. package/dist/heart/daemon/hatch-animation.js +10 -3
  20. package/dist/heart/daemon/hatch-flow.js +7 -72
  21. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  22. package/dist/heart/daemon/launchd.js +159 -0
  23. package/dist/heart/daemon/log-tailer.js +4 -3
  24. package/dist/heart/daemon/message-router.js +17 -8
  25. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  26. package/dist/heart/daemon/ouro-path-installer.js +57 -29
  27. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  28. package/dist/heart/daemon/process-manager.js +13 -0
  29. package/dist/heart/daemon/run-hooks.js +37 -0
  30. package/dist/heart/daemon/runtime-logging.js +58 -15
  31. package/dist/heart/daemon/runtime-metadata.js +219 -0
  32. package/dist/heart/daemon/runtime-mode.js +67 -0
  33. package/dist/heart/daemon/sense-manager.js +50 -2
  34. package/dist/heart/daemon/skill-management-installer.js +94 -0
  35. package/dist/heart/daemon/socket-client.js +202 -0
  36. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  37. package/dist/heart/daemon/specialist-prompt.js +7 -4
  38. package/dist/heart/daemon/specialist-tools.js +52 -3
  39. package/dist/heart/daemon/staged-restart.js +114 -0
  40. package/dist/heart/daemon/thoughts.js +507 -0
  41. package/dist/heart/daemon/update-checker.js +111 -0
  42. package/dist/heart/daemon/update-hooks.js +138 -0
  43. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  44. package/dist/heart/delegation.js +62 -0
  45. package/dist/heart/identity.js +64 -21
  46. package/dist/heart/kicks.js +1 -19
  47. package/dist/heart/model-capabilities.js +48 -0
  48. package/dist/heart/obligations.js +197 -0
  49. package/dist/heart/progress-story.js +42 -0
  50. package/dist/heart/provider-failover.js +88 -0
  51. package/dist/heart/provider-ping.js +159 -0
  52. package/dist/heart/providers/anthropic-token.js +163 -0
  53. package/dist/heart/providers/anthropic.js +195 -34
  54. package/dist/heart/providers/azure.js +115 -9
  55. package/dist/heart/providers/github-copilot.js +157 -0
  56. package/dist/heart/providers/minimax.js +33 -3
  57. package/dist/heart/providers/openai-codex.js +49 -14
  58. package/dist/heart/safe-workspace.js +381 -0
  59. package/dist/heart/session-activity.js +173 -0
  60. package/dist/heart/session-recall.js +216 -0
  61. package/dist/heart/streaming.js +108 -24
  62. package/dist/heart/target-resolution.js +123 -0
  63. package/dist/heart/tool-loop.js +194 -0
  64. package/dist/heart/turn-coordinator.js +28 -0
  65. package/dist/mind/associative-recall.js +14 -2
  66. package/dist/mind/bundle-manifest.js +12 -0
  67. package/dist/mind/context.js +60 -14
  68. package/dist/mind/first-impressions.js +16 -2
  69. package/dist/mind/friends/channel.js +35 -0
  70. package/dist/mind/friends/group-context.js +144 -0
  71. package/dist/mind/friends/store-file.js +19 -0
  72. package/dist/mind/friends/trust-explanation.js +74 -0
  73. package/dist/mind/friends/types.js +8 -0
  74. package/dist/mind/memory.js +27 -26
  75. package/dist/mind/obligation-steering.js +221 -0
  76. package/dist/mind/pending.js +76 -9
  77. package/dist/mind/phrases.js +1 -0
  78. package/dist/mind/prompt.js +456 -77
  79. package/dist/mind/token-estimate.js +8 -12
  80. package/dist/nerves/cli-logging.js +15 -2
  81. package/dist/nerves/coverage/run-artifacts.js +1 -1
  82. package/dist/nerves/index.js +12 -0
  83. package/dist/nerves/runtime.js +5 -1
  84. package/dist/repertoire/ado-client.js +4 -2
  85. package/dist/repertoire/coding/context-pack.js +254 -0
  86. package/dist/repertoire/coding/feedback.js +301 -0
  87. package/dist/repertoire/coding/index.js +4 -1
  88. package/dist/repertoire/coding/manager.js +210 -4
  89. package/dist/repertoire/coding/spawner.js +39 -9
  90. package/dist/repertoire/coding/tools.js +171 -4
  91. package/dist/repertoire/data/ado-endpoints.json +188 -0
  92. package/dist/repertoire/guardrails.js +290 -0
  93. package/dist/repertoire/mcp-client.js +254 -0
  94. package/dist/repertoire/mcp-manager.js +198 -0
  95. package/dist/repertoire/skills.js +3 -26
  96. package/dist/repertoire/tasks/board.js +12 -0
  97. package/dist/repertoire/tasks/index.js +23 -9
  98. package/dist/repertoire/tasks/transitions.js +1 -2
  99. package/dist/repertoire/tools-base.js +925 -250
  100. package/dist/repertoire/tools-bluebubbles.js +93 -0
  101. package/dist/repertoire/tools-teams.js +58 -25
  102. package/dist/repertoire/tools.js +106 -53
  103. package/dist/senses/bluebubbles-client.js +210 -5
  104. package/dist/senses/bluebubbles-entry.js +2 -0
  105. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  106. package/dist/senses/bluebubbles-media.js +339 -0
  107. package/dist/senses/bluebubbles-model.js +12 -4
  108. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  109. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  110. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  111. package/dist/senses/bluebubbles.js +915 -45
  112. package/dist/senses/cli-layout.js +187 -0
  113. package/dist/senses/cli.js +374 -131
  114. package/dist/senses/continuity.js +94 -0
  115. package/dist/senses/debug-activity.js +154 -0
  116. package/dist/senses/inner-dialog-worker.js +47 -18
  117. package/dist/senses/inner-dialog.js +388 -83
  118. package/dist/senses/pipeline.js +444 -0
  119. package/dist/senses/teams.js +607 -129
  120. package/dist/senses/trust-gate.js +112 -2
  121. package/package.json +9 -3
  122. package/subagents/README.md +4 -70
  123. package/dist/heart/daemon/subagent-installer.js +0 -134
  124. package/subagents/work-doer.md +0 -233
  125. package/subagents/work-merger.md +0 -624
  126. package/subagents/work-planner.md +0 -373
@@ -39,6 +39,9 @@ const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const identity_1 = require("../heart/identity");
41
41
  const runtime_1 = require("../nerves/runtime");
42
+ const types_1 = require("../mind/friends/types");
43
+ const pending_1 = require("../mind/pending");
44
+ // Canned reply; eventually agents should compose their own first-contact message
42
45
  exports.STRANGER_AUTO_REPLY = "I'm sorry, I'm not allowed to talk to strangers";
43
46
  function buildExternalKey(provider, externalId, tenantId) {
44
47
  return `${provider}:${tenantId ?? ""}:${externalId}`;
@@ -77,16 +80,107 @@ function appendPrimaryNotification(bundleRoot, provider, externalId, tenantId, n
77
80
  fs.mkdirSync(inboxDir, { recursive: true });
78
81
  fs.appendFileSync(notificationsPath, `${JSON.stringify(payload)}\n`, "utf8");
79
82
  }
83
+ function writeInnerPendingNotice(bundleRoot, noticeContent, nowIso) {
84
+ const innerPendingDir = path.join(bundleRoot, "state", "pending", pending_1.INNER_DIALOG_PENDING.friendId, pending_1.INNER_DIALOG_PENDING.channel, pending_1.INNER_DIALOG_PENDING.key);
85
+ const fileName = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
86
+ const filePath = path.join(innerPendingDir, fileName);
87
+ const payload = {
88
+ from: "instinct",
89
+ content: noticeContent,
90
+ timestamp: Date.now(),
91
+ at: nowIso,
92
+ };
93
+ fs.mkdirSync(innerPendingDir, { recursive: true });
94
+ fs.writeFileSync(filePath, JSON.stringify(payload), "utf-8");
95
+ }
80
96
  function enforceTrustGate(input) {
97
+ const { senseType } = input;
98
+ // Local (CLI) and internal (inner dialog) — always allow
99
+ if (senseType === "local" || senseType === "internal") {
100
+ return { allowed: true };
101
+ }
102
+ // Closed senses (Teams) — org already gates access, allow all trust levels
103
+ if (senseType === "closed") {
104
+ return { allowed: true };
105
+ }
106
+ // Open senses (BlueBubbles/iMessage) — enforce trust rules
81
107
  const trustLevel = input.friend.trustLevel ?? "friend";
82
- if (trustLevel !== "stranger") {
108
+ // Family and friend — always allow on open
109
+ if ((0, types_1.isTrustedLevel)(trustLevel)) {
83
110
  return { allowed: true };
84
111
  }
85
112
  const bundleRoot = input.bundleRoot ?? (0, identity_1.getAgentRoot)();
86
- const repliesPath = path.join(bundleRoot, "stranger-replies.json");
87
113
  const nowIso = (input.now ?? (() => new Date()))().toISOString();
114
+ // Acquaintance rules
115
+ if (trustLevel === "acquaintance") {
116
+ return handleAcquaintance(input, bundleRoot, nowIso);
117
+ }
118
+ // Stranger rules (trustLevel === "stranger")
119
+ return handleStranger(input, bundleRoot, nowIso);
120
+ }
121
+ function handleAcquaintance(input, bundleRoot, nowIso) {
122
+ const { isGroupChat, groupHasFamilyMember, hasExistingGroupWithFamily } = input;
123
+ // Group chat with family member present — allow
124
+ if (isGroupChat && groupHasFamilyMember) {
125
+ return { allowed: true };
126
+ }
127
+ let result;
128
+ let noticeDetail;
129
+ if (isGroupChat) {
130
+ // Group chat without family member — reject silently
131
+ result = { allowed: false, reason: "acquaintance_group_no_family" };
132
+ noticeDetail = `acquaintance "${input.friend.name}" messaged in a group chat without a family member present`;
133
+ }
134
+ else if (hasExistingGroupWithFamily) {
135
+ // 1:1 but shares a group with family — redirect
136
+ result = {
137
+ allowed: false,
138
+ reason: "acquaintance_1on1_has_group",
139
+ autoReply: "Hey! Reach me in our group chat instead.",
140
+ };
141
+ noticeDetail = `acquaintance "${input.friend.name}" DMed me directly — redirected to our group chat`;
142
+ }
143
+ else {
144
+ // 1:1, no shared group with family — redirect to any group
145
+ result = {
146
+ allowed: false,
147
+ reason: "acquaintance_1on1_no_group",
148
+ autoReply: "Hey! Reach me in a group chat instead.",
149
+ };
150
+ noticeDetail = `acquaintance "${input.friend.name}" DMed me directly — asked to reach me in a group chat`;
151
+ }
152
+ (0, runtime_1.emitNervesEvent)({
153
+ level: "warn",
154
+ component: "senses",
155
+ event: "senses.trust_gate",
156
+ message: "acquaintance message blocked",
157
+ meta: {
158
+ channel: input.channel,
159
+ provider: input.provider,
160
+ reason: result.reason,
161
+ },
162
+ });
163
+ try {
164
+ writeInnerPendingNotice(bundleRoot, noticeDetail, nowIso);
165
+ }
166
+ catch (error) {
167
+ (0, runtime_1.emitNervesEvent)({
168
+ level: "error",
169
+ component: "senses",
170
+ event: "senses.trust_gate_error",
171
+ message: "failed to write inner pending notice",
172
+ meta: {
173
+ reason: error instanceof Error ? error.message : String(error),
174
+ },
175
+ });
176
+ }
177
+ return result;
178
+ }
179
+ function handleStranger(input, bundleRoot, nowIso) {
180
+ const repliesPath = path.join(bundleRoot, "stranger-replies.json");
88
181
  const externalKey = buildExternalKey(input.provider, input.externalId, input.tenantId);
89
182
  const state = loadRepliesState(repliesPath);
183
+ // Subsequent contact — silent drop
90
184
  if (state[externalKey]) {
91
185
  (0, runtime_1.emitNervesEvent)({
92
186
  level: "warn",
@@ -103,6 +197,7 @@ function enforceTrustGate(input) {
103
197
  reason: "stranger_silent_drop",
104
198
  };
105
199
  }
200
+ // First contact — auto-reply, persist state, notify agent
106
201
  state[externalKey] = nowIso;
107
202
  try {
108
203
  persistRepliesState(repliesPath, state);
@@ -132,6 +227,21 @@ function enforceTrustGate(input) {
132
227
  },
133
228
  });
134
229
  }
230
+ const noticeDetail = `stranger "${input.friend.name}" tried to reach me via ${input.channel}. Auto-replied once.`;
231
+ try {
232
+ writeInnerPendingNotice(bundleRoot, noticeDetail, nowIso);
233
+ }
234
+ catch (error) {
235
+ (0, runtime_1.emitNervesEvent)({
236
+ level: "error",
237
+ component: "senses",
238
+ event: "senses.trust_gate_error",
239
+ message: "failed to write inner pending notice",
240
+ meta: {
241
+ reason: error instanceof Error ? error.message : String(error),
242
+ },
243
+ });
244
+ }
135
245
  (0, runtime_1.emitNervesEvent)({
136
246
  level: "warn",
137
247
  component: "senses",
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.13",
3
+ "version": "0.1.0-alpha.130",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
+ "cli": "dist/heart/daemon/ouro-bot-entry.js",
6
7
  "ouro": "dist/heart/daemon/ouro-entry.js",
7
8
  "ouro.bot": "dist/heart/daemon/ouro-bot-entry.js"
8
9
  },
@@ -10,7 +11,8 @@
10
11
  "dist/",
11
12
  "AdoptionSpecialist.ouro/",
12
13
  "subagents/",
13
- "assets/"
14
+ "assets/",
15
+ "changelog.json"
14
16
  ],
15
17
  "exports": {
16
18
  ".": "./dist/heart/daemon/daemon-cli.js",
@@ -31,15 +33,19 @@
31
33
  },
32
34
  "dependencies": {
33
35
  "@anthropic-ai/sdk": "^0.78.0",
36
+ "@azure/identity": "^4.13.0",
34
37
  "@microsoft/teams.apps": "^2.0.5",
35
38
  "@microsoft/teams.dev": "^2.0.5",
36
- "openai": "^6.27.0"
39
+ "fast-glob": "^3.3.3",
40
+ "openai": "^6.27.0",
41
+ "semver": "^7.7.4"
37
42
  },
38
43
  "repository": {
39
44
  "type": "git",
40
45
  "url": "https://github.com/ouroborosbot/ouroboros"
41
46
  },
42
47
  "devDependencies": {
48
+ "@types/semver": "^7.7.1",
43
49
  "@vitest/coverage-v8": "^4.0.18",
44
50
  "eslint": "^10.0.2",
45
51
  "typescript": "^5.7.0",
@@ -1,73 +1,7 @@
1
- # Sub-agents
1
+ # Workflow Skills (Moved)
2
2
 
3
- These are source-of-truth workflow definitions (`work-planner`, `work-doer`, `work-merger`) for planning, execution, and merge. They can be consumed either as Claude sub-agents (`.md` files with YAML frontmatter) or as Codex-style skills (`SKILL.md`).
3
+ The workflow skills (`work-planner`, `work-doer`, `work-merger`) have moved to the shared skills repository:
4
4
 
5
- ## Installation
5
+ **https://github.com/ouroborosbot/ouroboros-skills**
6
6
 
7
- ### Claude Code (sub-agents)
8
-
9
- Copy or symlink these files into Claude's sub-agent directory:
10
-
11
- ```bash
12
- # Claude Code
13
- cp subagents/*.md ~/.claude/agents/
14
- # or
15
- ln -s "$(pwd)"/subagents/*.md ~/.claude/agents/
16
- ```
17
-
18
- ### Codex / skill-based harnesses
19
-
20
- For tools that support skills but not Claude sub-agents, install these as skills:
21
-
22
- ```bash
23
- mkdir -p ~/.codex/skills/work-planner ~/.codex/skills/work-doer ~/.codex/skills/work-merger
24
-
25
- # Hard-link to keep one source of truth
26
- ln -f "$(pwd)/subagents/work-planner.md" ~/.codex/skills/work-planner/SKILL.md
27
- ln -f "$(pwd)/subagents/work-doer.md" ~/.codex/skills/work-doer/SKILL.md
28
- ln -f "$(pwd)/subagents/work-merger.md" ~/.codex/skills/work-merger/SKILL.md
29
- ```
30
-
31
- **Important:** Hard-links break when editors save by replacing the file (new inode). After editing any `subagents/*.md` file, re-run the `ln -f` command for that file to restore the link. You can verify with `stat -f '%i'` — both files should share the same inode.
32
-
33
- Optional UI metadata:
34
-
35
- ```bash
36
- mkdir -p ~/.codex/skills/work-planner/agents ~/.codex/skills/work-doer/agents ~/.codex/skills/work-merger/agents
37
- cat > ~/.codex/skills/work-planner/agents/openai.yaml << 'EOF'
38
- interface:
39
- display_name: "Work Planner"
40
- short_description: "Create and gate planning/doing task docs"
41
- default_prompt: "Use $work-planner to create or update a planning doc, then stop at NEEDS_REVIEW."
42
- EOF
43
- cat > ~/.codex/skills/work-doer/agents/openai.yaml << 'EOF'
44
- interface:
45
- display_name: "Work Doer"
46
- short_description: "Execute approved doing docs with strict TDD"
47
- default_prompt: "Use $work-doer to execute an approved doing doc unit by unit."
48
- EOF
49
- cat > ~/.codex/skills/work-merger/agents/openai.yaml << 'EOF'
50
- interface:
51
- display_name: "Work Merger"
52
- short_description: "Merge feature branch into main via PR after work-doer completes"
53
- default_prompt: "Use $work-merger to merge the current feature branch into main."
54
- EOF
55
- ```
56
-
57
- Restart the harness after install so new skills are discovered.
58
-
59
- ## Available sub-agents
60
-
61
- | File | Purpose |
62
- |------|---------|
63
- | `work-planner.md` | Interactive task planner. Generates planning docs through conversation, then converts to doing docs after human approval. |
64
- | `work-doer.md` | Task executor. Reads a doing doc and works through each unit sequentially with strict TDD. |
65
- | `work-merger.md` | Sync-and-merge agent. Merges feature branch into main via PR after work-doer completes. Handles conflicts, CI failures, and race conditions. |
66
-
67
- ## Workflow
68
-
69
- 1. Human describes a task
70
- 2. Agent invokes **work-planner** to create a planning doc → human approves → planner converts to doing doc
71
- 3. Agent invokes **work-doer** to execute the doing doc unit by unit
72
- 4. Each unit is committed independently with progress tracked in the doing doc
73
- 5. Agent invokes **work-merger** to merge the feature branch into main via PR (fetch, merge, resolve conflicts, CI gate, merge PR, cleanup)
7
+ To install or update these skills, use the **skill-management** skill from that repo. It covers browsing available skills, installing them into your agent's local skills directory, and keeping them up to date.
@@ -1,134 +0,0 @@
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.installSubagentsForAvailableCli = installSubagentsForAvailableCli;
37
- const fs = __importStar(require("fs"));
38
- const os = __importStar(require("os"));
39
- const path = __importStar(require("path"));
40
- const child_process_1 = require("child_process");
41
- const runtime_1 = require("../../nerves/runtime");
42
- function detectCliBinary(binary) {
43
- const result = (0, child_process_1.spawnSync)("which", [binary], { encoding: "utf-8" });
44
- if (result.status !== 0)
45
- return null;
46
- const resolved = result.stdout.trim();
47
- return resolved.length > 0 ? resolved : null;
48
- }
49
- function listSubagentSources(subagentsDir) {
50
- if (!fs.existsSync(subagentsDir))
51
- return [];
52
- return fs.readdirSync(subagentsDir)
53
- .filter((name) => name.endsWith(".md"))
54
- .filter((name) => name !== "README.md")
55
- .map((name) => path.join(subagentsDir, name))
56
- .sort((a, b) => a.localeCompare(b));
57
- }
58
- function pathExists(target) {
59
- try {
60
- fs.lstatSync(target);
61
- return true;
62
- }
63
- catch {
64
- return false;
65
- }
66
- }
67
- function ensureSymlink(source, target) {
68
- fs.mkdirSync(path.dirname(target), { recursive: true });
69
- if (pathExists(target)) {
70
- const stats = fs.lstatSync(target);
71
- if (stats.isSymbolicLink()) {
72
- const linkedPath = fs.readlinkSync(target);
73
- if (linkedPath === source)
74
- return false;
75
- }
76
- fs.unlinkSync(target);
77
- }
78
- fs.symlinkSync(source, target);
79
- return true;
80
- }
81
- async function installSubagentsForAvailableCli(options = {}) {
82
- const repoRoot = options.repoRoot ?? path.resolve(__dirname, "..", "..", "..");
83
- const homeDir = options.homeDir ?? os.homedir();
84
- const which = options.which ?? detectCliBinary;
85
- const subagentsDir = path.join(repoRoot, "subagents");
86
- const sources = listSubagentSources(subagentsDir);
87
- const notes = [];
88
- (0, runtime_1.emitNervesEvent)({
89
- component: "daemon",
90
- event: "daemon.subagent_install_start",
91
- message: "starting subagent auto-install",
92
- meta: { sources: sources.length },
93
- });
94
- if (sources.length === 0) {
95
- notes.push(`no subagent files found at ${subagentsDir}`);
96
- return { claudeInstalled: 0, codexInstalled: 0, notes };
97
- }
98
- let claudeInstalled = 0;
99
- let codexInstalled = 0;
100
- const claudePath = which("claude");
101
- if (!claudePath) {
102
- notes.push("claude CLI not found; skipping subagent install");
103
- }
104
- else {
105
- const claudeAgentsDir = path.join(homeDir, ".claude", "agents");
106
- for (const source of sources) {
107
- const target = path.join(claudeAgentsDir, path.basename(source));
108
- if (ensureSymlink(source, target)) {
109
- claudeInstalled += 1;
110
- }
111
- }
112
- }
113
- const codexPath = which("codex");
114
- if (!codexPath) {
115
- notes.push("codex CLI not found; skipping subagent install");
116
- }
117
- else {
118
- const codexSkillsDir = path.join(homeDir, ".codex", "skills");
119
- for (const source of sources) {
120
- const skillName = path.basename(source, ".md");
121
- const target = path.join(codexSkillsDir, skillName, "SKILL.md");
122
- if (ensureSymlink(source, target)) {
123
- codexInstalled += 1;
124
- }
125
- }
126
- }
127
- (0, runtime_1.emitNervesEvent)({
128
- component: "daemon",
129
- event: "daemon.subagent_install_end",
130
- message: "completed subagent auto-install",
131
- meta: { claudeInstalled, codexInstalled, notes: notes.length },
132
- });
133
- return { claudeInstalled, codexInstalled, notes };
134
- }
@@ -1,233 +0,0 @@
1
- ---
2
- name: work-doer
3
- description: Executes doing.md units sequentially with strict TDD. Reads the doing doc, works through each unit, commits after each. Use after planning is complete and doing.md exists.
4
- model: opus
5
- ---
6
-
7
- You are a task executor. Read a doing.md file and execute all units sequentially until complete or blocked.
8
-
9
- ## On Startup
10
-
11
- 1. **Find doing doc**: Look for `YYYY-MM-DD-HHMM-doing-*.md` in repo root
12
- 2. If multiple found, ask which one
13
- 3. If none found, ask user for location
14
- 4. **Check execution_mode**: Read the doing doc's `Execution Mode` field
15
- 5. **Verify artifacts directory exists**: `{task-name}/` next to `{task-name}.md`
16
- - If missing, create it: `mkdir {task-name}`
17
- 6. **Detect resume vs fresh start:**
18
- - Count completed units (✅) vs total units
19
- - Check git status for uncommitted changes
20
-
21
- 7. **Announce status clearly:**
22
-
23
- **If fresh start (0 units complete):**
24
- ```
25
- found: YYYY-MM-DD-HHMM-doing-{name}.md
26
- execution_mode: [pending|spawn|direct]
27
- artifacts: ./{task-name}/
28
- status: fresh start
29
- units: 0/X complete
30
- starting Unit 0...
31
- ```
32
-
33
- **If resuming (some units complete):**
34
- ```
35
- found: YYYY-MM-DD-HHMM-doing-{name}.md
36
- execution_mode: [pending|spawn|direct]
37
- status: RESUMING
38
- units: Y/X complete (✅ Unit 0, 1a, 1b...)
39
- uncommitted changes: [yes/no]
40
- resuming from Unit Z...
41
- ```
42
-
43
- **If uncommitted changes detected:**
44
- ```
45
- ⚠️ uncommitted changes found
46
- recommend: commit or stash before continuing
47
- proceed anyway? (y/n)
48
- ```
49
-
50
- ---
51
-
52
- ## Timestamp & Commit Pattern
53
-
54
- **All timestamps come from git commits for audit trail.**
55
-
56
- To get timestamp for progress log entries:
57
- ```bash
58
- git log -1 --format="%Y-%m-%d %H:%M"
59
- ```
60
-
61
- After any edit to doing doc:
62
- 1. Stage: `git add doing-*.md`
63
- 2. Commit: `git commit -m "docs(doing): <what changed>"`
64
- 3. Get timestamp from git log
65
- 4. Use that timestamp in progress log entry
66
-
67
- ---
68
-
69
- ## Execution Loop
70
-
71
- For each unit in order:
72
-
73
- ### 1. Announce
74
- ```
75
- starting Unit Xa: [name]
76
- ```
77
-
78
- ### 2. Execute (TDD strictly enforced)
79
-
80
- **General execution rules:**
81
- - Save all outputs, logs, and data to `{task-name}/` artifacts directory
82
- - If execution_mode is `pending`, wait for user approval before starting each unit
83
- - If execution_mode is `spawn`, spawn a sub-agent for each unit
84
- - If execution_mode is `direct`, proceed immediately
85
-
86
- **For test units (Xa):**
87
- 1. Write failing tests for the feature
88
- 2. Run tests — **must FAIL (red)**
89
- 3. If tests pass immediately, something is wrong — investigate
90
- 4. Commit: `git commit -m "test(scope): Unit Xa - [description]"`
91
- 5. Push
92
-
93
- **For implementation units (Xb):**
94
- 1. Write minimal code to make tests pass
95
- 2. **Do NOT modify tests** — implementation must satisfy existing tests
96
- 3. Run tests — **must PASS (green)**
97
- 4. **Run the build** (e.g. `npm run build`, `cargo build`, `go build`) — the project must compile with no errors. Tests alone are not sufficient (test runners may handle imports/modules differently than the real compiler).
98
- 5. No warnings allowed
99
- 6. Commit: `git commit -m "feat(scope): Unit Xb - [description]"`
100
- 7. Push
101
-
102
- **For verify/refactor units (Xc):**
103
- 1. Run coverage report
104
- 2. **Must be 100% on new code** — if not, add tests
105
- 3. Check edge cases: null, empty, boundary values
106
- 4. Check all error paths tested
107
- 5. Refactor if needed, keep tests green
108
- 6. **Run the build** — verify the project compiles clean
109
- 7. Commit: `git commit -m "refactor(scope): Unit Xc - [description]"` (if changes made)
110
- 8. Push
111
-
112
- **For non-coding units:**
113
- 1. Complete work as described
114
- 2. Produce specified output
115
- 3. Verify acceptance criteria
116
- 4. Commit relevant files
117
- 5. Push
118
-
119
- ### 3. Update doing.md
120
- - Change unit status: `⬜` → `✅`
121
- - Update `Completion Criteria` checkboxes that are now satisfied by this unit's evidence
122
- - Commit: `git commit -m "docs(doing): complete Unit Xa"`
123
- - Get timestamp: `git log -1 --format="%Y-%m-%d %H:%M"`
124
- - Add progress log entry with that timestamp:
125
- ```
126
- - 2026-02-03 14:25 Unit Xa complete: [brief summary]
127
- ```
128
-
129
- ### 4. Context management
130
- - Run `/compact` between units if context growing large
131
- - Each unit should be independent
132
- - Re-read files if you need prior context
133
-
134
- ### 5. Continue to next unit
135
-
136
- ---
137
-
138
- ## Code Coverage Requirements
139
-
140
- **MANDATORY: 100% coverage on all new code.**
141
-
142
- Before marking any implementation unit complete:
143
- 1. Run coverage report
144
- 2. Verify 100% on new/modified files
145
- 3. No `[ExcludeFromCodeCoverage]` or equivalent on new code
146
- 4. All branches covered (if/else, switch, try/catch)
147
- 5. All error paths have tests
148
- 6. If coverage < 100%, add tests before proceeding
149
-
150
- ---
151
-
152
- ## TDD Requirements
153
-
154
- **Strict TDD — no exceptions:**
155
-
156
- 1. **Tests first**: Write failing tests BEFORE any implementation
157
- 2. **Red**: Run tests, confirm they FAIL
158
- 3. **Green**: Write minimal code to pass
159
- 4. **Refactor**: Clean up, tests stay green
160
- 5. **Never skip**: No implementation without failing test first
161
- 6. **Never modify tests to pass**: Implementation satisfies tests, not vice versa
162
-
163
- ---
164
-
165
- ## Blocker Handling
166
-
167
- **For simple fixes or test failures:**
168
- 1. **Spawn sub-agent immediately** — don't ask, just do it
169
- 2. Sub-agent analyzes error, fixes issue, commits, pushes
170
- 3. Sub-agent reports back when done
171
- 4. Continue with next unit
172
-
173
- **For actual blockers (requirements unclear, external dependency, design decision needed):**
174
- 1. Mark unit as `❌ Blocked` in doing.md
175
- 2. Commit: `git commit -m "docs(doing): Unit Xa blocked"`
176
- 3. Get timestamp from git
177
- 4. Add progress log entry with error details
178
- 5. Output:
179
- ```
180
- ❌ blocked on Unit Xa
181
- error: [description]
182
- tried: [what you attempted]
183
- need: [what would help]
184
- ```
185
- 6. **STOP** — do not proceed until user resolves
186
-
187
- **Rule of thumb:**
188
- - Code error / test failure → spawn sub-agent
189
- - Requirement unclear / need user input → mark blocked and stop
190
-
191
- ---
192
-
193
- ## Completion
194
-
195
- When all units are `✅`:
196
- 1. Run full test suite one final time
197
- 2. Verify all tests pass, no warnings
198
- 3. Mark all satisfied `Completion Criteria` checkboxes in doing doc as `[x]`
199
- 4. If `Planning:` doc path exists, sync its `Completion Criteria` checkboxes to `[x]` based on final evidence
200
- 5. Update doing.md Status to `done`
201
- 6. Commit: `git commit -m "docs(doing): all units complete"`
202
- 7. Get timestamp from git
203
- 8. Add final progress log entry
204
- 9. Output:
205
- ```
206
- ✅ all units complete
207
- tests: [X passing]
208
- coverage: [X%]
209
- status: done
210
- ```
211
-
212
- ---
213
-
214
- ## Rules
215
-
216
- 1. **File naming**: Expect `YYYY-MM-DD-HHMM-doing-{name}.md` format
217
- 2. **Artifacts directory**: Use `{task-name}/` for all outputs, logs, data
218
- 3. **Execution mode**: Honor `pending | spawn | direct` from doing doc
219
- 4. **TDD strictly enforced** — tests before implementation, always
220
- 5. **100% coverage** — no exceptions, no exclude attributes
221
- 6. **Atomic commits** — one logical unit per commit, push after each
222
- 7. **Timestamps from git** — `git log -1 --format="%Y-%m-%d %H:%M"`
223
- 8. **Push after each unit phase complete**
224
- 9. **Update doing.md after each unit** — status and progress log
225
- 10. **Spawn sub-agents for fixes** — don't ask, just do it
226
- 11. **Update docs immediately** — when decisions made, commit right away
227
- 12. **Stop on actual blocker** — unclear requirements or need user input
228
- 13. **/compact proactively** — preserve context between units
229
- 14. **No warnings** — treat warnings as errors
230
- 15. **Run full test suite** — before marking unit complete, not just new tests
231
- 16. **Always compile** — run the project's build command after every implementation/refactor unit. Tests passing is necessary but not sufficient.
232
- 17. **Checklist hygiene is mandatory** — keep doing/planning `Completion Criteria` checklists synchronized with verified completion evidence.
233
- 18. **Verify APIs before importing** — before writing `import { Foo } from './bar'`, use `grep` or `read_file` to confirm `Foo` is actually exported from that module. Never assume an export exists — always check the source first. This prevents wasted cycles on "module has no exported member" errors.