@hanzlaa/rcode 3.4.10 → 3.4.12

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.
package/README.md CHANGED
@@ -100,6 +100,11 @@ Restart Claude Code (or your IDE), type `/`, and every `rihal-*` command appears
100
100
 
101
101
  Update anytime with `npx @hanzlaa/rcode update` (or `/rihal-update` inside a Claude session).
102
102
 
103
+ > **Want `rcode` on your PATH?** `npx @hanzlaa/rcode install` sets up the project files but doesn't put `rcode` in your shell. For the `rcode` CLI command (e.g. `rcode version`, `rcode update`), install globally once:
104
+ > ```bash
105
+ > npm install -g @hanzlaa/rcode
106
+ > ```
107
+
103
108
  ### Then begin the rihla
104
109
 
105
110
  ```
package/cli/digest.js CHANGED
@@ -12,7 +12,12 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
 
14
14
  function normalize(name) {
15
- return name.replace(/^rihal-/, '');
15
+ const stripped = name.replace(/^rihal-/, '');
16
+ // Reject path traversal attempts — names must be simple identifiers
17
+ if (stripped.includes('..') || stripped.includes('/') || stripped.includes('\\')) {
18
+ throw new Error(`Invalid agent name: '${name}'`);
19
+ }
20
+ return stripped;
16
21
  }
17
22
 
18
23
  function listAvailable(digestDir, agentsDir) {
package/cli/index.js CHANGED
@@ -51,7 +51,7 @@ Usage:
51
51
 
52
52
  📦 PROJECT
53
53
  install Install Rihal Code into the current project
54
- (sets up .rihal/, .claude/skills/, .claude/commands/rihal/,
54
+ (sets up .rihal/, .claude/skills/, .claude/commands/,
55
55
  .cursor/rules/, .windsurf/rules/, .antigravity/agents/, AGENTS.md)
56
56
  init Alias for install
57
57
  update Refresh skill files (backs up .rihal/ state first)
package/cli/install.js CHANGED
@@ -1972,6 +1972,10 @@ async function install(opts) {
1972
1972
  console.log(dim(' npx @hanzlaa/rcode@latest install # pull the latest rcode + brain'));
1973
1973
  console.log(dim(` /rihal-update v${version} # pin rcode to a specific version`));
1974
1974
  console.log('');
1975
+ console.log(dim(' Want the rcode CLI on your PATH? (optional — needed for rcode version / rcode update):'));
1976
+ console.log(dim(' npm install -g @hanzlaa/rcode # installs rcode, rihal, rihal-code commands'));
1977
+ console.log(dim(' rcode version # verify'));
1978
+ console.log('');
1975
1979
  console.log(dim(' Customize without losing changes on update:'));
1976
1980
  console.log(dim(' Create <name>.local.md siblings (e.g. .claude/agents/rihal-waleed.local.md)'));
1977
1981
  console.log(dim(' *.local.md files are NEVER touched by install / --force-overwrite / uninstall.'));
@@ -43,7 +43,7 @@ function writeFileAtomic(filePath, content, opts = {}) {
43
43
 
44
44
  let fd;
45
45
  try {
46
- fd = fs.openSync(tmpPath, 'w', mode ?? 0o644);
46
+ fd = fs.openSync(tmpPath, 'wx', mode ?? 0o644);
47
47
  fs.writeSync(fd, content, 0, encoding);
48
48
  // fsync the data to disk before rename — otherwise a crash between
49
49
  // write() and rename() could leave the target renamed but with zero
@@ -18,6 +18,14 @@ const { execSync, spawnSync } = require('child_process');
18
18
 
19
19
  // ---------- Utility: run gh commands safely ----------
20
20
 
21
+ function sanitizeGhOutput(text) {
22
+ if (!text) return '';
23
+ // Strip anything that looks like a token (ghp_*, ghs_*, github_pat_*)
24
+ return text
25
+ .replace(/\b(ghp_|ghs_|github_pat_)[A-Za-z0-9_]{10,}\b/g, '[REDACTED]')
26
+ .slice(0, 2000);
27
+ }
28
+
21
29
  function runGh(args, { input = null, allowFailure = false } = {}) {
22
30
  const result = spawnSync('gh', args, {
23
31
  encoding: 'utf8',
@@ -26,9 +34,8 @@ function runGh(args, { input = null, allowFailure = false } = {}) {
26
34
  });
27
35
 
28
36
  if (result.status !== 0 && !allowFailure) {
29
- throw new Error(
30
- `gh ${args.join(' ')} failed:\n${result.stderr || result.stdout || '(no output)'}`
31
- );
37
+ const detail = sanitizeGhOutput(result.stderr || result.stdout || '(no output)');
38
+ throw new Error(`gh ${args.join(' ')} failed:\n${detail}`);
32
39
  }
33
40
 
34
41
  return {
package/dist/rcode.js CHANGED
@@ -16462,6 +16462,10 @@ ${BLOCK}`);
16462
16462
  console.log(dim(" npx @hanzlaa/rcode@latest install # pull the latest rcode + brain"));
16463
16463
  console.log(dim(` /rihal-update v${version} # pin rcode to a specific version`));
16464
16464
  console.log("");
16465
+ console.log(dim(" Want the rcode CLI on your PATH? (optional \u2014 needed for rcode version / rcode update):"));
16466
+ console.log(dim(" npm install -g @hanzlaa/rcode # installs rcode, rihal, rihal-code commands"));
16467
+ console.log(dim(" rcode version # verify"));
16468
+ console.log("");
16465
16469
  console.log(dim(" Customize without losing changes on update:"));
16466
16470
  console.log(dim(" Create <name>.local.md siblings (e.g. .claude/agents/rihal-waleed.local.md)"));
16467
16471
  console.log(dim(" *.local.md files are NEVER touched by install / --force-overwrite / uninstall."));
@@ -16909,7 +16913,7 @@ var require_fsutil = __commonJS({
16909
16913
  );
16910
16914
  let fd;
16911
16915
  try {
16912
- fd = fs2.openSync(tmpPath, "w", mode ?? 420);
16916
+ fd = fs2.openSync(tmpPath, "wx", mode ?? 420);
16913
16917
  fs2.writeSync(fd, content, 0, encoding);
16914
16918
  fs2.fsyncSync(fd);
16915
16919
  fs2.closeSync(fd);
@@ -17923,7 +17927,11 @@ var require_digest = __commonJS({
17923
17927
  var fs2 = require("fs");
17924
17928
  var path2 = require("path");
17925
17929
  function normalize(name) {
17926
- return name.replace(/^rihal-/, "");
17930
+ const stripped = name.replace(/^rihal-/, "");
17931
+ if (stripped.includes("..") || stripped.includes("/") || stripped.includes("\\")) {
17932
+ throw new Error(`Invalid agent name: '${name}'`);
17933
+ }
17934
+ return stripped;
17927
17935
  }
17928
17936
  function listAvailable(digestDir, agentsDir) {
17929
17937
  const digestNames = fs2.existsSync(digestDir) ? fs2.readdirSync(digestDir).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => f.replace(".md", "")) : [];
@@ -19285,6 +19293,10 @@ var require_show_model = __commonJS({
19285
19293
  var require_github = __commonJS({
19286
19294
  "cli/lib/github.cjs"(exports2, module2) {
19287
19295
  var { execSync, spawnSync } = require("child_process");
19296
+ function sanitizeGhOutput(text) {
19297
+ if (!text) return "";
19298
+ return text.replace(/\b(ghp_|ghs_|github_pat_)[A-Za-z0-9_]{10,}\b/g, "[REDACTED]").slice(0, 2e3);
19299
+ }
19288
19300
  function runGh(args, { input = null, allowFailure = false } = {}) {
19289
19301
  const result = spawnSync("gh", args, {
19290
19302
  encoding: "utf8",
@@ -19292,10 +19304,9 @@ var require_github = __commonJS({
19292
19304
  stdio: input ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
19293
19305
  });
19294
19306
  if (result.status !== 0 && !allowFailure) {
19295
- throw new Error(
19296
- `gh ${args.join(" ")} failed:
19297
- ${result.stderr || result.stdout || "(no output)"}`
19298
- );
19307
+ const detail = sanitizeGhOutput(result.stderr || result.stdout || "(no output)");
19308
+ throw new Error(`gh ${args.join(" ")} failed:
19309
+ ${detail}`);
19299
19310
  }
19300
19311
  return {
19301
19312
  status: result.status,
@@ -20408,7 +20419,7 @@ Usage:
20408
20419
 
20409
20420
  \u{1F4E6} PROJECT
20410
20421
  install Install Rihal Code into the current project
20411
- (sets up .rihal/, .claude/skills/, .claude/commands/rihal/,
20422
+ (sets up .rihal/, .claude/skills/, .claude/commands/,
20412
20423
  .cursor/rules/, .windsurf/rules/, .antigravity/agents/, AGENTS.md)
20413
20424
  init Alias for install
20414
20425
  update Refresh skill files (backs up .rihal/ state first)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "3.4.10",
3
+ "version": "3.4.12",
4
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.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -8,15 +8,6 @@
8
8
  "rihal": "dist/rcode.js",
9
9
  "rihal-code": "dist/rcode.js"
10
10
  },
11
- "scripts": {
12
- "dashboard": "node server/dashboard.js",
13
- "test": "node --test",
14
- "test:ci": "node --test --test-reporter=spec",
15
- "postinstall": "node cli/postinstall.js",
16
- "build:cli": "node scripts/build.cjs",
17
- "build": "node scripts/build.cjs",
18
- "dogfood": "bash scripts/dogfood-check.sh"
19
- },
20
11
  "files": [
21
12
  "cli/",
22
13
  "rihal/",
@@ -69,5 +60,14 @@
69
60
  },
70
61
  "publishConfig": {
71
62
  "access": "public"
63
+ },
64
+ "scripts": {
65
+ "dashboard": "node server/dashboard.js",
66
+ "test": "node --test",
67
+ "test:ci": "node --test --test-reporter=spec",
68
+ "postinstall": "node cli/postinstall.js",
69
+ "build:cli": "node scripts/build.cjs",
70
+ "build": "node scripts/build.cjs",
71
+ "dogfood": "bash scripts/dogfood-check.sh"
72
72
  }
73
- }
73
+ }
@@ -4688,11 +4688,11 @@ function cmdProgress(args) {
4688
4688
  byNum[num] = {
4689
4689
  path: full,
4690
4690
  dirName: entry,
4691
- plan_count: files.filter(f => /PLAN\.md$|-PLAN\.md$|SPRINT\.md$/.test(f)).length,
4691
+ plan_count: files.filter(f => /-SPRINT\.md$/i.test(f)).length,
4692
4692
  summary_count: files.filter(f => /SUMMARY\.md$|-SUMMARY\.md$/.test(f)).length,
4693
4693
  has_research: files.includes('RESEARCH.md'),
4694
4694
  has_context: files.includes('CONTEXT.md'),
4695
- has_verification: files.includes('VERIFICATION.md'),
4695
+ has_verification: files.some(f => /VERIFICATION\.md$/i.test(f)),
4696
4696
  };
4697
4697
  }
4698
4698
  return byNum;
@@ -103,8 +103,8 @@ Auditor returns structured JSON:
103
103
  **If `MODE=phase-status` (Phase 8 / #461):**
104
104
 
105
105
  Spawn `rihal-docs-auditor` with `--mode=phase-status`. Pass:
106
- - `roadmap_phases[]` — output of `node rihal/bin/rihal-tools.cjs roadmap list-phases` (post-#464 fix)
107
- - `phase_dirs[]` — output of `node rihal/bin/rihal-tools.cjs init phase-op N` for each phase number, OR a direct walk of `.planning/phases/*` that captures: dir name, presence of `*-SUMMARY.md`, `*-SPRINT.md`, `*-PLAN.md`, `*-CONTEXT.md`, `*-RESEARCH.md`, `*-VERIFICATION.md`
106
+ - `roadmap_phases[]` — output of `node .rihal/bin/rihal-tools.cjs roadmap list-phases` (post-#464 fix)
107
+ - `phase_dirs[]` — output of `node .rihal/bin/rihal-tools.cjs init phase-op N` for each phase number, OR a direct walk of `.planning/phases/*` that captures: dir name, presence of `*-SUMMARY.md`, `*-SPRINT.md`, `*-PLAN.md`, `*-CONTEXT.md`, `*-RESEARCH.md`, `*-VERIFICATION.md`
108
108
  - For each phase, the most recent commit hash that touches files in `${phase_dir}/` (used as a freshness signal)
109
109
 
110
110
  Auditor returns structured JSON:
@@ -58,12 +58,15 @@ the weighted bar as the primary progress indicator to avoid a misleading `0/N (0
58
58
  For each entry in `SNAPSHOT.phases[]`:
59
59
 
60
60
  - `▶` if `phase.number === SNAPSHOT.current_phase`
61
- - `✓` if `phase.disk.summary_count > 0` AND matches `phase.disk.plan_count` AND `phase.disk.has_verification` (complete + verified; if VERIFICATION.md absent, use `◎` and label "complete-unverified")
61
+ - `✓` if `phase.disk.summary_count > 0` AND `phase.disk.summary_count >= phase.disk.plan_count` AND `phase.disk.has_verification` (complete + verified; if VERIFICATION.md absent, use `◎` and label "complete-unverified")
62
+ - `◎` if `phase.disk.summary_count > 0` AND `phase.disk.summary_count >= phase.disk.plan_count` AND NOT `phase.disk.has_verification` (work done, awaiting verification)
62
63
  - `◆` if `phase.disk.plan_count > phase.disk.summary_count` (executing — has plans, not all summarized)
63
64
  - `◇` if `phase.disk.has_context && !phase.disk.plan_count` (discussing — CONTEXT.md exists but no plan yet)
64
65
  - `◈` if `phase.disk.has_research && !phase.disk.plan_count` (researched — RESEARCH.md but no plan)
65
66
  - `○` otherwise (planned — no artifacts on disk)
66
67
 
68
+ **Edge case — summary without sprint:** If `summary_count > 0` AND `plan_count === 0`, treat as `◎ complete-unverified` (sprint was archived or the phase used an older single-file workflow; summary is evidence of completed work).
69
+
67
70
  ```
68
71
  Phases:
69
72
  ▶ [04] Component compaction — executing (1/3 plans)