@monoes/monomindcli 2.16.3 → 2.16.4

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 (134) hide show
  1. package/.claude/commands/mastermind/createorg.md +2 -2
  2. package/.claude/commands/mastermind/help.md +5 -11
  3. package/.claude/commands/mastermind/master.md +1 -2
  4. package/.claude/commands/mastermind/orgs.md +1 -1
  5. package/.claude/commands/mastermind/orgstatus.md +1 -1
  6. package/.claude/commands/mastermind/runorg.md +2 -2
  7. package/.claude/commands/mastermind/stoporg.md +7 -11
  8. package/.claude/helpers/agent-registry.cjs +434 -0
  9. package/.claude/helpers/handlers/pick-core.cjs +20 -2
  10. package/.claude/helpers/handlers/session-restore-handler.cjs +4 -1
  11. package/.claude/helpers/jev-catalog.cjs +7 -3
  12. package/.claude/helpers/jev-picker.cjs +3 -1
  13. package/.claude/helpers/pick-rank.cjs +90 -4
  14. package/.claude/skills/mastermind-agents/SKILL.md +31 -38
  15. package/.claude/skills/mastermind-createorg/SKILL.md +3 -3
  16. package/.claude/skills/mastermind-env/SKILL.md +4 -18
  17. package/.claude/skills/mastermind-inbox/SKILL.md +31 -52
  18. package/.claude/skills/mastermind-instance/SKILL.md +7 -5
  19. package/.claude/skills/mastermind-new-agent/SKILL.md +3 -3
  20. package/.claude/skills/mastermind-org-settings/SKILL.md +3 -4
  21. package/.claude/skills/mastermind-orgs/SKILL.md +26 -32
  22. package/.claude/skills/mastermind-orgstatus/SKILL.md +49 -140
  23. package/.claude/skills/mastermind-runorg/SKILL.md +13 -13
  24. package/.claude/skills/mastermind-stoporg/SKILL.md +7 -62
  25. package/.claude/skills/mastermind-tasks/SKILL.md +6 -8
  26. package/README.md +28 -43
  27. package/dist/src/agents/registry-builder.d.ts +57 -21
  28. package/dist/src/agents/registry-builder.d.ts.map +1 -1
  29. package/dist/src/agents/registry-builder.js +41 -341
  30. package/dist/src/agents/registry-builder.js.map +1 -1
  31. package/dist/src/agents/registry-freshness.d.ts +4 -4
  32. package/dist/src/agents/registry-freshness.d.ts.map +1 -1
  33. package/dist/src/agents/registry-freshness.js +11 -60
  34. package/dist/src/agents/registry-freshness.js.map +1 -1
  35. package/dist/src/commands/doc.d.ts.map +1 -1
  36. package/dist/src/commands/doc.js +6 -15
  37. package/dist/src/commands/doc.js.map +1 -1
  38. package/dist/src/commands/doctor-pick-checks.d.ts.map +1 -1
  39. package/dist/src/commands/doctor-pick-checks.js +9 -41
  40. package/dist/src/commands/doctor-pick-checks.js.map +1 -1
  41. package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
  42. package/dist/src/commands/doctor-project-checks.js +7 -5
  43. package/dist/src/commands/doctor-project-checks.js.map +1 -1
  44. package/dist/src/commands/hooks-routing-commands.d.ts.map +1 -1
  45. package/dist/src/commands/hooks-routing-commands.js +4 -3
  46. package/dist/src/commands/hooks-routing-commands.js.map +1 -1
  47. package/dist/src/commands/init-upgrade.d.ts.map +1 -1
  48. package/dist/src/commands/init-upgrade.js +4 -0
  49. package/dist/src/commands/init-upgrade.js.map +1 -1
  50. package/dist/src/commands/init-wizard.d.ts.map +1 -1
  51. package/dist/src/commands/init-wizard.js +7 -2
  52. package/dist/src/commands/init-wizard.js.map +1 -1
  53. package/dist/src/commands/init.d.ts.map +1 -1
  54. package/dist/src/commands/init.js +16 -27
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/monograph.d.ts.map +1 -1
  57. package/dist/src/commands/monograph.js +16 -21
  58. package/dist/src/commands/monograph.js.map +1 -1
  59. package/dist/src/commands/org.js +1 -1
  60. package/dist/src/commands/org.js.map +1 -1
  61. package/dist/src/decision/jev.d.ts +4 -0
  62. package/dist/src/decision/jev.d.ts.map +1 -1
  63. package/dist/src/decision/jev.js.map +1 -1
  64. package/dist/src/decision/pick-real.d.ts +93 -0
  65. package/dist/src/decision/pick-real.d.ts.map +1 -0
  66. package/dist/src/decision/pick-real.js +253 -0
  67. package/dist/src/decision/pick-real.js.map +1 -0
  68. package/dist/src/decision/picks.d.ts +1 -0
  69. package/dist/src/decision/picks.d.ts.map +1 -1
  70. package/dist/src/decision/picks.js +1 -0
  71. package/dist/src/decision/picks.js.map +1 -1
  72. package/dist/src/init/embeddings-step.d.ts +23 -0
  73. package/dist/src/init/embeddings-step.d.ts.map +1 -0
  74. package/dist/src/init/embeddings-step.js +53 -0
  75. package/dist/src/init/embeddings-step.js.map +1 -0
  76. package/dist/src/init/executor.d.ts.map +1 -1
  77. package/dist/src/init/executor.js +9 -1
  78. package/dist/src/init/executor.js.map +1 -1
  79. package/dist/src/init/helpers-generator.d.ts.map +1 -1
  80. package/dist/src/init/helpers-generator.js +4 -0
  81. package/dist/src/init/helpers-generator.js.map +1 -1
  82. package/dist/src/init/project-indexes.d.ts +19 -0
  83. package/dist/src/init/project-indexes.d.ts.map +1 -0
  84. package/dist/src/init/project-indexes.js +44 -0
  85. package/dist/src/init/project-indexes.js.map +1 -0
  86. package/dist/src/init/shared.d.ts +7 -2
  87. package/dist/src/init/shared.d.ts.map +1 -1
  88. package/dist/src/init/shared.js +7 -6
  89. package/dist/src/init/shared.js.map +1 -1
  90. package/dist/src/init/types.d.ts +13 -2
  91. package/dist/src/init/types.d.ts.map +1 -1
  92. package/dist/src/init/types.js +11 -3
  93. package/dist/src/init/types.js.map +1 -1
  94. package/dist/src/init/upgrade.d.ts +3 -0
  95. package/dist/src/init/upgrade.d.ts.map +1 -1
  96. package/dist/src/init/upgrade.js +21 -6
  97. package/dist/src/init/upgrade.js.map +1 -1
  98. package/dist/src/init/write-antigravity.d.ts +1 -0
  99. package/dist/src/init/write-antigravity.d.ts.map +1 -1
  100. package/dist/src/init/write-antigravity.js +19 -1
  101. package/dist/src/init/write-antigravity.js.map +1 -1
  102. package/dist/src/init/write-claude.d.ts.map +1 -1
  103. package/dist/src/init/write-claude.js +8 -5
  104. package/dist/src/init/write-claude.js.map +1 -1
  105. package/dist/src/mcp-tools/embeddings-tools.d.ts.map +1 -1
  106. package/dist/src/mcp-tools/embeddings-tools.js +14 -5
  107. package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
  108. package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
  109. package/dist/src/mcp-tools/hooks-routing.js +4 -3
  110. package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
  111. package/dist/src/mcp-tools/monograph/build-tools.d.ts.map +1 -1
  112. package/dist/src/mcp-tools/monograph/build-tools.js +16 -9
  113. package/dist/src/mcp-tools/monograph/build-tools.js.map +1 -1
  114. package/dist/src/memory/embedding-operations.d.ts +0 -4
  115. package/dist/src/memory/embedding-operations.d.ts.map +1 -1
  116. package/dist/src/memory/embedding-operations.js +11 -85
  117. package/dist/src/memory/embedding-operations.js.map +1 -1
  118. package/dist/src/memory/memory-bridge.d.ts +7 -0
  119. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  120. package/dist/src/memory/memory-bridge.js +15 -0
  121. package/dist/src/memory/memory-bridge.js.map +1 -1
  122. package/dist/src/orgrt/bash-timeout.d.ts +17 -0
  123. package/dist/src/orgrt/bash-timeout.d.ts.map +1 -1
  124. package/dist/src/orgrt/bash-timeout.js +15 -0
  125. package/dist/src/orgrt/bash-timeout.js.map +1 -1
  126. package/dist/src/orgrt/session.d.ts.map +1 -1
  127. package/dist/src/orgrt/session.js +5 -2
  128. package/dist/src/orgrt/session.js.map +1 -1
  129. package/dist/src/orgrt/task-match.d.ts +3 -1
  130. package/dist/src/orgrt/task-match.d.ts.map +1 -1
  131. package/dist/src/orgrt/task-match.js +12 -4
  132. package/dist/src/orgrt/task-match.js.map +1 -1
  133. package/dist/tsconfig.tsbuildinfo +1 -1
  134. package/package.json +5 -5
@@ -1,4 +1,4 @@
1
- <!-- Define and save an autonomous agent organization (Org Runtime v2) — roles and hierarchy. Suggest or confirm roles, then persist the org config for `monomind org run`/`serve`. -->
1
+ <!-- Define and save an autonomous agent organization (Org Runtime) — roles and hierarchy. Suggest or confirm roles, then persist the org config for `monomind org run`/`serve`. -->
2
2
 
3
3
  **If $ARGUMENTS is empty:** Output the following and wait.
4
4
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **MASTERMIND: CREATE ORG**
8
8
 
9
- An org is a named, persistent agent team, run by the Org Runtime v2 daemon — not a one-shot Mastermind run and not a Task-tool-spawned boss. Once created, every role in the config starts as its own live agent session the moment you run the org; roles message each other directly (no shared task board) and the org keeps running until stopped.
9
+ An org is a named, persistent agent team, run by the Org Runtime daemon — not a one-shot Mastermind run and not a Task-tool-spawned boss. Once created, every role in the config starts as its own live agent session the moment you run the org; roles message each other directly (no shared task board) and the org keeps running until stopped.
10
10
 
11
11
  Use orgs when the work is ongoing, not single-shot. A content team that ships 10 posts a month. A research squad that runs competitive scans weekly. A dev team with a permanent backlog.
12
12
 
@@ -18,23 +18,17 @@ Quick reference for all Mastermind skills and CLI commands available in this pro
18
18
  | `mastermind:swarm` | Swarm coordination reference — topologies, strategies, init patterns |
19
19
  | `mastermind:memory` | Memory CLI quick reference |
20
20
 
21
- ## Org Runtime (v2 default)
21
+ ## Org Runtime
22
22
 
23
23
  | Command | Description |
24
24
  |-------|-------------|
25
- | `/mastermind:createorg` | Define and save an org (always v2-shaped) |
26
- | `/mastermind:runorg` | Start a saved org via the Org Runtime v2 daemon (`monomind org run`/`serve`) — auto-migrates v1-shaped configs first |
27
- | `/mastermind:stoporg` | Stop a running scheduled org loop |
25
+ | `/mastermind:createorg` | Define and save an org |
26
+ | `/mastermind:runorg` | Start a saved org via the Org Runtime daemon (`monomind org run`/`serve`) — converts legacy-format config files first |
27
+ | `/mastermind:stoporg` | Stop a running org (`monomind org stop`) |
28
28
  | `/mastermind:orgstatus` | Detailed status for a single org |
29
29
  | `/mastermind:orgs` | List all saved orgs |
30
30
 
31
- ### Legacy v1 (marker `LEGACY-ORG-V1` — reachable only under these explicit names)
32
-
33
- | Command | Description |
34
- |-------|-------------|
35
- | `/mastermind:runorgv1` | Legacy prompt-orchestrated org runner — superseded by `/mastermind:runorg` |
36
- | `/mastermind:approvev1` | Legacy approval queue for v1 orgs — v2 approvals arrive as dashboard question events instead |
37
- | `/mastermind:heartbeatv1` | Legacy manual agent wake-up for v1 orgs — v2 roles are live SDK sessions with no wake cycle |
31
+ Pending tool approvals from a running org: `monomind org approvals <org>`, then `monomind org approve` / `monomind org deny` (or the dashboard's Human Input tab).
38
32
 
39
33
  ## Core CLI Commands
40
34
 
@@ -249,8 +249,7 @@ Describe your goal. Mastermind identifies the relevant domains, spawns specialis
249
249
 
250
250
  **Persistent agent orgs** — named teams that coordinate across sessions
251
251
  `/mastermind:createorg` — define an org: roles, hierarchy, goal
252
- `/mastermind:runorg` — start a saved org via the Org Runtime v2 daemon (auto-migrates v1 configs); legacy prompt-orchestrated path: `/mastermind:runorgv1`
253
- `/mastermind:approvev1` — review and action pending approval requests from running org agents (v1 orgs only — v2 approvals arrive in the dashboard Human Input tab)
252
+ `/mastermind:runorg` — start a saved org via the Org Runtime daemon (converts legacy-format config files first); pending tool approvals: `monomind org approvals <org>` or the dashboard Human Input tab
254
253
 
255
254
  **Autonomous & advanced**
256
255
  `/mastermind:repeat` — wrap any mastermind command in a loop (`--tillend` supported)
@@ -1,4 +1,4 @@
1
- <!-- List all saved orgs with their status, schedule interval, and last/next run times. Shows stopped/active/paused state for scheduled orgs. -->
1
+ <!-- List all saved orgs with their runtime status, schedule, and last run time. Flags crashed runs and legacy-format config files. -->
2
2
 
3
3
  **If $ARGUMENTS is empty:** Execute the listing below directly.
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Show detailed status for a single org — lifecycle state, schedule, last/next run, recent activity, and roles. For scheduled orgs shows loop health. -->
1
+ <!-- Show detailed status for a single org — runtime state, schedule, config health, pending approvals, recent activity, and roles. -->
2
2
 
3
3
  **If $ARGUMENTS is empty:** Output the following and wait.
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- Start a saved org via the Org Runtime v2 daemon (monomind org run/serve). Validates the config, auto-migrates v1-shaped configs, starts the daemon, and confirms liveness. The legacy prompt-orchestrated path lives at /mastermind:runorgv1. -->
1
+ <!-- Start a saved org via the Org Runtime daemon (monomind org run/serve). Validates the config, converts legacy-format config files, starts the daemon, and confirms liveness. -->
2
2
 
3
3
  **If $ARGUMENTS is empty:** Output the following and wait.
4
4
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **MASTERMIND: RUN ORG**
8
8
 
9
- Running an org starts it through the Org Runtime v2 daemon. Every role becomes
9
+ Running an org starts it through the Org Runtime daemon. Every role becomes
10
10
  a live SDK session; the daemon forwards dashboard events itself — no boss
11
11
  agent, no monotask board, no manual curl emissions.
12
12
 
@@ -1,4 +1,4 @@
1
- <!-- Stop a running scheduled org loop. Sets status to "stopped" — the next scheduled wakeup reads the status, skips all work, and does not reschedule. Loop dies within one interval. -->
1
+ <!-- Stop a running org. Wraps `monomind org stop`, which writes the stop file the org daemon polls — the daemon exits within about 2s. -->
2
2
 
3
3
  **If $ARGUMENTS is empty:** Output the following and wait.
4
4
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **MASTERMIND: STOP ORG**
8
8
 
9
- Stops a scheduled org loop cleanly. The org's `status` is set to `"stopped"` — the next scheduled wakeup will read the status, skip all work, and not reschedule itself. The loop is guaranteed to die within one interval (no orphaned wakeups).
9
+ Stops a running org cleanly. Runs `monomind org stop <name>`, which writes `.monomind/orgs/<name>/stop`; the org daemon polls for that file and exits within about 2 seconds.
10
10
 
11
11
  **Usage:**
12
12
 
@@ -23,16 +23,12 @@ Stops a scheduled org loop cleanly. The org's `status` is set to `"stopped"` —
23
23
 
24
24
  **Lifecycle:**
25
25
  ```
26
- stopped → (runorg) → active
27
- active → (stoporg) → stopped
28
- active → (HIL) → paused (set manually in .monomind/orgs/<name>.json)
29
- paused → (set active) → active (resume by setting status back to "active" in the JSON)
26
+ stopped → (runorg / monomind org run) → running
27
+ running → (stoporg / monomind org stop) → stopped
28
+ running → (monomind org pause) → paused
29
+ paused → (monomind org resume) → running
30
30
  ```
31
31
 
32
- **Note:** v2 orgs (the default) are stopped via `monomind org stop <name>` — this
33
- command handles both: it routes v2 orgs to the CLI and legacy `.loop` orgs
34
- through the v1 status-flip path.
35
-
36
32
  Your orgs (with live runtime status):
37
33
 
38
34
  ```bash
@@ -48,7 +44,7 @@ npx -y monomind@latest org list 2>/dev/null || echo "(none — run /mastermind:c
48
44
  Parse `$ARGUMENTS` for:
49
45
  - `--org <name>` → org_name = <name>
50
46
 
51
- If `--org` is not provided, list orgs with schedules and ask which to stop.
47
+ If `--org` is not provided, list orgs and ask which to stop.
52
48
 
53
49
  Verify the org file exists:
54
50
  ```bash
@@ -0,0 +1,434 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * The agent registry (.monomind/registry.json) — ONE implementation shared by
5
+ * the CLI (src/agents/registry-builder.ts and registry-freshness.ts load this
6
+ * file) and the SessionStart hook (handlers/pick-core.cjs), so an agent added
7
+ * to ~/.claude/agents or .claude/agents is indexed at the next session start
8
+ * without running the CLI, and both sides parse frontmatter the same way.
9
+ *
10
+ * Roots, in precedence order (the first definition of a slug wins):
11
+ * extra $MONOMIND_EXTRA_AGENT_PATHS (':'-separated), else a sibling
12
+ * ../agency-agents directory — canonical, wins over the project
13
+ * project <root>/.claude/agents
14
+ * user ~/.claude/agents — Claude Code loads these in every project, so
15
+ * they are valid Task subagent_types. As in Claude Code, a project
16
+ * (or extra) agent wins: a user agent whose slug or frontmatter
17
+ * `name` is already defined is left out (listed under `shadowed`).
18
+ *
19
+ * User entries record `~/.claude/agents/...` as filePath, never the absolute
20
+ * home path. The registry is rebuilt when missing or older than any agent file
21
+ * in those roots (an mtime scan, no parsing) or this builder.
22
+ *
23
+ * Usage: node .claude/helpers/agent-registry.cjs [projectRoot]
24
+ */
25
+
26
+ var fs = require('fs');
27
+ var os = require('os');
28
+ var path = require('path');
29
+
30
+ /** Directories to skip during the scan. `reengineer-squad` is a repo-only
31
+ * squad the package does not ship, and its `tester` would shadow the core
32
+ * tester. */
33
+ var SKIP_DIRS = new Set(['schemas', 'ephemeral', 'reengineer-squad']);
34
+ var MAX_AGENT_BYTES = 512 * 1024;
35
+ var USER_LABEL = '~/.claude/agents';
36
+
37
+ function homeDir(opts) {
38
+ return (opts && opts.home) || os.homedir();
39
+ }
40
+
41
+ /** Every `.md` under `root` (recursively), skipping SKIP_DIRS and `._` forks. */
42
+ function collectMdFiles(root) {
43
+ var results = [];
44
+ var entries;
45
+ try {
46
+ entries = fs.readdirSync(root);
47
+ } catch (e) {
48
+ return results;
49
+ }
50
+ for (var i = 0; i < entries.length; i++) {
51
+ var entry = entries[i];
52
+ var full = path.join(root, entry);
53
+ var stat;
54
+ try {
55
+ stat = fs.statSync(full);
56
+ } catch (e) {
57
+ continue;
58
+ }
59
+ if (stat.isDirectory()) {
60
+ if (SKIP_DIRS.has(entry)) continue;
61
+ results.push.apply(results, collectMdFiles(full));
62
+ } else if (stat.isFile() && path.extname(entry) === '.md' && !entry.startsWith('._')) {
63
+ results.push(full);
64
+ }
65
+ }
66
+ return results;
67
+ }
68
+
69
+ function unquote(v) {
70
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) return v.slice(1, -1);
71
+ return v;
72
+ }
73
+
74
+ /**
75
+ * Nested YAML list items under a key whose value was empty
76
+ * (`triggers:\n - pattern: ...`): a string array when every item is a plain
77
+ * scalar, an array of objects when items hold `key: value` pairs, null when
78
+ * there are no list items.
79
+ */
80
+ function parseNestedYamlList(lines, startIdx, parentIndent) {
81
+ var objects = [];
82
+ var simpleItems = [];
83
+ var current = null;
84
+ var consumed = 0;
85
+ var hasObjects = false;
86
+ for (var i = startIdx; i < lines.length; i++) {
87
+ var line = lines[i];
88
+ var trimmed = line.trim();
89
+ if (!trimmed || trimmed.startsWith('#')) {
90
+ consumed++;
91
+ continue;
92
+ }
93
+ var indent = line.length - line.trimStart().length;
94
+ if (indent <= parentIndent) break;
95
+ if (trimmed.startsWith('- ')) {
96
+ var itemContent = trimmed.slice(2);
97
+ var colon = itemContent.indexOf(': ');
98
+ if (colon !== -1) {
99
+ hasObjects = true;
100
+ current = {};
101
+ current[itemContent.slice(0, colon).trim()] = unquote(itemContent.slice(colon + 2).trim());
102
+ objects.push(current);
103
+ } else {
104
+ simpleItems.push(unquote(itemContent.trim()));
105
+ }
106
+ consumed++;
107
+ } else if (current && hasObjects) {
108
+ var c = trimmed.indexOf(': ');
109
+ if (c !== -1) current[trimmed.slice(0, c).trim()] = unquote(trimmed.slice(c + 2).trim());
110
+ consumed++;
111
+ } else {
112
+ break;
113
+ }
114
+ }
115
+ if (hasObjects && objects.length > 0) return { value: objects, consumed: consumed };
116
+ if (simpleItems.length > 0) return { value: simpleItems, consumed: consumed };
117
+ return { value: null, consumed: consumed };
118
+ }
119
+
120
+ /** The leading `---` frontmatter block as a flat key → value map (scalars,
121
+ * booleans, `[a, b]` and nested `- ` lists, `|`/`>` block scalars). */
122
+ function parseFrontmatter(content) {
123
+ var match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
124
+ if (!match) return {};
125
+ var result = {};
126
+ var lines = match[1].split('\n');
127
+ for (var i = 0; i < lines.length; i++) {
128
+ var line = lines[i];
129
+ var trimmed = line.trim();
130
+ if (!trimmed || trimmed.startsWith('#')) continue;
131
+ var colonIdx = trimmed.indexOf(':');
132
+ if (colonIdx === -1) continue;
133
+ var key = trimmed.slice(0, colonIdx).trim();
134
+ var value = trimmed.slice(colonIdx + 1).trim();
135
+
136
+ var blockScalar = /^([|>])[+-]?$/.exec(value);
137
+ if (blockScalar) {
138
+ var blockIndent = line.length - line.trimStart().length;
139
+ var body = [];
140
+ while (i + 1 < lines.length) {
141
+ var next = lines[i + 1];
142
+ if (next.trim() && next.length - next.trimStart().length <= blockIndent) break;
143
+ body.push(next.trim());
144
+ i++;
145
+ }
146
+ result[key] = body.join(blockScalar[1] === '|' ? '\n' : ' ').trim();
147
+ continue;
148
+ }
149
+
150
+ if (value === '') {
151
+ var nested = parseNestedYamlList(lines, i + 1, line.length - line.trimStart().length);
152
+ if (nested.value !== null) {
153
+ result[key] = nested.value;
154
+ i += nested.consumed;
155
+ continue;
156
+ }
157
+ }
158
+
159
+ if (value.startsWith('[') && value.endsWith(']')) {
160
+ value = value
161
+ .slice(1, -1)
162
+ .split(',')
163
+ .map(function (s) { return s.trim().replace(/^["']|["']$/g, ''); })
164
+ .filter(Boolean);
165
+ } else if (value === 'true') value = true;
166
+ else if (value === 'false') value = false;
167
+ else value = unquote(value);
168
+ result[key] = value;
169
+ }
170
+ return result;
171
+ }
172
+
173
+ /** The first argument that is not undefined/null (a `??` chain). */
174
+ function first() {
175
+ for (var i = 0; i < arguments.length; i++) if (arguments[i] !== undefined && arguments[i] !== null) return arguments[i];
176
+ return undefined;
177
+ }
178
+
179
+ function toStringArray(val) {
180
+ if (Array.isArray(val)) return val.map(String);
181
+ if (typeof val === 'string' && val.length > 0) return [val];
182
+ return [];
183
+ }
184
+
185
+ function parseTriggers(val) {
186
+ if (!val) return [];
187
+ var arr = Array.isArray(val) ? val : [val];
188
+ return arr.map(function (t) {
189
+ if (typeof t === 'object' && t !== null && 'pattern' in t) {
190
+ return { pattern: String(t.pattern), mode: String(first(t.mode, 'glob')) };
191
+ }
192
+ return { pattern: String(t), mode: 'glob' };
193
+ });
194
+ }
195
+
196
+ function slugFromFilename(file) {
197
+ return path.basename(file, path.extname(file))
198
+ .toLowerCase()
199
+ .replace(/\s+/g, '-')
200
+ .replace(/[^a-z0-9-]/g, '');
201
+ }
202
+
203
+ function categoryFromPath(file, root) {
204
+ var parts = path.relative(root, file).split(path.sep);
205
+ return parts.length > 1 ? parts[0] : 'default';
206
+ }
207
+
208
+ function isDir(p) {
209
+ try { return fs.statSync(p).isDirectory(); } catch (e) { return false; }
210
+ }
211
+
212
+ /**
213
+ * The agent roots for project `cwd`, in precedence order: extras, the
214
+ * project's .claude/agents, then ~/.claude/agents (opts.user === false leaves
215
+ * it out; opts.home overrides the home directory). When cwd is the home
216
+ * directory the two .claude/agents are one directory, listed once as `user`.
217
+ */
218
+ function computeAgentRoots(cwd, opts) {
219
+ var env = (opts && opts.env) || process.env;
220
+ var extras = env.MONOMIND_EXTRA_AGENT_PATHS ? env.MONOMIND_EXTRA_AGENT_PATHS.split(':').filter(Boolean) : [];
221
+ var sibling = path.join(cwd, '..', 'agency-agents');
222
+ if (extras.length === 0 && isDir(sibling)) extras.push(sibling);
223
+ var roots = extras.map(function (dir) { return { dir: dir, origin: 'extra' }; });
224
+ var projectDir = path.join(cwd, '.claude', 'agents');
225
+ var userDir = path.join(homeDir(opts), '.claude', 'agents');
226
+ var withUser = !(opts && opts.user === false);
227
+ if (!withUser || path.resolve(projectDir) !== path.resolve(userDir)) roots.push({ dir: projectDir, origin: 'project' });
228
+ if (withUser) roots.push({ dir: userDir, origin: 'user', label: USER_LABEL });
229
+ return roots;
230
+ }
231
+
232
+ /** A root given as a plain path is a project-local definition directory. */
233
+ function asRoot(r) {
234
+ return typeof r === 'string' ? { dir: r, origin: 'project' } : r;
235
+ }
236
+
237
+ /**
238
+ * Builds the registry from `roots` (first root wins a slug; a `user` agent
239
+ * also loses to any earlier agent with the same frontmatter `name` or slug),
240
+ * writing it to `outputPath` when given. opts.base: the directory project and
241
+ * extra filePaths are relative to (default cwd).
242
+ */
243
+ function buildUnifiedRegistry(roots, outputPath, opts) {
244
+ var now = new Date().toISOString();
245
+ var base = (opts && opts.base) || process.cwd();
246
+ var seen = new Map();
247
+ var names = new Set();
248
+ var dupes = new Map();
249
+ var shadowed = [];
250
+ var counts = { project: 0, user: 0, extra: 0 };
251
+ (roots || []).map(asRoot).forEach(function (root) {
252
+ var files = collectMdFiles(root.dir);
253
+ files.forEach(function (file) {
254
+ var content;
255
+ try {
256
+ if (fs.statSync(file).size > MAX_AGENT_BYTES) return;
257
+ content = fs.readFileSync(file, 'utf-8');
258
+ } catch (e) {
259
+ return;
260
+ }
261
+ var fm = parseFrontmatter(content);
262
+ var slug = (typeof fm.slug === 'string' && fm.slug) || slugFromFilename(file);
263
+ var name = (typeof fm.name === 'string' && fm.name) || slug;
264
+ var filePath = root.label
265
+ ? root.label + '/' + path.relative(root.dir, file).split(path.sep).join('/')
266
+ : path.isAbsolute(file) ? path.relative(base, file) : file;
267
+ var prior = seen.get(slug);
268
+ if (root.origin === 'user' && (prior ? prior.origin !== 'user' : names.has(name) || names.has(slug))) {
269
+ shadowed.push({ slug: slug, name: name, filePath: filePath });
270
+ return;
271
+ }
272
+ if (prior) {
273
+ var d = dupes.get(slug) || { slug: slug, kept: prior.filePath, dropped: [] };
274
+ d.dropped.push(filePath);
275
+ dupes.set(slug, d);
276
+ return;
277
+ }
278
+ var str = function (v) { return typeof v === 'string' ? v : undefined; };
279
+ seen.set(slug, {
280
+ slug: slug,
281
+ name: name,
282
+ version: str(fm.version) || '0.0.0',
283
+ category: str(fm.category) || categoryFromPath(file, root.dir),
284
+ description: str(fm.description) || '',
285
+ whenToUse: str(first(fm.when_to_use, fm.whenToUse)),
286
+ tags: toStringArray(fm.tags),
287
+ vibe: str(fm.vibe),
288
+ // A `capability:` block is read flattened, so its `expertise:` list lands top-level
289
+ capabilities: toStringArray(first(fm.capabilities, fm.expertise)),
290
+ taskTypes: toStringArray(first(fm.taskTypes, fm['task-types'], fm.task_types)),
291
+ tools: toStringArray(fm.tools),
292
+ triggers: parseTriggers(fm.triggers),
293
+ deprecated: fm.deprecated === true,
294
+ deprecatedBy: str(fm.deprecatedBy),
295
+ dependencies: toStringArray(fm.dependencies),
296
+ origin: root.origin,
297
+ filePath: filePath,
298
+ registeredAt: now,
299
+ lastUpdated: now,
300
+ });
301
+ names.add(name);
302
+ names.add(slug);
303
+ counts[root.origin] = (counts[root.origin] || 0) + 1;
304
+ });
305
+ });
306
+ var agents = Array.from(seen.values());
307
+ var registry = {
308
+ version: '1.0.0',
309
+ generatedAt: now,
310
+ totalAgents: agents.length,
311
+ counts: counts,
312
+ agents: agents,
313
+ duplicates: Array.from(dupes.values()),
314
+ shadowed: shadowed,
315
+ };
316
+ if (outputPath) writeRegistryFile(outputPath, registry);
317
+ return registry;
318
+ }
319
+
320
+ function agentCountOnDisk(file) {
321
+ try {
322
+ var reg = JSON.parse(fs.readFileSync(file, 'utf-8'));
323
+ return reg && Array.isArray(reg.agents) ? reg.agents.length : 0;
324
+ } catch (e) {
325
+ return 0;
326
+ }
327
+ }
328
+
329
+ /** Writes `registry` atomically — except an empty registry never replaces a
330
+ * non-empty one. Returns whether the file was written. */
331
+ function writeRegistryFile(file, registry) {
332
+ if (registry.agents.length === 0 && agentCountOnDisk(file) > 0) return false;
333
+ var tmp = file + '.' + process.pid + '.tmp';
334
+ fs.writeFileSync(tmp, JSON.stringify(registry, null, 2), 'utf-8');
335
+ fs.renameSync(tmp, file);
336
+ return true;
337
+ }
338
+
339
+ /** Newest mtime (ms) of any agent `.md` under `dirs`; directories count too,
340
+ * so a deleted file also marks the registry stale. 0 when there are none. */
341
+ function newestAgentMtime(dirs) {
342
+ var newest = 0;
343
+ function walk(dir) {
344
+ var entries;
345
+ try {
346
+ newest = Math.max(newest, fs.statSync(dir).mtimeMs);
347
+ entries = fs.readdirSync(dir, { withFileTypes: true });
348
+ } catch (e) {
349
+ return;
350
+ }
351
+ entries.forEach(function (e) {
352
+ var full = path.join(dir, e.name);
353
+ if (e.isDirectory()) walk(full);
354
+ else if (e.isFile() && path.extname(e.name) === '.md') {
355
+ try { newest = Math.max(newest, fs.statSync(full).mtimeMs); } catch (err) { /* vanished */ }
356
+ }
357
+ });
358
+ }
359
+ (dirs || []).forEach(function (d) { walk(typeof d === 'string' ? d : d.dir); });
360
+ return newest;
361
+ }
362
+
363
+ function registryPath(root) {
364
+ return path.join(root, '.monomind', 'registry.json');
365
+ }
366
+
367
+ function mtime(p) {
368
+ try { return fs.statSync(p).mtimeMs; } catch (e) { return 0; }
369
+ }
370
+
371
+ /** True when `root`'s registry is missing or older than an agent definition
372
+ * in any root (project, user, extra) or than this builder. */
373
+ function isStale(root, opts) {
374
+ var at = mtime(registryPath(root));
375
+ if (at === 0) return true;
376
+ return Math.max(newestAgentMtime(computeAgentRoots(root, opts)), mtime(__filename)) > at;
377
+ }
378
+
379
+ /** Rebuilds `root`'s registry when stale; the built registry, or null when it
380
+ * was fresh or the build failed. Never throws. */
381
+ function ensure(root, opts) {
382
+ try {
383
+ if (!isStale(root, opts)) return null;
384
+ fs.mkdirSync(path.join(root, '.monomind'), { recursive: true });
385
+ return buildUnifiedRegistry(computeAgentRoots(root, opts), registryPath(root), { base: root });
386
+ } catch (e) {
387
+ return null;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * The nearest directory at or above `cwd` holding `.claude/agents` or
393
+ * `.monomind`. The walk stops at the git root (inclusive) and never returns
394
+ * the home directory itself, whose ~/.monomind is the user's global state.
395
+ * Null when no project is found.
396
+ */
397
+ function findProjectRoot(cwd, home) {
398
+ var stopAt = path.resolve(home || os.homedir());
399
+ var dir = path.resolve(cwd);
400
+ for (;;) {
401
+ if (dir === stopAt) return null;
402
+ if (fs.existsSync(path.join(dir, '.claude', 'agents')) || fs.existsSync(path.join(dir, '.monomind'))) return dir;
403
+ if (fs.existsSync(path.join(dir, '.git'))) return null;
404
+ var parent = path.dirname(dir);
405
+ if (parent === dir) return null;
406
+ dir = parent;
407
+ }
408
+ }
409
+
410
+ function main() {
411
+ var root = process.argv[2] || process.env.CLAUDE_PROJECT_DIR || findProjectRoot(process.cwd());
412
+ if (!root) {
413
+ process.stderr.write('registry.json: no project found at or above ' + process.cwd() + '; nothing written\n');
414
+ return;
415
+ }
416
+ fs.mkdirSync(path.join(root, '.monomind'), { recursive: true });
417
+ var reg = buildUnifiedRegistry(computeAgentRoots(root), registryPath(root), { base: root });
418
+ process.stdout.write('registry.json: ' + reg.totalAgents + ' agents (' + reg.counts.user + ' from ' + USER_LABEL + ')\n');
419
+ }
420
+
421
+ if (require.main === module) main();
422
+
423
+ module.exports = {
424
+ USER_LABEL: USER_LABEL,
425
+ parseFrontmatter: parseFrontmatter,
426
+ computeAgentRoots: computeAgentRoots,
427
+ buildUnifiedRegistry: buildUnifiedRegistry,
428
+ writeRegistryFile: writeRegistryFile,
429
+ newestAgentMtime: newestAgentMtime,
430
+ registryPath: registryPath,
431
+ isStale: isStale,
432
+ ensure: ensure,
433
+ findProjectRoot: findProjectRoot,
434
+ };
@@ -62,11 +62,12 @@ function isSystemPrompt(prompt) {
62
62
  }
63
63
 
64
64
  /** True for a prompt with fewer than MIN_PICK_TOKENS content words
65
- * (pick-rank tokens: stopwords dropped, any script). */
65
+ * (pick-rank queryTokens: stopwords and words the prompt rules out dropped,
66
+ * any script), so "anything else pending rather than release" is a reply. */
66
67
  function isTrivialPrompt(prompt) {
67
68
  if (!pickRank || typeof prompt !== 'string') return false;
68
69
  var text = prompt.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, ' ').slice(0, 2000);
69
- return pickRank.tokens(text).length < MIN_PICK_TOKENS;
70
+ return pickRank.queryTokens(text).length < MIN_PICK_TOKENS;
70
71
  }
71
72
 
72
73
  function promptHash(prompt) {
@@ -422,6 +423,22 @@ function ensureSkillRegistryFresh(root, builder) {
422
423
  return 'rebuilt';
423
424
  }
424
425
 
426
+ /** Rebuild .monomind/registry.json when agent-registry.cjs says it is stale
427
+ * (missing, or older than an agent in .claude/agents, ~/.claude/agents or an
428
+ * extra root), so a new user agent is routed from the next session on. Only
429
+ * inside a project (findProjectRoot: never the home directory, never a
430
+ * folder without .monomind or .claude/agents). opts.home overrides the home
431
+ * directory. Returns what it did. */
432
+ function ensureAgentRegistryFresh(root, builder, opts) {
433
+ if (builder === undefined) {
434
+ try { builder = require('../agent-registry.cjs'); } catch (e) { builder = null; }
435
+ }
436
+ if (!builder || typeof builder.isStale !== 'function' || typeof builder.ensure !== 'function') return 'no-builder';
437
+ if (builder.findProjectRoot(root, opts && opts.home) !== path.resolve(root)) return 'not-a-project';
438
+ if (!builder.isStale(root, opts)) return 'fresh';
439
+ return builder.ensure(root, opts) ? 'rebuilt' : 'failed';
440
+ }
441
+
425
442
  module.exports = {
426
443
  KEYWORD_MIN_AGENT_SCORE: KEYWORD_MIN_AGENT_SCORE,
427
444
  KEYWORD_AGENT_LEAD: KEYWORD_AGENT_LEAD,
@@ -442,4 +459,5 @@ module.exports = {
442
459
  joinOutcome: joinOutcome,
443
460
  recordAdherence: recordAdherence,
444
461
  ensureSkillRegistryFresh: ensureSkillRegistryFresh,
462
+ ensureAgentRegistryFresh: ensureAgentRegistryFresh,
445
463
  };
@@ -39,6 +39,9 @@ module.exports = {
39
39
  // Keep skill-registry.json (the skill catalog routing and Jev read) in
40
40
  // step with .claude/skills: rebuilt when missing or older than any skill.
41
41
  try { require('./pick-core.cjs').ensureSkillRegistryFresh(CWD); } catch (e) { /* non-fatal */ }
42
+ // Same for .monomind/registry.json (the agent catalog): rebuilt when an
43
+ // agent in .claude/agents or ~/.claude/agents is newer.
44
+ try { require('./pick-core.cjs').ensureAgentRegistryFresh(CWD); } catch (e) { /* non-fatal */ }
42
45
 
43
46
  // ── Non-blocking security scan via @monoes/hooks worker ─────────────
44
47
  // The hooks package ships a security worker (worker-security.ts) that scans
@@ -135,7 +138,7 @@ module.exports = {
135
138
  if (bundledDir) {
136
139
  var healed = [];
137
140
  // Top-level critical files — mirrors executor.ts's `criticalHelpers` list.
138
- var helpersToCheck = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'monograph-freshen.cjs', 'control-start.cjs', 'intelligence.cjs', 'auto-memory-hook.mjs', 'build-skill-registry.cjs', 'org-skill-index.cjs', 'jev-picker.cjs', 'jev-catalog.cjs', 'redact-secrets.cjs', 'pick-rank.cjs', 'pick-stats.cjs'];
141
+ var helpersToCheck = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'monograph-freshen.cjs', 'control-start.cjs', 'intelligence.cjs', 'auto-memory-hook.mjs', 'build-skill-registry.cjs', 'agent-registry.cjs', 'org-skill-index.cjs', 'jev-picker.cjs', 'jev-catalog.cjs', 'redact-secrets.cjs', 'pick-rank.cjs', 'pick-stats.cjs'];
139
142
  for (var hi = 0; hi < helpersToCheck.length; hi++) {
140
143
  var hName = helpersToCheck[hi];
141
144
  var healedName = _healIfStale(
@@ -4,7 +4,8 @@
4
4
  * prompt hook (via jev-picker.cjs) and the CLI (`monomind pick`, MCP pick via
5
5
  * src/decision/catalogs.ts), so both rank exactly the same agents and skills.
6
6
  *
7
- * agents .monomind/registry.json (src/agents/registry-builder.ts)
7
+ * agents .monomind/registry.json (agent-registry.cjs: project,
8
+ * ~/.claude/agents and extra roots)
8
9
  * skills .claude/helpers/skill-registry.json (build-skill-registry.cjs):
9
10
  * `skills` = platform commands/skills, `orgSkills` = Org library
10
11
  */
@@ -60,7 +61,7 @@ function loadAgentCatalog(root, opts) {
60
61
  var category = typeof a.category === 'string' ? a.category : '';
61
62
  var description = typeof a.description === 'string' ? a.description : '';
62
63
  var when = typeof a.whenToUse === 'string' ? a.whenToUse.trim() : '';
63
- out.push({
64
+ var item = {
64
65
  id: a.slug,
65
66
  name: typeof a.name === 'string' ? a.name : a.slug,
66
67
  category: category,
@@ -69,7 +70,10 @@ function loadAgentCatalog(root, opts) {
69
70
  .concat(strings(a.tags), strings(a.capabilities), strings(a.taskTypes), typeof a.vibe === 'string' ? [a.vibe] : [])
70
71
  .filter(Boolean)
71
72
  .join(' '),
72
- });
73
+ };
74
+ // 'project' | 'user' (~/.claude/agents) | 'extra'; shown by `pick --json`, never sent to the model.
75
+ if (typeof a.origin === 'string') item.origin = a.origin;
76
+ out.push(item);
73
77
  });
74
78
  return out;
75
79
  }