@hanzlaa/rcode 3.4.32 → 3.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 (86) hide show
  1. package/AGENTS.md +6 -6
  2. package/CONTRIBUTING.md +2 -0
  3. package/LICENSE +21 -0
  4. package/README.md +66 -403
  5. package/cli/agent.js +3 -2
  6. package/cli/doctor.js +87 -1
  7. package/cli/install.js +122 -31
  8. package/cli/lib/schemas.cjs +318 -0
  9. package/cli/postinstall.js +19 -3
  10. package/dist/rcode.js +318 -24
  11. package/package.json +8 -4
  12. package/rihal/agents/rihal-cross-platform-auditor.md +15 -0
  13. package/rihal/agents/rihal-dep-auditor.md +15 -0
  14. package/rihal/agents/rihal-docs-auditor.md +3 -145
  15. package/rihal/agents/rihal-i18n-auditor.md +16 -0
  16. package/rihal/agents/rihal-nyquist-auditor.md +4 -156
  17. package/rihal/agents/rihal-observability-auditor.md +16 -0
  18. package/rihal/agents/rihal-phase-researcher.md +1 -1
  19. package/rihal/agents/rihal-planner.md +1 -1
  20. package/rihal/bin/rihal-hooks.cjs +394 -4
  21. package/rihal/bin/rihal-tools.cjs +891 -24
  22. package/rihal/commands/create-prd.md +18 -0
  23. package/rihal/commands/execute-milestone.md +18 -0
  24. package/rihal/commands/plan-milestone.md +18 -0
  25. package/rihal/commands/scaffold-milestone.md +18 -0
  26. package/rihal/commands/scaffold-skill.md +18 -0
  27. package/rihal/references/REFERENCES_INDEX.md +49 -7
  28. package/rihal/references/agent-contracts.md +10 -0
  29. package/rihal/references/design-tokens.md +98 -0
  30. package/rihal/references/docs-auditor-playbook.md +148 -0
  31. package/rihal/references/git-preflight.md +117 -0
  32. package/rihal/references/iterative-retrieval.md +85 -0
  33. package/rihal/references/nyquist-auditor-playbook.md +157 -0
  34. package/rihal/references/workstream-flag.md +2 -2
  35. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +9 -0
  36. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +9 -0
  37. package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +4 -0
  38. package/rihal/skills/actions/4-implementation/rihal-code-review/steps/step-02-review.md +7 -3
  39. package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +4 -0
  40. package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +4 -0
  41. package/rihal/skills/agents/haitham-frontend/SKILL.md +2 -0
  42. package/rihal/skills/agents/majlis-council/SKILL.md +1 -1
  43. package/rihal/team.yaml +32 -0
  44. package/rihal/templates/settings-hooks.json +39 -0
  45. package/rihal/workflows/check-todos.md +4 -0
  46. package/rihal/workflows/code-review-fix.md +4 -3
  47. package/rihal/workflows/code-review.md +1 -1
  48. package/rihal/workflows/debug.md +1 -1
  49. package/rihal/workflows/dev-story.md +4 -0
  50. package/rihal/workflows/diff.md +2 -2
  51. package/rihal/workflows/do.md +16 -8
  52. package/rihal/workflows/docs-update.md +2 -2
  53. package/rihal/workflows/enable-hooks.md +6 -1
  54. package/rihal/workflows/execute-milestone.md +139 -0
  55. package/rihal/workflows/execute-regression-gates.md +1 -1
  56. package/rihal/workflows/execute-sprint.md +54 -2
  57. package/rihal/workflows/execute-verify-phase-goal.md +31 -4
  58. package/rihal/workflows/execute-waves.md +33 -5
  59. package/rihal/workflows/execute.md +40 -6
  60. package/rihal/workflows/help.md +1 -1
  61. package/rihal/workflows/import.md +1 -1
  62. package/rihal/workflows/lens-audit.md +39 -23
  63. package/rihal/workflows/list-workspaces.md +1 -1
  64. package/rihal/workflows/map-codebase.md +4 -4
  65. package/rihal/workflows/new-milestone.md +18 -1
  66. package/rihal/workflows/new-project-research.md +53 -1
  67. package/rihal/workflows/new-workspace.md +1 -1
  68. package/rihal/workflows/plan-milestone.md +105 -0
  69. package/rihal/workflows/plan-research-validation.md +1 -1
  70. package/rihal/workflows/plan-spawn-planner.md +1 -1
  71. package/rihal/workflows/plan.md +31 -3
  72. package/rihal/workflows/plant-seed.md +6 -0
  73. package/rihal/workflows/quick.md +11 -5
  74. package/rihal/workflows/research-phase.md +24 -0
  75. package/rihal/workflows/scaffold-milestone.md +60 -0
  76. package/rihal/workflows/scaffold-skill.md +137 -0
  77. package/rihal/workflows/scan.md +1 -1
  78. package/rihal/workflows/session-report.md +43 -3
  79. package/rihal/workflows/verify-work.md +3 -3
  80. package/server/dashboard.js +53 -6
  81. package/server/lib/api.js +7 -0
  82. package/server/lib/html/client.js +725 -13
  83. package/server/lib/html/css.js +2046 -466
  84. package/server/lib/html/shell.js +227 -134
  85. package/server/lib/scanner.js +33 -0
  86. package/server/orchestrator.js +438 -0
package/dist/rcode.js CHANGED
@@ -15217,7 +15217,7 @@ var require_install = __commonJS({
15217
15217
  var bold = (s) => pc.bold(s);
15218
15218
  var PACKAGE_ROOT2 = path2.resolve(__dirname, "..");
15219
15219
  var SOURCE_ROOT = path2.join(PACKAGE_ROOT2, "rihal");
15220
- var SUPPORTED_IDES = Object.freeze(["claude", "cursor", "gemini", "vscode", "antigravity"]);
15220
+ var SUPPORTED_IDES = Object.freeze(["claude", "cursor", "gemini", "vscode", "antigravity", "windsurf"]);
15221
15221
  var ConfigSchema = z.object({
15222
15222
  user_name: z.string().min(1),
15223
15223
  project_name: z.string().min(1),
@@ -15394,12 +15394,13 @@ var require_install = __commonJS({
15394
15394
  console.log(lines.join("\n"));
15395
15395
  }
15396
15396
  function detectIdeSignals(target) {
15397
- const signals = { claude: false, cursor: false, gemini: false, vscode: false, antigravity: false };
15397
+ const signals = { claude: false, cursor: false, gemini: false, vscode: false, antigravity: false, windsurf: false };
15398
15398
  if (fs2.existsSync(path2.join(target, ".claude"))) signals.claude = true;
15399
15399
  if (fs2.existsSync(path2.join(target, ".cursor"))) signals.cursor = true;
15400
15400
  if (fs2.existsSync(path2.join(target, ".gemini"))) signals.gemini = true;
15401
15401
  if (fs2.existsSync(path2.join(target, ".vscode"))) signals.vscode = true;
15402
15402
  if (fs2.existsSync(path2.join(target, ".antigravity"))) signals.antigravity = true;
15403
+ if (fs2.existsSync(path2.join(target, ".windsurf"))) signals.windsurf = true;
15403
15404
  const home = os.homedir();
15404
15405
  if (fs2.existsSync(path2.join(home, ".claude"))) signals.claude = true;
15405
15406
  if (fs2.existsSync(path2.join(home, ".cursor"))) signals.cursor = true;
@@ -15408,9 +15409,12 @@ var require_install = __commonJS({
15408
15409
  if (fs2.existsSync(path2.join(home, ".vscode"))) signals.vscode = true;
15409
15410
  if (fs2.existsSync(path2.join(home, ".config", "Code"))) signals.vscode = true;
15410
15411
  if (fs2.existsSync(path2.join(home, ".antigravity"))) signals.antigravity = true;
15412
+ if (fs2.existsSync(path2.join(home, ".windsurf"))) signals.windsurf = true;
15413
+ if (fs2.existsSync(path2.join(home, ".codeium", "windsurf"))) signals.windsurf = true;
15411
15414
  if (process.env.CURSOR_TRACE_ID || /cursor/i.test(process.env.TERM_PROGRAM || "")) signals.cursor = true;
15412
15415
  if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_ENTRYPOINT) signals.claude = true;
15413
15416
  if (process.env.VSCODE_PID || /vscode/i.test(process.env.TERM_PROGRAM || "")) signals.vscode = true;
15417
+ if (/windsurf/i.test(process.env.TERM_PROGRAM || "")) signals.windsurf = true;
15414
15418
  return signals;
15415
15419
  }
15416
15420
  async function resolveIde(opts) {
@@ -15530,7 +15534,7 @@ Installs (IDE-specific):
15530
15534
  case "vscode":
15531
15535
  return {
15532
15536
  agentsDir: path2.join(target, ".claude", "agents"),
15533
- commandsDir: path2.join(target, ".claude", "commands", "rihal"),
15537
+ commandsDir: path2.join(target, ".claude", "commands"),
15534
15538
  workflowsDir: path2.join(target, ".rihal", "workflows"),
15535
15539
  referencesDir: path2.join(target, ".rihal", "references"),
15536
15540
  binDir: path2.join(target, ".rihal", "bin"),
@@ -15544,8 +15548,16 @@ Installs (IDE-specific):
15544
15548
  referencesDir: path2.join(target, ".rihal", "references"),
15545
15549
  binDir: path2.join(target, ".rihal", "bin")
15546
15550
  };
15551
+ case "windsurf":
15552
+ return {
15553
+ agentsDir: path2.join(target, ".windsurf", "rules", "rihal", "agents"),
15554
+ commandsDir: path2.join(target, ".windsurf", "rules", "rihal", "commands"),
15555
+ workflowsDir: path2.join(target, ".rihal", "workflows"),
15556
+ referencesDir: path2.join(target, ".rihal", "references"),
15557
+ binDir: path2.join(target, ".rihal", "bin")
15558
+ };
15547
15559
  default:
15548
- throw new Error(`Unknown IDE: ${ide}. Supported: claude, cursor, gemini, vscode, antigravity`);
15560
+ throw new Error(`Unknown IDE: ${ide}. Supported: ${SUPPORTED_IDES.join(", ")}`);
15549
15561
  }
15550
15562
  }
15551
15563
  function walkFiles(dir, extraIgnore = []) {
@@ -15944,6 +15956,34 @@ ${BLOCK}`, { mode: 493 });
15944
15956
  }
15945
15957
  return { frontmatter: fm, body };
15946
15958
  }
15959
+ function migrateVscodeCommandsLayout(target) {
15960
+ const legacyDir = path2.join(target, ".claude", "commands", "rihal");
15961
+ const newRoot = path2.join(target, ".claude", "commands");
15962
+ if (!fs2.existsSync(legacyDir) || !fs2.statSync(legacyDir).isDirectory()) {
15963
+ return { moved: 0, removed_dir: false };
15964
+ }
15965
+ let moved = 0;
15966
+ for (const entry of fs2.readdirSync(legacyDir)) {
15967
+ const src = path2.join(legacyDir, entry);
15968
+ if (!fs2.statSync(src).isFile() || !entry.endsWith(".md")) continue;
15969
+ const baseName = path2.basename(entry, ".md");
15970
+ const targetName = baseName.startsWith("rihal-") ? entry : `rihal-${entry}`;
15971
+ const dst = path2.join(newRoot, targetName);
15972
+ if (fs2.existsSync(dst)) {
15973
+ fs2.unlinkSync(src);
15974
+ continue;
15975
+ }
15976
+ fs2.renameSync(src, dst);
15977
+ moved++;
15978
+ }
15979
+ let removedDir = false;
15980
+ try {
15981
+ fs2.rmdirSync(legacyDir);
15982
+ removedDir = true;
15983
+ } catch (_) {
15984
+ }
15985
+ return { moved, removed_dir: removedDir };
15986
+ }
15947
15987
  function buildInstallPlan(ide = "claude", target = process.cwd()) {
15948
15988
  if (Array.isArray(ide)) {
15949
15989
  const seen = /* @__PURE__ */ new Set();
@@ -15957,19 +15997,6 @@ ${BLOCK}`, { mode: 493 });
15957
15997
  }
15958
15998
  }
15959
15999
  }
15960
- if (ide.includes("claude") && ide.includes("vscode")) {
15961
- const claudeCommandRels = new Set(
15962
- merged.filter((e) => e.ide === "claude" && e.rel.split(path2.sep).join("/").startsWith(".claude/commands/")).map((e) => path2.basename(e.rel, ".md").replace(/^rihal-/, ""))
15963
- );
15964
- return merged.filter((e) => {
15965
- const rel = e.rel.split(path2.sep).join("/");
15966
- if (e.ide === "vscode" && rel.startsWith(".claude/commands/rihal/")) {
15967
- const baseName = path2.basename(e.rel, path2.extname(e.rel));
15968
- return !claudeCommandRels.has(baseName);
15969
- }
15970
- return true;
15971
- });
15972
- }
15973
16000
  return merged;
15974
16001
  }
15975
16002
  const plan = [];
@@ -16007,7 +16034,7 @@ ${BLOCK}`, { mode: 493 });
16007
16034
  const rel = path2.relative(path2.join(SOURCE_ROOT, "commands"), f);
16008
16035
  const ext = ide === "cursor" ? ".mdc" : ".md";
16009
16036
  const baseName = path2.basename(f, ".md");
16010
- const outName = ide === "claude" ? `rihal-${baseName}${ext}` : baseName + ext;
16037
+ const outName = ide === "claude" || ide === "vscode" ? `rihal-${baseName}${ext}` : baseName + ext;
16011
16038
  plan.push({ src: f, rel: path2.join(relCommands, path2.dirname(rel), outName), ide, cursor: ide === "cursor" });
16012
16039
  }
16013
16040
  const agentRulesDir = path2.join(target, ".rihal", "agents-rules");
@@ -16476,6 +16503,12 @@ ${BLOCK}`, { mode: 493 });
16476
16503
  return 1;
16477
16504
  }
16478
16505
  }
16506
+ if (Array.isArray(opts.ides) && opts.ides.includes("vscode") || opts.ide === "vscode") {
16507
+ const migrated = migrateVscodeCommandsLayout(opts.target);
16508
+ if (migrated.moved > 0) {
16509
+ console.log(` \u21BB Migrated ${migrated.moved} legacy vscode-layout command(s) to .claude/commands/rihal-{name}.md`);
16510
+ }
16511
+ }
16479
16512
  const fullPlan = buildInstallPlan(opts.ides, opts.target);
16480
16513
  const plan = filterPlanByModules(fullPlan, opts.modules);
16481
16514
  if (plan.length === 0) {
@@ -16521,6 +16554,7 @@ ${BLOCK}`, { mode: 493 });
16521
16554
  let copied = 0;
16522
16555
  let skipped = 0;
16523
16556
  let preserved = 0;
16557
+ let updated = 0;
16524
16558
  const preservedFiles = [];
16525
16559
  const preservedDiffs = [];
16526
16560
  const conflictedFiles = [];
@@ -16618,6 +16652,7 @@ ${BLOCK}`, { mode: 493 });
16618
16652
  fs2.writeFileSync(c.destPath, c.sourceContent, "utf8");
16619
16653
  applied++;
16620
16654
  }
16655
+ updated += applied;
16621
16656
  console.log(" " + ok(`Applied v${readPackageVersion()} to ${applied} file${applied === 1 ? "" : "s"}.`));
16622
16657
  } else if (action === "review") {
16623
16658
  let applied = 0, kept = 0;
@@ -16664,6 +16699,7 @@ ${BLOCK}`, { mode: 493 });
16664
16699
  kept++;
16665
16700
  }
16666
16701
  }
16702
+ updated += applied;
16667
16703
  console.log(" " + ok(`Review complete: ${applied} applied, ${kept} kept local.`));
16668
16704
  } else {
16669
16705
  console.log(" " + dim(`${conflictedFiles.length} file${conflictedFiles.length === 1 ? "" : "s"} kept local. Re-run with --force-overwrite or 'rcode update' anytime.`));
@@ -16673,6 +16709,10 @@ ${BLOCK}`, { mode: 493 });
16673
16709
  }
16674
16710
  console.log("");
16675
16711
  }
16712
+ if (updated > 0) {
16713
+ console.log(" " + ok(`Total this run: ${copied} installed \xB7 ${updated} updated \xB7 ${preserved + skipped} unchanged.`));
16714
+ console.log("");
16715
+ }
16676
16716
  if (opts.global) {
16677
16717
  const configDir2 = path2.join(opts.target, ".rihal", "_config");
16678
16718
  ensureDir(configDir2);
@@ -16766,8 +16806,8 @@ ${BLOCK}`, { mode: 493 });
16766
16806
  const match = before.match(re);
16767
16807
  const currentInFile = match ? match[1] === "true" : null;
16768
16808
  if (match && currentInFile !== desired) {
16769
- const updated = before.replace(re, `commit_planning: ${desired}`);
16770
- writeFileAtomic(configPath, updated);
16809
+ const updated2 = before.replace(re, `commit_planning: ${desired}`);
16810
+ writeFileAtomic(configPath, updated2);
16771
16811
  console.log(" " + dim(`Updated commit_planning in config.yaml (${currentInFile} \u2192 ${desired}) \u2014 closes #685.`));
16772
16812
  } else if (!match) {
16773
16813
  const appended = before.replace(/\n*$/, "") + `
@@ -16951,14 +16991,14 @@ commit_planning: ${desired}
16951
16991
  const homeCommands = path2.join(os.homedir(), ".claude/commands");
16952
16992
  const homeSkills = path2.join(os.homedir(), ".claude/skills");
16953
16993
  if (agentCount === 0 && fs2.existsSync(homeAgents)) {
16954
- const n = fs2.readdirSync(homeAgents).filter((f) => f.startsWith("rihal-") && f.endsWith(".md")).length;
16994
+ const n = fs2.readdirSync(homeAgents).filter((f) => (f.startsWith("rihal-") || f.startsWith("rcode-")) && f.endsWith(".md")).length;
16955
16995
  if (n > 0) {
16956
16996
  agentCount = n;
16957
16997
  agentsFromGlobal = true;
16958
16998
  }
16959
16999
  }
16960
17000
  if (commandCount === 0 && fs2.existsSync(homeCommands)) {
16961
- const n = fs2.readdirSync(homeCommands).filter((f) => f.startsWith("rihal-") && f.endsWith(".md")).length;
17001
+ const n = fs2.readdirSync(homeCommands).filter((f) => (f.startsWith("rihal-") || f.startsWith("rcode-")) && f.endsWith(".md")).length;
16962
17002
  if (n > 0) {
16963
17003
  commandCount = n;
16964
17004
  commandsFromGlobal = true;
@@ -16999,6 +17039,12 @@ commit_planning: ${desired}
16999
17039
  console.log(" /rihal-do # interactive command picker");
17000
17040
  console.log(" /rihal-council <q> # multi-agent strategic answer");
17001
17041
  console.log("");
17042
+ if (opts.global || opts.noPrompt) {
17043
+ console.log(` ${dim("Configure interactively (one-time, per project):")}`);
17044
+ console.log(` ${dim("rcode install # pick IDE + planning policy for THIS project")}`);
17045
+ console.log(` ${dim("rcode config # adjust defaults later")}`);
17046
+ console.log("");
17047
+ }
17002
17048
  console.log(dim(" Refresh anytime:"));
17003
17049
  console.log(dim(" npx @hanzlaa/rcode@latest install # pull the latest rcode + brain"));
17004
17050
  console.log(dim(` /rihal-update v${version} # pin rcode to a specific version`));
@@ -17252,6 +17298,8 @@ commit_planning: ${desired}
17252
17298
  module2.exports.buildInstallPlan = buildInstallPlan;
17253
17299
  module2.exports.install = install;
17254
17300
  module2.exports.SUPPORTED_IDES = SUPPORTED_IDES;
17301
+ module2.exports.migrateVscodeCommandsLayout = migrateVscodeCommandsLayout;
17302
+ module2.exports.getPathsForIde = getPathsForIde;
17255
17303
  }
17256
17304
  });
17257
17305
 
@@ -18904,7 +18952,8 @@ var require_agent = __commonJS({
18904
18952
  console.error(`Available: ${available}`);
18905
18953
  process.exit(1);
18906
18954
  }
18907
- const claudeCheck = spawnSync("which", ["claude"], { encoding: "utf8" });
18955
+ const whichCmd = process.platform === "win32" ? "where" : "which";
18956
+ const claudeCheck = spawnSync(whichCmd, ["claude"], { encoding: "utf8" });
18908
18957
  if (claudeCheck.status !== 0) {
18909
18958
  console.error("Error: claude binary not found. Install Claude Code: https://claude.ai/code");
18910
18959
  process.exit(1);
@@ -19143,6 +19192,193 @@ var require_memory_bank = __commonJS({
19143
19192
  }
19144
19193
  });
19145
19194
 
19195
+ // cli/lib/schemas.cjs
19196
+ var require_schemas = __commonJS({
19197
+ "cli/lib/schemas.cjs"(exports2, module2) {
19198
+ var { z } = require_zod();
19199
+ function parseFrontmatter(text) {
19200
+ if (typeof text !== "string" || !text.startsWith("---\n")) {
19201
+ return { frontmatter: {}, body: text || "" };
19202
+ }
19203
+ const end = text.indexOf("\n---\n", 4);
19204
+ if (end === -1) return { frontmatter: {}, body: text };
19205
+ const block = text.slice(4, end);
19206
+ const body = text.slice(end + 5);
19207
+ const fm = {};
19208
+ const lines = block.split("\n");
19209
+ let i = 0;
19210
+ while (i < lines.length) {
19211
+ const raw = lines[i];
19212
+ if (!raw.trim() || raw.trim().startsWith("#")) {
19213
+ i++;
19214
+ continue;
19215
+ }
19216
+ const m = raw.match(/^([A-Za-z0-9_-]+):(.*)$/);
19217
+ if (!m) {
19218
+ i++;
19219
+ continue;
19220
+ }
19221
+ const key = m[1].trim();
19222
+ let inline = m[2].trim();
19223
+ inline = inline.replace(/\s+#.*$/, "").trim();
19224
+ if (inline === ">" || inline === "|" || inline === ">-" || inline === "|-") {
19225
+ const collected = [];
19226
+ i++;
19227
+ while (i < lines.length) {
19228
+ const cont = lines[i];
19229
+ if (cont.trim() === "") {
19230
+ collected.push("");
19231
+ i++;
19232
+ continue;
19233
+ }
19234
+ if (/^\s/.test(cont)) {
19235
+ collected.push(cont.trim());
19236
+ i++;
19237
+ continue;
19238
+ }
19239
+ break;
19240
+ }
19241
+ fm[key] = collected.join(" ").replace(/\s+/g, " ").trim();
19242
+ continue;
19243
+ }
19244
+ if (inline === "") {
19245
+ const items = [];
19246
+ let j = i + 1;
19247
+ while (j < lines.length) {
19248
+ const cont = lines[j];
19249
+ if (cont.trim() === "" || cont.trim().startsWith("#")) {
19250
+ j++;
19251
+ continue;
19252
+ }
19253
+ const li = cont.match(/^\s+-\s+(.*)$/);
19254
+ if (!li) break;
19255
+ let v = li[1].trim();
19256
+ if (v.startsWith('"') && v.endsWith('"')) v = v.slice(1, -1);
19257
+ if (v.startsWith("'") && v.endsWith("'")) v = v.slice(1, -1);
19258
+ items.push(v);
19259
+ j++;
19260
+ }
19261
+ if (items.length) {
19262
+ fm[key] = items;
19263
+ i = j;
19264
+ continue;
19265
+ }
19266
+ fm[key] = "";
19267
+ i++;
19268
+ continue;
19269
+ }
19270
+ if (inline.startsWith('"') && inline.endsWith('"')) inline = inline.slice(1, -1);
19271
+ if (inline.startsWith("'") && inline.endsWith("'")) inline = inline.slice(1, -1);
19272
+ fm[key] = inline;
19273
+ i++;
19274
+ }
19275
+ return { frontmatter: fm, body };
19276
+ }
19277
+ function countTriggerPhrases(fm) {
19278
+ const desc = typeof fm.description === "string" ? fm.description : "";
19279
+ const quoted = desc.match(/"[^"]+"/g) || [];
19280
+ if (quoted.length > 0) return quoted.length;
19281
+ if (Array.isArray(fm.triggers)) {
19282
+ return fm.triggers.filter((t) => typeof t === "string" && t.trim()).length;
19283
+ }
19284
+ if (typeof fm.triggers === "string" && fm.triggers.trim()) {
19285
+ return fm.triggers.split(",").map((s) => s.trim()).filter(Boolean).length;
19286
+ }
19287
+ return 0;
19288
+ }
19289
+ var skillFrontmatterSchema = z.object({
19290
+ name: z.string().min(1, "name is required"),
19291
+ description: z.string().min(1, "description is required")
19292
+ });
19293
+ var NEGATIVE_BOUNDARY_RE = /not for|do not|does not|don't|never\b|audit-only|negative/i;
19294
+ function validateSkillFrontmatter(obj, body = "") {
19295
+ const errors = [];
19296
+ const warnings = [];
19297
+ const parsed = skillFrontmatterSchema.safeParse(obj || {});
19298
+ if (!parsed.success) {
19299
+ for (const issue of parsed.error.issues) {
19300
+ const field = issue.path.length ? issue.path.join(".") : "(root)";
19301
+ errors.push(`${field}: ${issue.message}`);
19302
+ }
19303
+ }
19304
+ if (obj && typeof obj === "object") {
19305
+ const count = countTriggerPhrases(obj);
19306
+ if (count < 5) {
19307
+ errors.push(`too few trigger phrases: found ${count}, need at least 5`);
19308
+ } else if (count > 12) {
19309
+ warnings.push(`many trigger phrases: found ${count}, recommended max is 12`);
19310
+ }
19311
+ const desc = typeof obj.description === "string" ? obj.description : "";
19312
+ const bodyText = typeof body === "string" ? body : "";
19313
+ const hasBoundary = NEGATIVE_BOUNDARY_RE.test(desc) || /##[^\n]*\bnot\b/i.test(bodyText) || /\bdo not (use|include)\b/i.test(bodyText) || /\bdon't touch\b/i.test(bodyText);
19314
+ if (!hasBoundary) {
19315
+ errors.push(
19316
+ 'missing negative-boundary clause (e.g. "Do NOT use for: ..." in the description or a "## Do NOT use" section)'
19317
+ );
19318
+ }
19319
+ }
19320
+ return { ok: errors.length === 0, errors, warnings };
19321
+ }
19322
+ var agentFrontmatterSchema = z.object({
19323
+ name: z.string().min(1, "name is required").refine((v) => v.startsWith("rihal-"), 'name must start with the "rihal-" prefix'),
19324
+ description: z.string().min(1, "description is required"),
19325
+ color: z.string().min(1, "color is required")
19326
+ });
19327
+ function validateAgentFrontmatter(obj) {
19328
+ const errors = [];
19329
+ const parsed = agentFrontmatterSchema.safeParse(obj || {});
19330
+ if (!parsed.success) {
19331
+ for (const issue of parsed.error.issues) {
19332
+ const field = issue.path.length ? issue.path.join(".") : "(root)";
19333
+ errors.push(`${field}: ${issue.message}`);
19334
+ }
19335
+ }
19336
+ if (obj && typeof obj === "object") {
19337
+ const tools = obj.tools;
19338
+ const hasTools = Array.isArray(tools) ? tools.length > 0 : typeof tools === "string" && tools.trim().length > 0;
19339
+ if (!hasTools) {
19340
+ errors.push("tools is required (comma-separated string or array)");
19341
+ }
19342
+ } else {
19343
+ errors.push("tools is required (comma-separated string or array)");
19344
+ }
19345
+ return { ok: errors.length === 0, errors };
19346
+ }
19347
+ var stateSchema = z.object({
19348
+ version: z.union([z.string(), z.number()]),
19349
+ project: z.string().min(1),
19350
+ phases: z.array(z.any()),
19351
+ schema_version: z.number(),
19352
+ current_phase: z.union([z.string(), z.number()]).optional(),
19353
+ current_plan: z.union([z.string(), z.number()]).optional(),
19354
+ current_sprint: z.union([z.string(), z.number()]).nullable().optional(),
19355
+ velocity_history: z.array(z.any()).optional(),
19356
+ milestones: z.array(z.any()).optional()
19357
+ }).passthrough();
19358
+ function validateState(obj) {
19359
+ const errors = [];
19360
+ const parsed = stateSchema.safeParse(obj || {});
19361
+ if (!parsed.success) {
19362
+ for (const issue of parsed.error.issues) {
19363
+ const where = issue.path.length ? issue.path.join(".") : "(root)";
19364
+ errors.push(`${where}: ${issue.message}`);
19365
+ }
19366
+ }
19367
+ return { ok: errors.length === 0, errors };
19368
+ }
19369
+ module2.exports = {
19370
+ parseFrontmatter,
19371
+ countTriggerPhrases,
19372
+ skillFrontmatterSchema,
19373
+ agentFrontmatterSchema,
19374
+ stateSchema,
19375
+ validateSkillFrontmatter,
19376
+ validateAgentFrontmatter,
19377
+ validateState
19378
+ };
19379
+ }
19380
+ });
19381
+
19146
19382
  // cli/doctor.js
19147
19383
  var require_doctor = __commonJS({
19148
19384
  "cli/doctor.js"(exports2, module2) {
@@ -19151,6 +19387,11 @@ var require_doctor = __commonJS({
19151
19387
  var { spawnSync } = require("child_process");
19152
19388
  var { verifyInstall, formatReport } = require_manifest();
19153
19389
  var { checkStaleness } = require_memory_bank();
19390
+ var {
19391
+ parseFrontmatter,
19392
+ validateSkillFrontmatter,
19393
+ validateAgentFrontmatter
19394
+ } = require_schemas();
19154
19395
  function findSkillFiles(dir) {
19155
19396
  const results = [];
19156
19397
  if (!fs2.existsSync(dir)) return results;
@@ -19164,6 +19405,10 @@ var require_doctor = __commonJS({
19164
19405
  }
19165
19406
  return results;
19166
19407
  }
19408
+ function findAgentFiles(dir) {
19409
+ if (!fs2.existsSync(dir)) return [];
19410
+ return fs2.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => path2.join(dir, e.name));
19411
+ }
19167
19412
  function checkCompliance(filePath) {
19168
19413
  const content = fs2.readFileSync(filePath, "utf8");
19169
19414
  const missing = [];
@@ -19346,6 +19591,52 @@ var require_doctor = __commonJS({
19346
19591
  }
19347
19592
  return failing;
19348
19593
  }
19594
+ function runSchemaValidation(packageRoot) {
19595
+ const skillDirs = [
19596
+ path2.join(packageRoot, "rihal/skills/agents"),
19597
+ path2.join(packageRoot, "rihal/skills/actions")
19598
+ ];
19599
+ let totalSkills = 0;
19600
+ let totalAgents = 0;
19601
+ let failing = 0;
19602
+ let warned = 0;
19603
+ for (const dir of skillDirs) {
19604
+ for (const file of findSkillFiles(dir)) {
19605
+ totalSkills++;
19606
+ const { frontmatter, body } = parseFrontmatter(fs2.readFileSync(file, "utf8"));
19607
+ const result = validateSkillFrontmatter(frontmatter, body);
19608
+ const rel = path2.relative(packageRoot, file);
19609
+ if (!result.ok) {
19610
+ failing++;
19611
+ console.log(` \u2717 ${rel}`);
19612
+ for (const err of result.errors) console.log(` ${err}`);
19613
+ }
19614
+ if (result.warnings && result.warnings.length > 0) {
19615
+ warned++;
19616
+ for (const w of result.warnings) console.log(` \u26A0 ${rel}: ${w}`);
19617
+ }
19618
+ }
19619
+ }
19620
+ for (const file of findAgentFiles(path2.join(packageRoot, "rihal/agents"))) {
19621
+ totalAgents++;
19622
+ const { frontmatter } = parseFrontmatter(fs2.readFileSync(file, "utf8"));
19623
+ const result = validateAgentFrontmatter(frontmatter);
19624
+ if (!result.ok) {
19625
+ failing++;
19626
+ const rel = path2.relative(packageRoot, file);
19627
+ console.log(` \u2717 ${rel}`);
19628
+ for (const err of result.errors) console.log(` ${err}`);
19629
+ }
19630
+ }
19631
+ if (failing === 0) {
19632
+ console.log(
19633
+ ` \u2713 ${totalSkills} skill + ${totalAgents} agent frontmatter blocks pass schema validation` + (warned > 0 ? ` (${warned} with advisory warnings)` : "")
19634
+ );
19635
+ } else {
19636
+ console.log(` \u2717 ${failing} artifact(s) failed schema validation`);
19637
+ }
19638
+ return failing;
19639
+ }
19349
19640
  function checkDuplicateInstalls() {
19350
19641
  const os = require("os");
19351
19642
  const home = os.homedir();
@@ -19420,7 +19711,10 @@ Duplicate installations:`);
19420
19711
  console.log(`
19421
19712
  Package compliance:`);
19422
19713
  const complianceFailures = runCompliance(packageRoot);
19423
- const totalFailures = preflightFailures + complianceFailures + duplicateFailures;
19714
+ console.log(`
19715
+ Artifact schema validation:`);
19716
+ const schemaFailures = runSchemaValidation(packageRoot);
19717
+ const totalFailures = preflightFailures + complianceFailures + duplicateFailures + schemaFailures;
19424
19718
  console.log();
19425
19719
  if (totalFailures === 0) {
19426
19720
  console.log(`\u2705 All checks passed.`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "3.4.32",
4
- "description": "rcode — the memory bank for AI-driven SaaS teams. Persistent project context, distinctive engineering personas, and phase-based workflows. Built by Rihal. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
3
+ "version": "3.5.0",
4
+ "description": "rcode — the AI team that never forgets. Persistent memory, specialist agents, and slash commands for AI IDEs. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
7
7
  "rcode": "dist/rcode.js",
@@ -40,13 +40,17 @@
40
40
  "arabic",
41
41
  "ai-methodology",
42
42
  "team-simulation",
43
- "orchestration"
43
+ "orchestration",
44
+ "memory-bank",
45
+ "cursor",
46
+ "planning",
47
+ "subagents"
44
48
  ],
45
49
  "author": {
46
50
  "name": "Hanzla Habib",
47
51
  "url": "https://github.com/hanzlahabib"
48
52
  },
49
- "license": "UNLICENSED",
53
+ "license": "MIT",
50
54
  "repository": {
51
55
  "type": "git",
52
56
  "url": "https://github.com/hanzlahabib/rihal-code.git"
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: rihal-cross-platform-auditor
3
+ description: |
4
+ Cross-platform portability auditor. Detects bash-isms, macOS-only flags
5
+ (BSD sed/awk), hardcoded absolute Unix paths in Node code, Windows path
6
+ separators, and CRLF line endings. Audit-only — never modifies scripts.
7
+ Activates: "cross-platform audit", "bash-isms", "macOS only",
8
+ "Windows compatibility", "portability check".
9
+ Do NOT use for: fixing scripts, frontend RTL, or translation.
10
+ tools: Read, Bash, Grep, Glob
11
+ color: yellow
12
+ ---
13
+
14
+ @.rihal/references/response-style.md
15
+ @rihal/skills/agents/rihal-cross-platform-auditor/SKILL.md
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: rihal-dep-auditor
3
+ description: |
4
+ Dependency health auditor — scans for outdated packages, CVEs, unused
5
+ dependencies, loose version pins, and missing lock files. Audit-only:
6
+ never modifies package.json or runs installs.
7
+ Activates: "audit dependencies", "dep health", "CVE scan", "check packages",
8
+ "outdated deps", "loose pins", "lock file".
9
+ Do NOT use for: installing packages, updating deps, or security penetration testing.
10
+ tools: Read, Bash, Grep, Glob
11
+ color: yellow
12
+ ---
13
+
14
+ @.rihal/references/response-style.md
15
+ @rihal/skills/agents/rihal-dep-auditor/SKILL.md