@moda-ai/cli 1.26.0 → 1.27.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.
@@ -1071,14 +1071,14 @@ function detectAgents() {
1071
1071
  import { execFileSync as execFileSync2 } from "node:child_process";
1072
1072
  import { createHash as createHash2 } from "node:crypto";
1073
1073
  import {
1074
- existsSync as existsSync6,
1074
+ existsSync as existsSync7,
1075
1075
  lstatSync as lstatSync2,
1076
- mkdirSync as mkdirSync4,
1076
+ mkdirSync as mkdirSync5,
1077
1077
  readdirSync as readdirSync3,
1078
- readFileSync as readFileSync4,
1079
- writeFileSync as writeFileSync5
1078
+ readFileSync as readFileSync5,
1079
+ writeFileSync as writeFileSync6
1080
1080
  } from "node:fs";
1081
- import { basename, dirname as dirname4, join as join5, relative as relative3 } from "node:path";
1081
+ import { basename, dirname as dirname5, join as join6, relative as relative3 } from "node:path";
1082
1082
 
1083
1083
  // src/skills-manifest.ts
1084
1084
  import {
@@ -1299,10 +1299,86 @@ function yamlQuote(value) {
1299
1299
  return JSON.stringify(value);
1300
1300
  }
1301
1301
 
1302
+ // src/agents-md.ts
1303
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
1304
+ import { dirname as dirname4, join as join5 } from "node:path";
1305
+ var AGENTS_MD_FILENAME = "AGENTS.md";
1306
+ var FIXES_BLOCK_BEGIN = "<!-- moda:fixes:begin -->";
1307
+ var FIXES_BLOCK_END = "<!-- moda:fixes:end -->";
1308
+ function renderFixesBlock() {
1309
+ return [
1310
+ FIXES_BLOCK_BEGIN,
1311
+ "## Moda Fixes (managed block — edits inside are overwritten by moda-cli)",
1312
+ "",
1313
+ "- Before editing prompt or skill files, run `moda fixes` to see the ranked",
1314
+ ' queue of Moda Fixes ("a fix with proof") already drafted for this repo,',
1315
+ " and `moda fix <fix_id> --packet` for the evidence packet.",
1316
+ "- Gate any prompt/skill change against production evidence with",
1317
+ " `moda fix verify <fix_id> [--prompt-file=<path>]`. Exit codes: 0 = holdout",
1318
+ " win vs baseline (pass), 1 = failing (per-case findings in the envelope),",
1319
+ " 3 = degraded/inconclusive — never treat exit 3 as a pass.",
1320
+ "- Fix PRs must carry the magic word `Fixes MODA-FIX-<SHORTREF>` in the PR",
1321
+ " body so the merge confirms the fix and starts production monitoring.",
1322
+ FIXES_BLOCK_END
1323
+ ].join(`
1324
+ `);
1325
+ }
1326
+ function upsertManagedBlock(existing, block) {
1327
+ if (existing === null) {
1328
+ return `${block}
1329
+ `;
1330
+ }
1331
+ const beginIdx = existing.indexOf(FIXES_BLOCK_BEGIN);
1332
+ const endIdx = existing.indexOf(FIXES_BLOCK_END);
1333
+ if (beginIdx !== -1 && endIdx !== -1 && endIdx >= beginIdx) {
1334
+ const before = existing.slice(0, beginIdx);
1335
+ const after = existing.slice(endIdx + FIXES_BLOCK_END.length);
1336
+ return `${before}${block}${after}`;
1337
+ }
1338
+ const separator = existing === "" || existing.endsWith(`
1339
+
1340
+ `) ? "" : existing.endsWith(`
1341
+ `) ? `
1342
+ ` : `
1343
+
1344
+ `;
1345
+ return `${existing}${separator}${block}
1346
+ `;
1347
+ }
1348
+ function writeFileIfChanged(filePath, content) {
1349
+ try {
1350
+ mkdirSync4(dirname4(filePath), { recursive: true });
1351
+ if (existsSync6(filePath)) {
1352
+ const current = readFileSync4(filePath, "utf8");
1353
+ if (current === content)
1354
+ return "unchanged";
1355
+ writeFileSync5(filePath, content);
1356
+ return "updated";
1357
+ }
1358
+ writeFileSync5(filePath, content);
1359
+ return "wrote";
1360
+ } catch {
1361
+ return "failed";
1362
+ }
1363
+ }
1364
+ function upsertFixesAgentsBlock(cwd) {
1365
+ const filePath = join5(cwd, AGENTS_MD_FILENAME);
1366
+ let existing = null;
1367
+ if (existsSync6(filePath)) {
1368
+ try {
1369
+ existing = readFileSync4(filePath, "utf8");
1370
+ } catch {
1371
+ return { path: AGENTS_MD_FILENAME, status: "failed" };
1372
+ }
1373
+ }
1374
+ const next = upsertManagedBlock(existing, renderFixesBlock());
1375
+ return { path: AGENTS_MD_FILENAME, status: writeFileIfChanged(filePath, next) };
1376
+ }
1377
+
1302
1378
  // src/skills.ts
1303
1379
  var SKILLS_REQUEST_TIMEOUT_MS = 15000;
1304
1380
  var VALID_STATUSES = new Set(["approved", "proposed", "all"]);
1305
- var SKILL_PROPOSALS_MANIFEST_PATH = join5(".moda", "skill-proposals.yml");
1381
+ var SKILL_PROPOSALS_MANIFEST_PATH = join6(".moda", "skill-proposals.yml");
1306
1382
  async function fetchSkills(status) {
1307
1383
  const apiKey = resolveApiKey();
1308
1384
  if (!apiKey) {
@@ -1427,15 +1503,15 @@ function formatRunStatusHuman(run, events) {
1427
1503
  }
1428
1504
  function writeIfChanged(filePath, content) {
1429
1505
  try {
1430
- mkdirSync4(dirname4(filePath), { recursive: true });
1431
- if (existsSync6(filePath)) {
1432
- const current = readFileSync4(filePath, "utf8");
1506
+ mkdirSync5(dirname5(filePath), { recursive: true });
1507
+ if (existsSync7(filePath)) {
1508
+ const current = readFileSync5(filePath, "utf8");
1433
1509
  if (current === content)
1434
1510
  return "unchanged";
1435
- writeFileSync5(filePath, content);
1511
+ writeFileSync6(filePath, content);
1436
1512
  return "updated";
1437
1513
  }
1438
- writeFileSync5(filePath, content);
1514
+ writeFileSync6(filePath, content);
1439
1515
  return "wrote";
1440
1516
  } catch {
1441
1517
  return "failed";
@@ -1472,7 +1548,7 @@ async function pullSkills(options = {}) {
1472
1548
  }
1473
1549
  }
1474
1550
  const agents = detectAgents();
1475
- const writeCursor = agents.includes("Cursor") || existsSync6(join5(cwd, ".cursor"));
1551
+ const writeCursor = agents.includes("Cursor") || existsSync7(join6(cwd, ".cursor"));
1476
1552
  const records = [];
1477
1553
  for (const skill of skills) {
1478
1554
  if (!skill.skill_id || !skill.skill_md) {
@@ -1485,7 +1561,7 @@ async function pullSkills(options = {}) {
1485
1561
  log(` skipped invalid skill_id: ${JSON.stringify(skill.skill_id).slice(0, 80)}`);
1486
1562
  continue;
1487
1563
  }
1488
- const claudePath = join5(cwd, ".claude", "skills", safeId, "SKILL.md");
1564
+ const claudePath = join6(cwd, ".claude", "skills", safeId, "SKILL.md");
1489
1565
  const claudeStatus = writeIfChanged(claudePath, skill.skill_md);
1490
1566
  const claudeRelativePath = `.claude/skills/${safeId}/SKILL.md`;
1491
1567
  recordOutcome(summary, claudeStatus, log, claudeRelativePath);
@@ -1493,7 +1569,7 @@ async function pullSkills(options = {}) {
1493
1569
  if (claudeStatus !== "failed")
1494
1570
  paths.push(claudeRelativePath);
1495
1571
  if (writeCursor) {
1496
- const cursorPath = join5(cwd, ".cursor", "rules", `${safeId}.mdc`);
1572
+ const cursorPath = join6(cwd, ".cursor", "rules", `${safeId}.mdc`);
1497
1573
  const cursorStatus = writeIfChanged(cursorPath, claudeSkillToCursorRule(skill.skill_md));
1498
1574
  const cursorRelativePath = `.cursor/rules/${safeId}.mdc`;
1499
1575
  recordOutcome(summary, cursorStatus, log, cursorRelativePath);
@@ -1531,15 +1607,16 @@ function recordOutcome(summary, status, log, relativePath) {
1531
1607
  break;
1532
1608
  }
1533
1609
  }
1534
- var SKILLS_DIR = join5(".claude", "skills");
1610
+ var SKILLS_DIR = join6(".claude", "skills");
1535
1611
  var SKILL_FILE = "SKILL.md";
1536
1612
  var MAX_SKILL_WALK_DEPTH = 4;
1537
1613
  async function runSkillSync(flags = {}, profileOptions = {}, options = {}) {
1538
1614
  const cwd = options.cwd ?? process.cwd();
1539
1615
  const dryRun = flags["dry-run"] === "true";
1616
+ const agentsMd = dryRun ? undefined : upsertFixesAgentsBlock(cwd);
1540
1617
  const skills = discoverLocalSkills(cwd);
1541
1618
  if (skills.length === 0) {
1542
- return { synced: [], dryRun };
1619
+ return { synced: [], dryRun, ...agentsMd ? { agentsMd } : {} };
1543
1620
  }
1544
1621
  const sourceCommit = getGitCommit2(cwd);
1545
1622
  const response = await callControlAPI("/skills/sync", {
@@ -1558,11 +1635,11 @@ async function runSkillSync(flags = {}, profileOptions = {}, options = {}) {
1558
1635
  dryRun
1559
1636
  })
1560
1637
  }, profileOptions);
1561
- return { synced: response.synced ?? [], dryRun };
1638
+ return { synced: response.synced ?? [], dryRun, ...agentsMd ? { agentsMd } : {} };
1562
1639
  }
1563
1640
  function discoverLocalSkills(cwd) {
1564
- const skillsRoot = join5(cwd, SKILLS_DIR);
1565
- if (!existsSync6(skillsRoot))
1641
+ const skillsRoot = join6(cwd, SKILLS_DIR);
1642
+ if (!existsSync7(skillsRoot))
1566
1643
  return [];
1567
1644
  const managed = new Set(managedSkillPaths(cwd));
1568
1645
  const skills = [];
@@ -1570,12 +1647,12 @@ function discoverLocalSkills(cwd) {
1570
1647
  const sourcePath = relative3(cwd, absPath).replace(/\\/g, "/");
1571
1648
  if (managed.has(sourcePath))
1572
1649
  continue;
1573
- const key = basename(dirname4(absPath));
1650
+ const key = basename(dirname5(absPath));
1574
1651
  if (key === "moda-cli" || !/^[A-Za-z0-9_-]+$/.test(key))
1575
1652
  continue;
1576
1653
  let content;
1577
1654
  try {
1578
- content = readFileSync4(absPath, "utf8");
1655
+ content = readFileSync5(absPath, "utf8");
1579
1656
  } catch {
1580
1657
  continue;
1581
1658
  }
@@ -1610,7 +1687,7 @@ function walkSkillFiles(dir, depth) {
1610
1687
  }
1611
1688
  const files = [];
1612
1689
  for (const entry of entries) {
1613
- const fullPath = join5(dir, entry);
1690
+ const fullPath = join6(dir, entry);
1614
1691
  let stat;
1615
1692
  try {
1616
1693
  stat = lstatSync2(fullPath);
@@ -1684,7 +1761,7 @@ async function applySkillProposal(proposalId, flags, profileOptions = {}, option
1684
1761
  }
1685
1762
  const safeId = safeSkillId(proposal.skillId);
1686
1763
  const relativePath = `.claude/skills/${safeId}/SKILL.md`;
1687
- const writeStatus = writeIfChanged(join5(cwd, relativePath), proposal.skillMd);
1764
+ const writeStatus = writeIfChanged(join6(cwd, relativePath), proposal.skillMd);
1688
1765
  if (writeStatus === "failed") {
1689
1766
  throw new Error(`Failed to write ${relativePath}`);
1690
1767
  }
@@ -1717,9 +1794,9 @@ function safeSkillId(skillId) {
1717
1794
  return safeId;
1718
1795
  }
1719
1796
  function writeSkillProposalManifest(cwd, proposal, relativePath) {
1720
- const manifestPath = join5(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
1721
- mkdirSync4(dirname4(manifestPath), { recursive: true });
1722
- const existing = existsSync6(manifestPath) ? readFileSync4(manifestPath, "utf8") : `applied_proposals:
1797
+ const manifestPath = join6(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
1798
+ mkdirSync5(dirname5(manifestPath), { recursive: true });
1799
+ const existing = existsSync7(manifestPath) ? readFileSync5(manifestPath, "utf8") : `applied_proposals:
1723
1800
  `;
1724
1801
  const entry = [
1725
1802
  ` - proposal_id: "${escapeYamlString(proposal.proposalId)}"`,
@@ -1738,7 +1815,7 @@ function writeSkillProposalManifest(cwd, proposal, relativePath) {
1738
1815
  const prefix = existing.endsWith(`
1739
1816
  `) ? existing : `${existing}
1740
1817
  `;
1741
- writeFileSync5(manifestPath, `${prefix}${entry}
1818
+ writeFileSync6(manifestPath, `${prefix}${entry}
1742
1819
  `);
1743
1820
  }
1744
1821
  function escapeYamlString(value) {
@@ -1843,7 +1920,11 @@ async function runSkillsCommand(input) {
1843
1920
  }
1844
1921
  if (sub === "sync") {
1845
1922
  const result = await runSkillSync(flags, { profile: context.profile, env: context.env });
1846
- context.output.writeData({ synced: result.synced, dryRun: result.dryRun });
1923
+ context.output.writeData({
1924
+ synced: result.synced,
1925
+ dryRun: result.dryRun,
1926
+ ...result.agentsMd ? { agentsMd: result.agentsMd } : {}
1927
+ });
1847
1928
  return 0;
1848
1929
  }
1849
1930
  if (sub !== "pull") {
@@ -1973,8 +2054,8 @@ function sha2562(input) {
1973
2054
 
1974
2055
  // src/doctor.ts
1975
2056
  import { createHash as createHash3 } from "node:crypto";
1976
- import { existsSync as existsSync7, readFileSync as readFileSync5, statSync } from "node:fs";
1977
- import { join as join6 } from "node:path";
2057
+ import { existsSync as existsSync8, readFileSync as readFileSync6, statSync } from "node:fs";
2058
+ import { join as join7 } from "node:path";
1978
2059
  async function runDoctorCommand(context) {
1979
2060
  const report = await buildDoctorReport(context, {
1980
2061
  offline: context.flags.offline === "true"
@@ -2097,8 +2178,8 @@ async function buildDoctorReport(context, options) {
2097
2178
  checks.push(urlCheck("base_url_valid", "config", "Base URL is valid", baseUrl));
2098
2179
  checks.push(urlCheck("ingest_url_valid", "config", "Ingest URL is valid", ingestUrl));
2099
2180
  checks.push(localBaseUrlCheck(activeProfile.environment, baseUrl));
2100
- const manifestPath = join6(context.cwd, ".moda", "harness.json");
2101
- const manifestExists = existsSync7(manifestPath);
2181
+ const manifestPath = join7(context.cwd, ".moda", "harness.json");
2182
+ const manifestExists = existsSync8(manifestPath);
2102
2183
  checks.push({
2103
2184
  id: "harness_manifest_exists",
2104
2185
  group: "harness",
@@ -2184,7 +2265,7 @@ function authTokenCheck(context, hasApiKey) {
2184
2265
  };
2185
2266
  }
2186
2267
  function configPermissionCheck(path) {
2187
- if (!existsSync7(path)) {
2268
+ if (!existsSync8(path)) {
2188
2269
  return {
2189
2270
  id: "config_file_permissions",
2190
2271
  group: "config",
@@ -2327,7 +2408,7 @@ function harnessCurrentCheck(cwd, manifestPath, manifestExists, tenantId) {
2327
2408
  tenantId,
2328
2409
  tenantSource: tenantId ? "env_or_config" : undefined
2329
2410
  });
2330
- const saved = JSON.parse(readFileSync5(manifestPath, "utf8"));
2411
+ const saved = JSON.parse(readFileSync6(manifestPath, "utf8"));
2331
2412
  const savedHash = stableGraphHash(saved);
2332
2413
  const currentHash = stableGraphHash(current);
2333
2414
  const matches = savedHash === currentHash;
@@ -2387,8 +2468,8 @@ function harnessSyncStateCheck(cwd) {
2387
2468
  };
2388
2469
  }
2389
2470
  function harnessReportStateCheck(cwd) {
2390
- const reportPath = join6(cwd, HARNESS_REPORT_PATH);
2391
- if (!existsSync7(reportPath)) {
2471
+ const reportPath = join7(cwd, HARNESS_REPORT_PATH);
2472
+ if (!existsSync8(reportPath)) {
2392
2473
  return {
2393
2474
  id: "harness_report_approved_synced",
2394
2475
  group: "harness",
@@ -2481,11 +2562,11 @@ function harnessReportStateCheck(cwd) {
2481
2562
  }
2482
2563
  }
2483
2564
  function readHarnessReportApproval(cwd) {
2484
- const approvalPath = join6(cwd, HARNESS_REPORT_APPROVAL_PATH);
2485
- if (!existsSync7(approvalPath))
2565
+ const approvalPath = join7(cwd, HARNESS_REPORT_APPROVAL_PATH);
2566
+ if (!existsSync8(approvalPath))
2486
2567
  return null;
2487
2568
  try {
2488
- return JSON.parse(readFileSync5(approvalPath, "utf8"));
2569
+ return JSON.parse(readFileSync6(approvalPath, "utf8"));
2489
2570
  } catch {
2490
2571
  return {
2491
2572
  schemaVersion: HARNESS_REPORT_APPROVAL_SCHEMA_VERSION,
@@ -2499,7 +2580,7 @@ function readHarnessReportApproval(cwd) {
2499
2580
  }
2500
2581
  }
2501
2582
  function discoveryCheck(id, group, title, cwd, relativePaths, nextCommand) {
2502
- const found = relativePaths.filter((path) => existsSync7(join6(cwd, path)));
2583
+ const found = relativePaths.filter((path) => existsSync8(join7(cwd, path)));
2503
2584
  return {
2504
2585
  id,
2505
2586
  group,
@@ -2515,8 +2596,8 @@ function sdkInitializationCheck(cwd) {
2515
2596
  const candidates = ["src", "app", "agent_harness"];
2516
2597
  let detected = false;
2517
2598
  for (const candidate of candidates) {
2518
- const path = join6(cwd, candidate);
2519
- if (!existsSync7(path))
2599
+ const path = join7(cwd, candidate);
2600
+ if (!existsSync8(path))
2520
2601
  continue;
2521
2602
  try {
2522
2603
  const stat = statSync(path);
@@ -2839,4 +2920,4 @@ function isRecord(value) {
2839
2920
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2840
2921
  }
2841
2922
 
2842
- export { runPromptsCommand, initPrompts, runPromptSync, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, upsertSkillManifestRecord, runSkillSync, runSkillsCommand, runDoctorCommand, runStatusCommand, buildDoctorReport };
2923
+ export { DEFAULT_POLL_INTERVAL_MS, DEFAULT_WAIT_TIMEOUT_MS, fetchLatestRun, runPromptsCommand, initPrompts, runPromptSync, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, upsertSkillManifestRecord, runSkillSync, runSkillsCommand, runDoctorCommand, runStatusCommand, buildDoctorReport };