@0dai-dev/cli 4.3.6 → 4.3.8

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 (77) hide show
  1. package/README.md +12 -11
  2. package/bin/0dai.js +133 -33
  3. package/lib/ai/manifest/mcp-exposure-contract.json +121 -0
  4. package/lib/ai/meta/manifest/mcp-tool-tiers.json +435 -0
  5. package/lib/ai/registry/mcp-catalog.json +98 -0
  6. package/lib/commands/auth.js +2 -1
  7. package/lib/commands/compliance.js +1 -1
  8. package/lib/commands/doctor.js +707 -12
  9. package/lib/commands/experience.js +40 -5
  10. package/lib/commands/feedback.js +157 -15
  11. package/lib/commands/gh.js +26 -0
  12. package/lib/commands/graph.js +9 -4
  13. package/lib/commands/heatmap.js +1 -1
  14. package/lib/commands/init.js +298 -27
  15. package/lib/commands/mcp.js +111 -33
  16. package/lib/commands/models.js +138 -41
  17. package/lib/commands/play.js +20 -4
  18. package/lib/commands/provider.js +30 -59
  19. package/lib/commands/quota.js +1 -1
  20. package/lib/commands/receipt.js +1 -1
  21. package/lib/commands/run.js +14 -6
  22. package/lib/commands/runner.js +31 -1
  23. package/lib/commands/status.js +176 -11
  24. package/lib/commands/swarm.js +130 -12
  25. package/lib/commands/trust.js +1 -1
  26. package/lib/commands/update.js +184 -38
  27. package/lib/commands/usage.js +1 -1
  28. package/lib/commands/validate.js +32 -3
  29. package/lib/commands/vault.js +43 -8
  30. package/lib/python/__init__.py +0 -0
  31. package/lib/python/agent_quotas.py +525 -0
  32. package/lib/python/anomaly_alert.py +397 -0
  33. package/lib/python/anti_pattern_detector.py +799 -0
  34. package/lib/python/auth.py +443 -0
  35. package/lib/python/capi_profile_guard.py +477 -0
  36. package/lib/python/compliance_report.py +581 -0
  37. package/lib/python/drift_detector.py +388 -0
  38. package/lib/python/experience_pipeline.py +1130 -0
  39. package/lib/python/graph.py +19 -0
  40. package/lib/python/graph_core.py +293 -0
  41. package/lib/python/graph_io.py +179 -0
  42. package/lib/python/graph_legacy.py +2052 -0
  43. package/lib/python/graph_legacy_helpers.py +221 -0
  44. package/lib/python/graph_outcomes_core.py +85 -0
  45. package/lib/python/graph_queries.py +171 -0
  46. package/lib/python/graph_slice.py +198 -0
  47. package/lib/python/graph_slicer.py +576 -0
  48. package/lib/python/graph_slicer_cli.py +60 -0
  49. package/lib/python/graph_validation.py +64 -0
  50. package/lib/python/heatmap.py +943 -0
  51. package/lib/python/json_utils.py +193 -0
  52. package/lib/python/mcp_exposure_check.py +247 -0
  53. package/lib/python/model_router.py +1434 -0
  54. package/lib/python/project_manager.py +621 -0
  55. package/lib/python/provider_profiles.py +1618 -0
  56. package/lib/python/provider_registry.py +1211 -0
  57. package/lib/python/provider_registry_cli.py +125 -0
  58. package/lib/python/receipt_png.py +727 -0
  59. package/lib/python/structural_memory.py +325 -0
  60. package/lib/python/swarm_cost.py +177 -0
  61. package/lib/python/usage_ledger.py +569 -0
  62. package/lib/scripts/mcp_tier_config.py +240 -0
  63. package/lib/shared.js +96 -12
  64. package/lib/tui/index.mjs +35174 -0
  65. package/lib/utils/activation_telemetry.js +1 -4
  66. package/lib/utils/constants.js +7 -1
  67. package/lib/utils/identity.js +184 -0
  68. package/lib/utils/mcp-auth.js +81 -15
  69. package/lib/utils/plan.js +1 -1
  70. package/lib/vault/index.js +19 -3
  71. package/lib/vault/storage.js +21 -2
  72. package/lib/wizard.js +5 -2
  73. package/package.json +9 -3
  74. package/scripts/build-python-bundle.js +106 -0
  75. package/scripts/build-tui.js +14 -1
  76. package/scripts/harvest_experience.py +523 -0
  77. package/scripts/postinstall.js +15 -9
package/README.md CHANGED
@@ -43,39 +43,36 @@ Daily (regular work):
43
43
  0dai run <goal> # split a backlog item into agent tasks [--dry-run] [--dry-cost] [--agent claude|codex|gemini]
44
44
  0dai swarm status # show queued, active, and done tasks
45
45
  0dai swarm add # queue one task [--task '...' --to agent]
46
- 0dai swarm-run # add, dispatch, and wait for one swarm task as JSON
46
+ 0dai swarm pick # pick one queued task [--agent codex]
47
+ 0dai swarm-run # repo-checkout helper: add, dispatch, and wait for one swarm task as JSON
47
48
  0dai harvest # convert experience events into candidate lessons
48
49
  0dai watch # live task monitor [--interval N]
49
50
  0dai reflect # session reflection: delivered, delegation, blockers
50
- 0dai standup # morning voice briefing about overnight agent work
51
- 0dai feedback push # send feedback to 0dai
51
+ 0dai standup # repo-checkout helper: morning voice briefing about overnight agent work
52
+ 0dai feedback push --target . # send feedback to 0dai
52
53
  0dai feedback retry # retry queued feedback after a failed push
53
- 0dai persona-simulate "topic" # focus-group report and optional issue drafts
54
54
  ```
55
55
 
56
56
  Pro / advanced:
57
57
 
58
58
  ```bash
59
59
  0dai init-existing # existing-repo setup alias for init
60
- 0dai project bind # bind repository to your 0dai account [--json]
60
+ 0dai project bind # bind repository to your 0dai account [--name NAME] [--json]
61
61
  0dai project status # local project binding and health [--json]
62
62
  0dai graph push # upload local graph (Pro: edges, Free: nodes)
63
63
  0dai graph pull # download server graph and merge locally
64
64
  0dai graph status # local graph stats and sync state
65
- 0dai ci # portable CI pipelines and AI-MQ [list|plan|mq-status]
66
65
  0dai heatmap # repo treemap: LOC × agent-edit intensity
67
66
  0dai session save # save session for roaming
68
- 0dai provider # local provider profiles and direct invoke
67
+ 0dai provider # provider profiles and local BYOK registry [status|list|switch|clear]
69
68
  0dai models # model ratings (--fast/--balanced/--deep/--available)
70
69
  0dai quota # agent subscription usage [--refresh] [--json]
71
70
  0dai usage # local token, task, and USD ledger [status|daily|monthly]
72
71
  0dai workspace # tmux workspace sessions (init|up|status)
73
- 0dai runner # runner/host architecture and queues [--json]
74
- 0dai report # privacy-safe project reports (preview|push|status)
72
+ 0dai report # repo-checkout helper: privacy-safe project reports (preview|push|status)
75
73
  0dai compliance # SOC2/ISO evidence and ADR audit-trail export
76
- 0dai experience # structured experience events (list|stats|sync|warnings|dismiss)
74
+ 0dai experience # structured experience events (list|stats|record-json|sync|warnings|dismiss)
77
75
  0dai receipt # session receipt PNG [--last|--active|--session ID]
78
- 0dai boneyard # weekly digest of worst agent moves [--week YYYY-WW|current]
79
76
  0dai gh branch-protection # GitHub branch protection [print|apply|install]
80
77
  0dai import claude-code-agents # import .claude/agents/*.md as personas [--dry-run]
81
78
  0dai auth logout # remove credentials
@@ -100,6 +97,10 @@ Global flags: `--target PATH`, `--version`, `--help`, `--json`, `--quiet`. See `
100
97
 
101
98
  Your source code is never sent. Only file names and package/build manifests.
102
99
 
100
+ ## Privacy
101
+
102
+ Local activation and experience records stay on your machine: `ai/meta/telemetry/activation.jsonl`, `ai/experience/`, and the generated `ai/` layer/project agent configs are local files. Server-side activation sends one `free_tier_activated` event when you run `0dai activate free` unless telemetry is opted out. See https://0dai.dev/legal/privacy.
103
+
103
104
  ## Why 0dai, not just Cursor or Copilot?
104
105
 
105
106
  Cursor and Copilot are editors. They help inside one coding session. 0dai writes a project layer that Claude Code, Codex, OpenCode, Gemini, and Aider can all read. The point is not another autocomplete box; it is one manifest, one set of agent roles, one task queue, and one health check for the repo.
package/bin/0dai.js CHANGED
@@ -26,7 +26,7 @@ if ((earlyParsed.args[0] || "help") === "swarm-run") {
26
26
  }
27
27
 
28
28
  const shared = require("../lib/shared");
29
- const { T, R, D, log, VERSION, fs, path, spawnSync, findRepoScript, checkVersion } = shared;
29
+ const { T, R, D, log, VERSION, fs, path, spawnSync, findRepoScript, repoScriptCandidates, checkVersion } = shared;
30
30
 
31
31
  /**
32
32
  * Hot-path Go binary fallback (issue #2424).
@@ -130,7 +130,10 @@ function tryGoHotPath(cmdName, target, argv) {
130
130
  if (!bin) return false;
131
131
  if (!goBinaryCompatible(bin)) return false;
132
132
  const forwarded = [cmdName, "--target", target, ...argv];
133
- const res = spawnSync(bin, forwarded, { stdio: "inherit" });
133
+ const res = spawnSync(bin, forwarded, {
134
+ stdio: "inherit",
135
+ env: { ...process.env, ODAI_NODE_PTY_PROBE_DIR: path.join(__dirname, "..") },
136
+ });
134
137
  if (res.error) return false;
135
138
  if (typeof res.status === "number") process.exit(res.status);
136
139
  process.exit(0);
@@ -157,7 +160,7 @@ const { cmdAudit } = require("../lib/commands/audit");
157
160
  const { cmdExport } = require("../lib/commands/export");
158
161
  const { cmdMcp } = require("../lib/commands/mcp");
159
162
  const { cmdVault } = require("../lib/commands/vault");
160
- const { cmdDoctor } = require("../lib/commands/doctor");
163
+ const { cmdDoctor, collectLayerVersionFreshness } = require("../lib/commands/doctor");
161
164
  const { cmdValidate } = require("../lib/commands/validate");
162
165
  const { cmdUpdate } = require("../lib/commands/update");
163
166
  const { cmdUpgrade } = require("../lib/commands/upgrade");
@@ -207,7 +210,7 @@ function printHelp() {
207
210
  console.log("");
208
211
  console.log("Start (first 5 minutes):");
209
212
  console.log(" init Create ai/ layer + MCP [--local] [--dry-run] [--minimal]");
210
- console.log(" doctor Check health, credentials, and drift [--drift]");
213
+ console.log(" doctor Check health, credentials, and drift [--drift] [--security]");
211
214
  console.log(" status Show maturity, swarm, and session state [--json]");
212
215
  console.log(" quickstart Run auth, init, doctor, and status checks in order");
213
216
  console.log(" detect Show detected stack");
@@ -221,40 +224,38 @@ function printHelp() {
221
224
  console.log(" run <goal> Split a backlog item into agent tasks [--dry-run] [--dry-cost] [--max-cost N] [--agent claude|codex|gemini] [--provider X]");
222
225
  console.log(" swarm status Show queued, active, and done tasks");
223
226
  console.log(" swarm add Queue one task for an agent [--task '...' --to agent]");
224
- console.log(" swarm-run Add, dispatch, and wait for one swarm task as JSON");
227
+ console.log(" swarm pick Pick one queued task for this agent [--agent codex]");
228
+ console.log(" swarm-run Repo-checkout helper: add, dispatch, and wait for one swarm task as JSON");
225
229
  console.log(" harvest Convert experience events into candidate lessons");
226
230
  console.log(" watch Live task monitor: queue, active, recently done [--interval N]");
227
231
  console.log(" reflect Session reflection: delivered, delegation rate, blockers");
228
- console.log(" standup Morning voice briefing about overnight agent work");
232
+ console.log(" standup Repo-checkout helper: morning voice briefing about overnight agent work");
229
233
  console.log(" feedback push Send feedback to 0dai");
234
+ console.log(" feedback submit Send one feedback report file [--file ai/feedback/codex-report.json]");
230
235
  console.log(" feedback retry Retry queued feedback after a failed push");
231
236
  console.log("");
232
237
  console.log("Pro / advanced:");
233
238
  console.log(" init-existing Legacy alias for init (older docs / scripted bootstraps); use 'init' [--minimal] [--dry-run]");
234
- console.log(" project bind Bind current repository to your 0dai account [--json]");
239
+ console.log(" project bind Bind current repository to your 0dai account [--name NAME] [--json]");
235
240
  console.log(" project status Show local project binding and health state [--json]");
236
241
  console.log(" graph push Upload local graph to server (Pro: edges, Free: nodes)");
237
242
  console.log(" graph pull Download server graph and merge locally");
238
243
  console.log(" graph status Show local graph stats and sync state");
239
- console.log(" ci Plan portable 0dai CI pipelines and inspect AI-MQ [list|plan|mq-status] [--json]");
240
244
  console.log(" mcp MCP server, tools, and health [list|catalog|doctor|call] [--json]");
241
245
  console.log(" vault Local age-encrypted secrets vault [init|add|get] [--json]");
242
246
  console.log(" heatmap Repo treemap: LOC x agent-edit intensity");
243
247
  console.log(" session save Save session for roaming");
244
- console.log(" provider Local provider profiles, bindings, and direct invoke");
248
+ console.log(" provider Local provider profiles and BYOK registry [status|list|switch|clear]");
245
249
  console.log(" models Show model ratings (--fast/--balanced/--deep/--available)");
246
250
  console.log(" models recommend Ledger-ranked model pick for a task type [--task TYPE] [--goal '...'] [--json]");
247
251
  console.log(" quota Agent subscription usage table [--refresh] [--json]");
248
252
  console.log(" usage Local token, task, and USD usage ledger [status|daily|monthly]");
249
253
  console.log(" workspace Manage tmux workspace sessions (init|up|status)");
250
- console.log(" runner Show runner/project-host architecture, queues, labels, and burst routing [status|plan|queue-status|label-audit|route-dry-run] [--json]");
251
- console.log(" report Privacy-safe project reports (preview|push|status)");
254
+ console.log(" report Repo-checkout helper: privacy-safe project reports (preview|push|status)");
252
255
  console.log(" trust Pre-run blast-radius: protected paths, authority matrix, egress [--json]");
253
256
  console.log(" compliance SOC2/ISO evidence and ADR audit-trail export");
254
- console.log(" experience Structured experience events (list|stats|sync|warnings|dismiss)");
255
- console.log(" persona-simulate Produce a focus-group report and optional issue drafts");
257
+ console.log(" experience Structured experience events (list|stats|record-json|sync|warnings|dismiss)");
256
258
  console.log(" receipt Render a 1200×630 session receipt PNG [--last|--active|--session ID]");
257
- console.log(" boneyard Weekly digest of worst agent moves [--week YYYY-WW|current]");
258
259
  console.log(" gh branch-protection [print|apply|install] Manage generated GitHub branch protection");
259
260
  console.log(" import claude-code-agents Import .claude/agents/*.md as 0dai personas [--source DIR] [--target DIR] [--dry-run]");
260
261
  console.log(" auth logout Remove credentials");
@@ -270,29 +271,106 @@ function printHelp() {
270
271
  console.log("https://0dai.dev");
271
272
  }
272
273
 
274
+ function explainMissingHarvestHelper(target, args) {
275
+ const candidates = repoScriptCandidates(target, "harvest_experience.py");
276
+ if (args.includes("--json")) {
277
+ console.log(JSON.stringify({
278
+ error: "harvest_helper_unavailable",
279
+ helper: "harvest_experience.py",
280
+ target,
281
+ looked_in: candidates,
282
+ fallback_inputs: [
283
+ "ai/experience/events/*.json",
284
+ "ai/experience/events/*.jsonl",
285
+ "ai/experience/outbox/*.json",
286
+ "ai/experience/outbox/*.jsonl",
287
+ ],
288
+ required_fallback_fields: [
289
+ "schema",
290
+ "event_id",
291
+ "timestamp",
292
+ "event_type",
293
+ "tool",
294
+ "task_type",
295
+ "summary",
296
+ "paths",
297
+ "ci_passed",
298
+ "source",
299
+ ],
300
+ }, null, 2));
301
+ return;
302
+ }
303
+
304
+ log("harvest helper unavailable");
305
+ if (!args.includes("--explain")) return;
306
+
307
+ console.log(` ${D}looked for scripts/harvest_experience.py at:${R}`);
308
+ for (const candidate of candidates) {
309
+ console.log(` - ${candidate}`);
310
+ }
311
+ console.log(` ${D}fallback event inputs:${R}`);
312
+ console.log(" - ai/experience/events/*.json");
313
+ console.log(" - ai/experience/events/*.jsonl");
314
+ console.log(" - ai/experience/outbox/*.json");
315
+ console.log(" - ai/experience/outbox/*.jsonl");
316
+ console.log(` ${D}fallback schema fields:${R}`);
317
+ console.log(" schema, event_id, timestamp, event_type, tool, task_type, summary, paths, ci_passed, source");
318
+ }
319
+
273
320
  const SYNC_ALLOWED_FLAGS = new Set([
321
+ "--check",
274
322
  "--dry-run",
275
323
  "--yes",
276
324
  "-y",
277
325
  "--quiet",
278
326
  "-q",
279
327
  "--force",
328
+ "--force-template-reset",
280
329
  "--no-diff",
281
330
  "--no-mcp-auth",
282
331
  "--skip-link-check",
283
332
  "--strict-links",
284
333
  ]);
285
334
 
335
+ function printInitHelp(commandName = "init") {
336
+ console.log(`\n ${T}0dai ${commandName}${R} — Create or refresh the managed ai/ layer\n`);
337
+ console.log("Usage:");
338
+ console.log(` 0dai ${commandName} [--local] [--minimal] [--dry-run] [--no-wizard] [--auth-code CODE] [--code LICENSE] [--no-mcp-auth] [--mcp-host HOST] [--reset] [--target PATH]`);
339
+ console.log("");
340
+ console.log("Options:");
341
+ console.log(" --local Generate the ai/ layer offline without signing in");
342
+ console.log(" --minimal Generate the smallest starter layer");
343
+ console.log(" --dry-run Preview init changes without writing them");
344
+ console.log(" (pair with --local to preview configs with no account or network)");
345
+ console.log(" --no-wizard Skip the interactive first-run wizard");
346
+ console.log(" --auth-code Exchange a browser/device auth code before init");
347
+ console.log(" --code Redeem an activation/license code before init");
348
+ console.log(" --no-mcp-auth Skip MCP cloud-token bootstrap during init");
349
+ console.log(" --mcp-host Use a custom MCP cloud host");
350
+ console.log(" --reset Overwrite managed MCP entries during bootstrap");
351
+ console.log(" --target PATH Run init against another project path");
352
+ console.log("");
353
+ }
354
+
355
+ function handleInitHelp(args) {
356
+ const initArgs = args.slice(1);
357
+ if (!initArgs.includes("--help") && !initArgs.includes("-h")) return false;
358
+ printInitHelp(args[0] || "init");
359
+ return true;
360
+ }
361
+
286
362
  function printSyncHelp() {
287
363
  console.log(`\n ${T}0dai sync${R} — Update the managed ai/ layer\n`);
288
364
  console.log("Usage:");
289
- console.log(" 0dai sync [--dry-run] [--yes|-y] [--quiet|-q] [--force] [--no-diff] [--no-mcp-auth] [--target PATH]");
365
+ console.log(" 0dai sync [--check] [--dry-run] [--yes|-y] [--quiet|-q] [--force] [--force-template-reset] [--no-diff] [--no-mcp-auth] [--target PATH]");
290
366
  console.log("");
291
367
  console.log("Options:");
368
+ console.log(" --check CI-friendly alias for --dry-run --quiet");
292
369
  console.log(" --dry-run Preview managed file changes without writing them");
293
370
  console.log(" --yes, -y Apply changes without an interactive confirmation prompt");
294
371
  console.log(" --quiet, -q Reduce non-essential output");
295
372
  console.log(" --force Also overwrite native config files from managed ai/ sources");
373
+ console.log(" --force-template-reset Allow replacing project-owned roadmap/manifest state");
296
374
  console.log(" --no-diff Hide unified diff output in previews/prompts");
297
375
  console.log(" --no-mcp-auth Skip MCP cloud-token bootstrap during sync");
298
376
  console.log(" --skip-link-check Skip the SPEC-028 doc cross-link scan after sync");
@@ -318,6 +396,24 @@ function handleSyncHelpOrInvalidArgs(args) {
318
396
  return false;
319
397
  }
320
398
 
399
+ function printActivateHelp() {
400
+ console.log(`\n ${T}0dai activate${R} — Claim or inspect a 0dai activation\n`);
401
+ console.log("Usage:");
402
+ console.log(" 0dai activate [free|status|code <CODE>] [--target PATH]");
403
+ console.log("");
404
+ console.log("Privacy:");
405
+ console.log(" Local files stay local; 'activate free' sends one free_tier_activated event unless telemetry is opted out.");
406
+ console.log(" https://0dai.dev/legal/privacy");
407
+ console.log("");
408
+ }
409
+
410
+ function handleActivateHelp(args) {
411
+ const activateArgs = args.slice(1);
412
+ if (!activateArgs.includes("--help") && !activateArgs.includes("-h")) return false;
413
+ printActivateHelp();
414
+ return true;
415
+ }
416
+
321
417
  async function main() {
322
418
  const { args, target } = parseTargetAndArgs(process.argv.slice(2), process.cwd());
323
419
 
@@ -333,8 +429,18 @@ async function main() {
333
429
  return;
334
430
  }
335
431
 
336
- // Non-blocking version check (runs in background, once per day)
337
- checkVersion();
432
+ if ((cmd === "init" || cmd === "init-existing") && handleInitHelp(args)) {
433
+ return;
434
+ }
435
+
436
+ if (cmd === "activate" && handleActivateHelp(args)) {
437
+ return;
438
+ }
439
+
440
+ // Non-blocking version check (runs in background, once per day). Skip
441
+ // machine-readable commands so update network I/O never holds JSON output
442
+ // open or pollutes stdout.
443
+ if (!args.includes("--json") && cmd !== "init" && cmd !== "init-existing") checkVersion();
338
444
 
339
445
  // Track first run for time-to-init telemetry
340
446
  try { require("../lib/onboarding").trackFirstRun(target); } catch {}
@@ -359,6 +465,7 @@ async function main() {
359
465
  case "watch": cmdWatch(target, args.slice(1)); break;
360
466
  case "audit": cmdAudit(target); break;
361
467
  case "export": await cmdExport(target, args); break;
468
+ case "vault": cmdVault(target, args[1], args.slice(2)); break;
362
469
  case "security": {
363
470
  const subSec = args[1] || "";
364
471
  if (subSec === "install-hook") {
@@ -397,26 +504,19 @@ async function main() {
397
504
  case "detect": await cmdDetect(target); break;
398
505
  case "doctor": {
399
506
  const driftMode = args.includes("--drift");
507
+ const securityMode = args.includes("--security");
400
508
  // Go fast-path covers only the base read-only doctor (no --drift, no
401
- // network). Anything else falls through to the full Node implementation.
402
- if (!driftMode) {
509
+ // --security, no network). Anything else falls through to the full Node
510
+ // implementation so the flag is not silently swallowed.
511
+ const layerFreshness = collectLayerVersionFreshness(target);
512
+ if (!driftMode && !securityMode && layerFreshness.status !== "stale") {
403
513
  tryGoHotPath("doctor", target, args.slice(1));
404
514
  }
405
- cmdDoctor(target, { drift: driftMode, json: args.includes("--json") });
406
- if (args.includes("--drift")) {
407
- const ds = findRepoScript(target, "drift_detector.py");
408
- console.log("\n drift report:");
409
- if (ds) {
410
- const result = spawnSync("python3", [ds, "report", "--target", target], { stdio: "inherit" });
411
- if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
412
- } else {
413
- console.log(` ${D}drift detector unavailable in this environment${R}`);
414
- }
415
- }
515
+ cmdDoctor(target, { drift: driftMode, security: securityMode, json: args.includes("--json") });
416
516
  break;
417
517
  }
418
518
  case "drift": {
419
- const ds = findRepoScript(target, "drift_detector.py");
519
+ const ds = shared.resolvePythonScript(target, "drift_detector.py");
420
520
  if (!ds) { log("drift detector unavailable"); break; }
421
521
  if (sub === "accept" && args[2]) {
422
522
  spawnSync("python3", [ds, "accept", args[2], "--target", target], { stdio: "inherit" });
@@ -441,7 +541,7 @@ async function main() {
441
541
  case "project":
442
542
  if (sub === "status") cmdStatus(target, { json: args.includes("--json") });
443
543
  else if (sub === "bind") await cmdProjectBind(target, args.slice(2));
444
- else console.log("Usage: 0dai project [bind [--json]|status [--json]] [--target PATH]");
544
+ else console.log("Usage: 0dai project [bind [--name NAME] [--json]|status [--json]] [--target PATH]");
445
545
  break;
446
546
  case "auth":
447
547
  if (sub === "login") await cmdAuthLogin(args.slice(2));
@@ -479,7 +579,7 @@ async function main() {
479
579
  case "feedback": await cmdFeedback(target, sub, args); break;
480
580
  case "harvest": {
481
581
  const harvestScript = findRepoScript(target, "harvest_experience.py");
482
- if (!harvestScript) { log("harvest helper unavailable"); break; }
582
+ if (!harvestScript) { explainMissingHarvestHelper(target, args); break; }
483
583
  const result = spawnSync("python3", [harvestScript, "--target", target, ...args.slice(1)], { stdio: "inherit", timeout: 15000 });
484
584
  if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
485
585
  break;
@@ -0,0 +1,121 @@
1
+ {
2
+ "schema_version": 1,
3
+ "updated_at": "2026-05-28",
4
+ "purpose": "Expected MCP exposure contract for agent runtimes. Health checks compare observed tool names against this manifest and scripts/mcp_tier_config.py.",
5
+ "project_server": {
6
+ "name": "0dai",
7
+ "config_path": ".mcp.json",
8
+ "tool_count_source": "scripts/mcp_tier_config.py",
9
+ "expected_tool_count": 113
10
+ },
11
+ "normalization": {
12
+ "accepted_examples": [
13
+ "memory_search",
14
+ "mcp__0dai__memory_search",
15
+ "mcp__0dai_internal__.memory_search",
16
+ "mcp__0dai_internal__memory_search"
17
+ ],
18
+ "rule": "Compare by canonical tool basename after MCP namespace prefixes."
19
+ },
20
+ "agents": {
21
+ "default": {
22
+ "required": [
23
+ "memory_search",
24
+ "get_project_health",
25
+ "get_codebase_map",
26
+ "record_experience"
27
+ ],
28
+ "recommended": [
29
+ "get_anomaly_taxonomy",
30
+ "get_anomaly_events",
31
+ "get_anomaly_summary",
32
+ "memory_add",
33
+ "memory_inject",
34
+ "search_experience",
35
+ "get_project_graph",
36
+ "get_specs",
37
+ "get_session",
38
+ "save_session",
39
+ "get_wal"
40
+ ]
41
+ },
42
+ "codex": {
43
+ "inherits": "default",
44
+ "required": [
45
+ "memory_search",
46
+ "get_project_health",
47
+ "get_codebase_map",
48
+ "record_experience"
49
+ ],
50
+ "recommended": [
51
+ "get_anomaly_taxonomy",
52
+ "get_anomaly_events",
53
+ "get_anomaly_summary",
54
+ "record_anomaly",
55
+ "memory_add",
56
+ "memory_inject",
57
+ "search_experience",
58
+ "get_project_graph",
59
+ "get_specs",
60
+ "get_session",
61
+ "save_session",
62
+ "swarm_delegate",
63
+ "watch_tasks",
64
+ "get_portfolio",
65
+ "get_wal"
66
+ ]
67
+ },
68
+ "claude": {
69
+ "inherits": "default",
70
+ "required": [
71
+ "memory_search",
72
+ "get_project_health",
73
+ "get_codebase_map",
74
+ "record_experience",
75
+ "get_session",
76
+ "save_session"
77
+ ],
78
+ "recommended": [
79
+ "get_anomaly_taxonomy",
80
+ "get_anomaly_events",
81
+ "get_anomaly_summary",
82
+ "record_anomaly",
83
+ "memory_add",
84
+ "memory_inject",
85
+ "search_experience",
86
+ "swarm_delegate",
87
+ "watch_tasks",
88
+ "get_portfolio",
89
+ "gh_label_create",
90
+ "gh_issue_edit_body"
91
+ ]
92
+ },
93
+ "pool": {
94
+ "inherits": "default",
95
+ "required": [
96
+ "memory_search",
97
+ "get_project_health",
98
+ "get_codebase_map",
99
+ "record_experience",
100
+ "watch_tasks"
101
+ ],
102
+ "recommended": [
103
+ "get_anomaly_taxonomy",
104
+ "get_anomaly_events",
105
+ "get_anomaly_summary",
106
+ "memory_add",
107
+ "search_experience",
108
+ "swarm_delegate",
109
+ "run_task",
110
+ "get_swarm_status",
111
+ "save_session"
112
+ ]
113
+ }
114
+ },
115
+ "statuses": {
116
+ "green": "All required and recommended tools for the agent were observed.",
117
+ "yellow": "Required tools were observed, but recommended tools or full server count evidence is missing.",
118
+ "red": "One or more required tools are missing from the observed runtime.",
119
+ "unknown": "No observed tool evidence was supplied by the runtime."
120
+ }
121
+ }