@cohortapp/agent-sdk 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/bin/maestro.mjs +9 -0
  2. package/lib/backlog.mjs +35 -0
  3. package/lib/backlog.test.mjs +36 -0
  4. package/lib/channels/contract.mjs +1 -0
  5. package/lib/channels/contract.test.mjs +2 -1
  6. package/lib/channels/inbox-item.mjs +54 -0
  7. package/lib/comms/send-gate.mjs +56 -1
  8. package/lib/comms/send-gate.test.mjs +56 -0
  9. package/lib/execution/disposition.mjs +62 -2
  10. package/lib/execution/disposition.test.mjs +54 -0
  11. package/lib/execution/drive.mjs +1 -1
  12. package/lib/execution/effects.mjs +282 -24
  13. package/lib/execution/effects.test.mjs +112 -0
  14. package/lib/execution/index.mjs +1 -0
  15. package/lib/execution/intake.mjs +43 -9
  16. package/lib/execution/intake.test.mjs +46 -0
  17. package/lib/execution/pipeline.mjs +5 -0
  18. package/lib/execution/surface-policy.mjs +80 -30
  19. package/lib/goals/classify.mjs +49 -5
  20. package/lib/goals/classify.test.mjs +58 -0
  21. package/lib/goals/collaborate.mjs +131 -17
  22. package/lib/goals/collaborate.test.mjs +16 -4
  23. package/lib/goals/loop.mjs +160 -9
  24. package/lib/goals/loop.test.mjs +129 -3
  25. package/lib/kpi-sensors.mjs +666 -0
  26. package/lib/kpi-sensors.test.mjs +275 -0
  27. package/lib/kpi.mjs +23 -0
  28. package/lib/mandate/audit.mjs +3 -0
  29. package/lib/mandate/contract.mjs +277 -0
  30. package/lib/mandate/contract.test.mjs +185 -0
  31. package/lib/mandate/derive.mjs +49 -5
  32. package/lib/mandate/derive.test.mjs +7 -1
  33. package/lib/mandate/model.mjs +10 -1
  34. package/lib/mandate/model.test.mjs +22 -3
  35. package/lib/mandate/refresh.mjs +53 -5
  36. package/lib/mandate/refresh.test.mjs +83 -1
  37. package/lib/org/doctor.mjs +66 -0
  38. package/lib/org/doctor.test.mjs +73 -1
  39. package/lib/org/inbound/directedness.mjs +119 -1
  40. package/lib/org/inbound/directedness.test.mjs +67 -0
  41. package/lib/org/inbound/facts.mjs +132 -9
  42. package/lib/org/inbound/facts.test.mjs +96 -0
  43. package/lib/org/inbound/hydrate.mjs +40 -0
  44. package/lib/org/inbound/index.test.mjs +83 -0
  45. package/lib/org/inbound/project.mjs +8 -0
  46. package/lib/org/inbound/surfaces.mjs +20 -0
  47. package/lib/org/param-contract.mjs +16 -2
  48. package/lib/org/protocol.checksum +1 -1
  49. package/lib/org/protocol.mjs +214 -2
  50. package/lib/org/protocol.test.mjs +11 -2
  51. package/lib/org/push.mjs +213 -49
  52. package/lib/org/push.test.mjs +112 -10
  53. package/lib/plan/compile.mjs +85 -8
  54. package/lib/plan/compile.test.mjs +82 -0
  55. package/lib/plan/emit.test.mjs +6 -1
  56. package/lib/setup/sections/mandate.mjs +43 -1
  57. package/lib/subagents/schema.mjs +14 -2
  58. package/lib/subagents/schema.test.mjs +22 -0
  59. package/package.json +1 -1
  60. package/scripts/ci/check-subagent-frontmatter.mjs +139 -0
  61. package/scripts/ci/check-subagent-frontmatter.test.mjs +124 -0
  62. package/scripts/ci/check.mjs +3 -0
  63. package/scripts/ci/conformance-org-api.mjs +16 -0
  64. package/scripts/ci/journey-approval-escalation.mjs +341 -0
  65. package/scripts/daemon/agent-daemon.mjs +582 -28
  66. package/scripts/daemon/cadence-handlers.mjs +273 -17
  67. package/scripts/daemon/cadence-handlers.test.mjs +101 -0
  68. package/scripts/daemon/execution-ladder.test.mjs +430 -0
  69. package/scripts/daemon/goal-steward-cadence.test.mjs +69 -0
  70. package/scripts/daemon/maestro-daemon.mjs +53 -0
  71. package/scripts/daemon/prompt-builder.mjs +47 -0
  72. package/scripts/daemon/responder.mjs +70 -3
  73. package/scripts/poller/imap-client.mjs +20 -1
  74. package/scripts/poller/inbox-scan-poller.mjs +15 -0
  75. package/scripts/poller/utils.mjs +51 -0
  76. package/scripts/setup/generate-capability.mjs +120 -11
  77. package/scripts/setup/generate-capability.test.mjs +134 -0
  78. package/scripts/setup/generate-plan.mjs +6 -1
  79. package/scripts/setup/repair-subagent-frontmatter.mjs +231 -0
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * repair-subagent-frontmatter.mjs — bring every `agents/<slug>/agent.md` back to
4
+ * a state that `lib/subagents/schema.validateAgentMd()` accepts.
5
+ *
6
+ * WHY THIS EXISTS. `renderAgentMd()` in generate-capability.mjs shipped two bugs
7
+ * that every repo scaffolded before 2026-08-11 inherited across its whole role
8
+ * roster:
9
+ *
10
+ * 1. it emitted no `tools:` key, so EVERY generated sub-agent failed
11
+ * validation ("frontmatter.tools is required") — 57 of 70 in this repo; and
12
+ * 2. it interpolated identity at GENERATION time rather than leaving
13
+ * `{{agent.*}}` tokens for lib/render.mjs, so a repo generated before
14
+ * config/agent.json was populated baked the empty values in permanently:
15
+ * "You are the Chief of Staff sub-agent for ( at UNCONFIGURED)".
16
+ *
17
+ * Both are fixed at source in generate-capability.mjs. This pass repairs the
18
+ * files that were already written, and it does so by calling that SAME exported
19
+ * renderAgentMd() — a second implementation here would be free to drift from the
20
+ * generator, which is the whole failure mode we are cleaning up.
21
+ *
22
+ * SAFETY. Dry-run by default; `--write` applies. It only rewrites files that
23
+ * FAIL validation and whose slug is still present in the capability pack, and it
24
+ * refuses to touch a file whose body has been edited away from the generator's
25
+ * output unless `--force` is passed — a hand-tailored mandate is real work and
26
+ * regenerating it silently would destroy it.
27
+ *
28
+ * Usage:
29
+ * node scripts/setup/repair-subagent-frontmatter.mjs # report only
30
+ * node scripts/setup/repair-subagent-frontmatter.mjs --write # apply
31
+ *
32
+ * @module scripts/setup/repair-subagent-frontmatter
33
+ */
34
+
35
+ "use strict";
36
+
37
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
38
+ import { join, dirname, resolve } from "node:path";
39
+ import { fileURLToPath, pathToFileURL } from "node:url";
40
+
41
+ import {
42
+ checkAgentFile,
43
+ splitFrontmatter,
44
+ } from "../../lib/subagents/schema.mjs";
45
+ import { renderAgentMd } from "./generate-capability.mjs";
46
+
47
+ const here = dirname(fileURLToPath(import.meta.url));
48
+ const MAESTRO_ROOT =
49
+ process.env.MAESTRO_ROOT &&
50
+ existsSync(join(process.env.MAESTRO_ROOT, "lib", "capability.mjs"))
51
+ ? process.env.MAESTRO_ROOT
52
+ : resolve(here, "..", "..");
53
+ const AGENT_ROOT = process.env.AGENT_ROOT || process.cwd();
54
+
55
+ /**
56
+ * The `{function, altitude}` the on-disk roster was generated from.
57
+ *
58
+ * Deliberately read from config/agent-roster.yaml, NOT config/agent.json: the
59
+ * roster records what was actually written to agents/, and the two can disagree
60
+ * (this repo's agent.json says technical-leader × svp while every file on disk is
61
+ * executive-operator × c-suite). Repairing against agent.json would look up the
62
+ * wrong pack and "repair" nothing.
63
+ *
64
+ * @param {object} yaml js-yaml module
65
+ * @returns {{function:string, altitude:string}|null}
66
+ */
67
+ export function rosterArchetype(yaml, agentRoot = AGENT_ROOT) {
68
+ const p = join(agentRoot, "config", "agent-roster.yaml");
69
+ if (!existsSync(p)) return null;
70
+ const doc = yaml.load(readFileSync(p, "utf-8")) || {};
71
+ const m = String(doc.archetype || "").match(/^([a-z-]+)@([a-z-]+)$/);
72
+ return m ? { function: m[1], altitude: m[2] } : null;
73
+ }
74
+
75
+ /**
76
+ * Pull the prose under a `## <heading>` out of an agent.md body, whitespace
77
+ * normalised. Returns "" when the heading is absent.
78
+ *
79
+ * Normalising whitespace is not cosmetic here: the machine-wide prettier hook
80
+ * inserts a blank line after every markdown heading, so the on-disk files differ
81
+ * from the generator's own output by whitespace alone in three places.
82
+ *
83
+ * @param {string} text full agent.md @param {string} heading e.g. "Mandate"
84
+ * @returns {string}
85
+ */
86
+ export function sectionText(text, heading) {
87
+ const body = splitFrontmatter(text).body;
88
+ const m = body.match(
89
+ new RegExp(`^##\\s+${heading}\\s*$([\\s\\S]*?)(?=^##\\s|\\Z)`, "m"),
90
+ );
91
+ return m ? m[1].replace(/\s+/g, " ").trim() : "";
92
+ }
93
+
94
+ /**
95
+ * Has this file's BODY been tailored away from the pack's own content?
96
+ *
97
+ * Deliberately NOT a diff against a regenerated file. The corrupt files differ
98
+ * from a token-bearing regeneration in exactly the identity spans, and trying to
99
+ * regex those spans back out is guesswork that fails open (an over-eager pattern
100
+ * silently overwrites someone's tailored mandate) or closed (a shy one repairs
101
+ * nothing — the first cut of this function flagged all 57 files as hand-edited).
102
+ *
103
+ * So compare the only two things a human tailoring an agent would actually
104
+ * change, against the pack they came from: the mandate prose and the towers
105
+ * list. Everything else in the template is boilerplate the generator owns. If
106
+ * both still match the pack verbatim, nothing has been authored here and
107
+ * regenerating destroys nothing.
108
+ *
109
+ * @param {string} currentText @param {object} packEntry @returns {boolean}
110
+ */
111
+ export function bodyWasHandEdited(currentText, packEntry) {
112
+ const mandate = String((packEntry && packEntry.mandate) || "")
113
+ .replace(/\s+/g, " ")
114
+ .trim();
115
+ const towers = (packEntry && packEntry.towers) || [];
116
+ const expectedTowers = towers.length
117
+ ? `- ${towers.join(", ")}`
118
+ : "- (general)";
119
+ return (
120
+ sectionText(currentText, "Mandate") !== mandate ||
121
+ sectionText(currentText, "Towers served") !== expectedTowers
122
+ );
123
+ }
124
+
125
+ async function main(argv = process.argv.slice(2)) {
126
+ const write = argv.includes("--write");
127
+ const force = argv.includes("--force");
128
+
129
+ const yamlMod = await import("js-yaml");
130
+ const yaml = yamlMod && yamlMod.default ? yamlMod.default : yamlMod;
131
+ const { resolveArchetype } = await import(
132
+ pathToFileURL(join(MAESTRO_ROOT, "lib", "archetype.mjs")).href
133
+ );
134
+ const { resolveCapabilitySurface } = await import(
135
+ pathToFileURL(join(MAESTRO_ROOT, "lib", "capability.mjs")).href
136
+ );
137
+
138
+ const sel = rosterArchetype(yaml);
139
+ if (!sel) {
140
+ console.error(
141
+ "[repair-subagent-frontmatter] config/agent-roster.yaml has no `archetype: <function>@<altitude>` — nothing to repair against.",
142
+ );
143
+ return 1;
144
+ }
145
+ const profile = await resolveArchetype(sel);
146
+ const surface = await resolveCapabilitySurface(sel);
147
+ const byId = new Map(surface.roleAgents.map((a) => [a.id, a]));
148
+
149
+ const agentsDir = join(AGENT_ROOT, "agents");
150
+ const slugs = existsSync(agentsDir)
151
+ ? readdirSync(agentsDir, { withFileTypes: true })
152
+ .filter((d) => d.isDirectory())
153
+ .map((d) => d.name)
154
+ .sort()
155
+ : [];
156
+
157
+ const repaired = [];
158
+ const skippedValid = [];
159
+ const unknown = [];
160
+ const handEdited = [];
161
+
162
+ for (const slug of slugs) {
163
+ const p = join(agentsDir, slug, "agent.md");
164
+ if (!existsSync(p)) {
165
+ unknown.push(`${slug} (no agent.md)`);
166
+ continue;
167
+ }
168
+ const text = readFileSync(p, "utf-8");
169
+ if (checkAgentFile(text, slug).ok) {
170
+ skippedValid.push(slug);
171
+ continue;
172
+ }
173
+ const packEntry = byId.get(slug);
174
+ if (!packEntry) {
175
+ unknown.push(`${slug} (invalid, but not in the ${sel.function} pack)`);
176
+ continue;
177
+ }
178
+ if (!force && bodyWasHandEdited(text, packEntry)) {
179
+ handEdited.push(slug);
180
+ continue;
181
+ }
182
+ const fresh = renderAgentMd(packEntry, profile);
183
+ const after = checkAgentFile(fresh, slug);
184
+ if (!after.ok) {
185
+ unknown.push(
186
+ `${slug} (regenerated but STILL invalid: ${after.errors.join("; ")})`,
187
+ );
188
+ continue;
189
+ }
190
+ if (write) writeFileSync(p, fresh, "utf-8");
191
+ repaired.push(slug);
192
+ }
193
+
194
+ const verb = write ? "repaired" : "would repair";
195
+ console.log(
196
+ `[repair-subagent-frontmatter] ${sel.function} × ${sel.altitude}: ${slugs.length} agent dir(s)`,
197
+ );
198
+ console.log(
199
+ `[repair-subagent-frontmatter] already valid: ${skippedValid.length}`,
200
+ );
201
+ console.log(`[repair-subagent-frontmatter] ${verb}: ${repaired.length}`);
202
+ if (handEdited.length)
203
+ console.log(
204
+ `[repair-subagent-frontmatter] SKIPPED (body hand-edited, pass --force to overwrite): ${handEdited.join(", ")}`,
205
+ );
206
+ if (unknown.length)
207
+ console.log(
208
+ `[repair-subagent-frontmatter] NOT repairable: ${unknown.join(", ")}`,
209
+ );
210
+ if (!write && repaired.length)
211
+ console.log(
212
+ "[repair-subagent-frontmatter] dry run — re-run with --write to apply.",
213
+ );
214
+ return 0;
215
+ }
216
+
217
+ if (
218
+ process.argv[1] &&
219
+ import.meta.url === pathToFileURL(process.argv[1]).href
220
+ ) {
221
+ main()
222
+ .then((c) => process.exit(c))
223
+ .catch((e) => {
224
+ console.error(
225
+ `[repair-subagent-frontmatter] FAILED: ${e && e.message ? e.message : e}`,
226
+ );
227
+ process.exit(1);
228
+ });
229
+ }
230
+
231
+ export { main };