@hanzlaa/rcode 3.4.33 → 3.6.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 (106) 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/doctor.js +87 -1
  6. package/cli/install.js +122 -31
  7. package/cli/lib/schemas.cjs +318 -0
  8. package/cli/postinstall.js +19 -3
  9. package/dist/rcode.js +316 -23
  10. package/package.json +14 -4
  11. package/rihal/agents/rihal-cross-platform-auditor.md +1 -1
  12. package/rihal/agents/rihal-dep-auditor.md +1 -1
  13. package/rihal/agents/rihal-docs-auditor.md +3 -145
  14. package/rihal/agents/rihal-i18n-auditor.md +1 -1
  15. package/rihal/agents/rihal-nyquist-auditor.md +4 -156
  16. package/rihal/agents/rihal-observability-auditor.md +1 -1
  17. package/rihal/bin/rihal-hooks.cjs +394 -4
  18. package/rihal/bin/rihal-tools.cjs +891 -24
  19. package/rihal/commands/create-prd.md +18 -0
  20. package/rihal/commands/execute-milestone.md +18 -0
  21. package/rihal/commands/plan-milestone.md +18 -0
  22. package/rihal/commands/scaffold-milestone.md +18 -0
  23. package/rihal/commands/scaffold-skill.md +18 -0
  24. package/rihal/references/REFERENCES_INDEX.md +49 -7
  25. package/rihal/references/agent-contracts.md +10 -0
  26. package/rihal/references/design-tokens.md +98 -0
  27. package/rihal/references/docs-auditor-playbook.md +148 -0
  28. package/rihal/references/git-preflight.md +117 -0
  29. package/rihal/references/iterative-retrieval.md +85 -0
  30. package/rihal/references/nyquist-auditor-playbook.md +157 -0
  31. package/rihal/references/workstream-flag.md +2 -2
  32. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +9 -0
  33. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +9 -0
  34. package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +4 -0
  35. package/rihal/skills/actions/4-implementation/rihal-code-review/steps/step-02-review.md +2 -2
  36. package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +4 -0
  37. package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +4 -0
  38. package/rihal/skills/agents/haitham-frontend/SKILL.md +2 -0
  39. package/rihal/templates/settings-hooks.json +39 -0
  40. package/rihal/workflows/check-todos.md +4 -0
  41. package/rihal/workflows/code-review-fix.md +4 -3
  42. package/rihal/workflows/code-review.md +1 -1
  43. package/rihal/workflows/debug.md +1 -1
  44. package/rihal/workflows/dev-story.md +4 -0
  45. package/rihal/workflows/diff.md +2 -2
  46. package/rihal/workflows/do.md +16 -8
  47. package/rihal/workflows/docs-update.md +2 -2
  48. package/rihal/workflows/enable-hooks.md +6 -1
  49. package/rihal/workflows/execute-milestone.md +139 -0
  50. package/rihal/workflows/execute-regression-gates.md +1 -1
  51. package/rihal/workflows/execute-sprint.md +54 -2
  52. package/rihal/workflows/execute-verify-phase-goal.md +31 -4
  53. package/rihal/workflows/execute-waves.md +33 -5
  54. package/rihal/workflows/execute.md +40 -6
  55. package/rihal/workflows/help.md +1 -1
  56. package/rihal/workflows/import.md +1 -1
  57. package/rihal/workflows/lens-audit.md +39 -23
  58. package/rihal/workflows/list-workspaces.md +1 -1
  59. package/rihal/workflows/map-codebase.md +4 -4
  60. package/rihal/workflows/new-milestone.md +18 -1
  61. package/rihal/workflows/new-project-research.md +53 -1
  62. package/rihal/workflows/new-workspace.md +1 -1
  63. package/rihal/workflows/plan-milestone.md +105 -0
  64. package/rihal/workflows/plan-research-validation.md +1 -1
  65. package/rihal/workflows/plan-spawn-planner.md +1 -1
  66. package/rihal/workflows/plan.md +31 -3
  67. package/rihal/workflows/plant-seed.md +6 -0
  68. package/rihal/workflows/quick.md +11 -5
  69. package/rihal/workflows/research-phase.md +24 -0
  70. package/rihal/workflows/scaffold-milestone.md +60 -0
  71. package/rihal/workflows/scaffold-skill.md +137 -0
  72. package/rihal/workflows/scan.md +1 -1
  73. package/rihal/workflows/session-report.md +43 -3
  74. package/rihal/workflows/verify-work.md +3 -3
  75. package/server/dashboard.js +154 -5
  76. package/server/lib/html/client/agents-data.js +27 -0
  77. package/server/lib/html/client/app.js +15 -0
  78. package/server/lib/html/client/components/App.js +211 -0
  79. package/server/lib/html/client/components/OrchPanel.js +293 -0
  80. package/server/lib/html/client/components/Sidebar.js +73 -0
  81. package/server/lib/html/client/components/Topbar.js +53 -0
  82. package/server/lib/html/client/components/XtermPanel.js +220 -0
  83. package/server/lib/html/client/components/shared.js +330 -0
  84. package/server/lib/html/client/icons-client.js +85 -0
  85. package/server/lib/html/client/orchestrator.js +279 -0
  86. package/server/lib/html/client/preact.js +34 -0
  87. package/server/lib/html/client/store.js +91 -0
  88. package/server/lib/html/client/util.js +186 -0
  89. package/server/lib/html/client/views/AgentsView.js +83 -0
  90. package/server/lib/html/client/views/DecisionsView.js +102 -0
  91. package/server/lib/html/client/views/FilesView.js +223 -0
  92. package/server/lib/html/client/views/KanbanView.js +236 -0
  93. package/server/lib/html/client/views/MemoryView.js +157 -0
  94. package/server/lib/html/client/views/MilestonesView.js +136 -0
  95. package/server/lib/html/client/views/OrchestrationView.js +167 -0
  96. package/server/lib/html/client/views/OverviewView.js +221 -0
  97. package/server/lib/html/client/views/PhasesView.js +184 -0
  98. package/server/lib/html/client/views/RoadmapView.js +238 -0
  99. package/server/lib/html/client/views/SprintsView.js +178 -0
  100. package/server/lib/html/client/views/TasksView.js +148 -0
  101. package/server/lib/html/client.js +42 -1064
  102. package/server/lib/html/css.js +2266 -466
  103. package/server/lib/html/icons.js +68 -0
  104. package/server/lib/html/shell.js +16 -210
  105. package/server/lib/scanner.js +109 -0
  106. package/server/orchestrator.js +362 -0
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Schema validators for rihal-code's own artifacts.
3
+ *
4
+ * Validates SKILL.md frontmatter, agent frontmatter, and `.rihal/state.json`
5
+ * against zod schemas — mirroring the zod usage style in `cli/lib/config.cjs`.
6
+ *
7
+ * Why: today malformed SKILL.md / agent frontmatter is only caught by brittle
8
+ * grep checks (the old AGENTS.md 5-component snippet). Schema validation gives
9
+ * a single, testable enforcement point used by both `cli/doctor.js` and
10
+ * `scripts/dogfood-check.sh`.
11
+ *
12
+ * Each `validate*` function returns `{ ok: boolean, errors: string[] }` so
13
+ * callers can print actionable diagnostics without try/catch noise.
14
+ *
15
+ * Exports only — no side effects.
16
+ */
17
+
18
+ const { z } = require('zod');
19
+
20
+ // ---------- Frontmatter parser ----------
21
+
22
+ /**
23
+ * Extract YAML frontmatter from a markdown document.
24
+ *
25
+ * Handles three value shapes seen in rihal artifacts:
26
+ * - plain scalars: `name: rihal-foo`
27
+ * - folded multiline: `description: >` followed by indented lines
28
+ * - block sequences: `triggers:` followed by ` - "phrase"` lines
29
+ *
30
+ * Kept intentionally minimal — matches the parser used in
31
+ * `test/compliance.test.cjs` but adds folded-scalar + list support, because
32
+ * real SKILL.md files use `description: >` blocks.
33
+ *
34
+ * @param {string} text full file contents
35
+ * @returns {{ frontmatter: object, body: string }}
36
+ */
37
+ function parseFrontmatter(text) {
38
+ if (typeof text !== 'string' || !text.startsWith('---\n')) {
39
+ return { frontmatter: {}, body: text || '' };
40
+ }
41
+ const end = text.indexOf('\n---\n', 4);
42
+ if (end === -1) return { frontmatter: {}, body: text };
43
+ const block = text.slice(4, end);
44
+ const body = text.slice(end + 5);
45
+
46
+ const fm = {};
47
+ const lines = block.split('\n');
48
+ let i = 0;
49
+ while (i < lines.length) {
50
+ const raw = lines[i];
51
+ // Skip blank lines and full-line comments
52
+ if (!raw.trim() || raw.trim().startsWith('#')) {
53
+ i++;
54
+ continue;
55
+ }
56
+ const m = raw.match(/^([A-Za-z0-9_-]+):(.*)$/);
57
+ if (!m) {
58
+ i++;
59
+ continue;
60
+ }
61
+ const key = m[1].trim();
62
+ let inline = m[2].trim();
63
+ // Strip a trailing inline comment from scalar values
64
+ inline = inline.replace(/\s+#.*$/, '').trim();
65
+
66
+ if (inline === '>' || inline === '|' || inline === '>-' || inline === '|-') {
67
+ // Folded / literal block scalar — collect indented continuation lines
68
+ const collected = [];
69
+ i++;
70
+ while (i < lines.length) {
71
+ const cont = lines[i];
72
+ if (cont.trim() === '') {
73
+ collected.push('');
74
+ i++;
75
+ continue;
76
+ }
77
+ if (/^\s/.test(cont)) {
78
+ collected.push(cont.trim());
79
+ i++;
80
+ continue;
81
+ }
82
+ break;
83
+ }
84
+ fm[key] = collected.join(' ').replace(/\s+/g, ' ').trim();
85
+ continue;
86
+ }
87
+
88
+ if (inline === '') {
89
+ // Possible block sequence — collect `- item` continuation lines
90
+ const items = [];
91
+ let j = i + 1;
92
+ while (j < lines.length) {
93
+ const cont = lines[j];
94
+ if (cont.trim() === '' || cont.trim().startsWith('#')) {
95
+ j++;
96
+ continue;
97
+ }
98
+ const li = cont.match(/^\s+-\s+(.*)$/);
99
+ if (!li) break;
100
+ let v = li[1].trim();
101
+ if (v.startsWith('"') && v.endsWith('"')) v = v.slice(1, -1);
102
+ if (v.startsWith("'") && v.endsWith("'")) v = v.slice(1, -1);
103
+ items.push(v);
104
+ j++;
105
+ }
106
+ if (items.length) {
107
+ fm[key] = items;
108
+ i = j;
109
+ continue;
110
+ }
111
+ // Empty key with no list — store empty string
112
+ fm[key] = '';
113
+ i++;
114
+ continue;
115
+ }
116
+
117
+ // Plain scalar
118
+ if (inline.startsWith('"') && inline.endsWith('"')) inline = inline.slice(1, -1);
119
+ if (inline.startsWith("'") && inline.endsWith("'")) inline = inline.slice(1, -1);
120
+ fm[key] = inline;
121
+ i++;
122
+ }
123
+ return { frontmatter: fm, body };
124
+ }
125
+
126
+ // ---------- Trigger-phrase counting ----------
127
+
128
+ /**
129
+ * Count distinct activation/trigger phrases for a skill.
130
+ *
131
+ * The 5–12 standard applies to the curated activation phrases quoted in the
132
+ * `description` body, so that is the canonical source. Real SKILL.md files
133
+ * also carry a much larger multilingual `triggers` array (English + Arabic,
134
+ * often 20+ entries) — that list is NOT subject to the 5–12 cap. Only when a
135
+ * skill has no quoted phrases in its description do we fall back to counting
136
+ * the `triggers` field.
137
+ *
138
+ * @param {object} fm parsed frontmatter
139
+ * @returns {number}
140
+ */
141
+ function countTriggerPhrases(fm) {
142
+ const desc = typeof fm.description === 'string' ? fm.description : '';
143
+ const quoted = desc.match(/"[^"]+"/g) || [];
144
+ if (quoted.length > 0) return quoted.length;
145
+
146
+ if (Array.isArray(fm.triggers)) {
147
+ return fm.triggers.filter((t) => typeof t === 'string' && t.trim()).length;
148
+ }
149
+ if (typeof fm.triggers === 'string' && fm.triggers.trim()) {
150
+ // Comma-separated fallback
151
+ return fm.triggers.split(',').map((s) => s.trim()).filter(Boolean).length;
152
+ }
153
+ return 0;
154
+ }
155
+
156
+ // ---------- SKILL.md frontmatter ----------
157
+
158
+ const skillFrontmatterSchema = z.object({
159
+ name: z.string().min(1, 'name is required'),
160
+ description: z.string().min(1, 'description is required'),
161
+ });
162
+
163
+ // Negative-boundary signal — an explicit statement of what a skill does NOT
164
+ // do. Two conventions exist in the package: agent-persona skills carry it in
165
+ // the frontmatter `description` ("Do NOT use for: ..."), action skills carry
166
+ // it as a body section ("## Do NOT use this skill for"). Either satisfies it.
167
+ const NEGATIVE_BOUNDARY_RE = /not for|do not|does not|don't|never\b|audit-only|negative/i;
168
+
169
+ /**
170
+ * Validate a parsed SKILL.md frontmatter object.
171
+ *
172
+ * Enforces the 5-component skill standard's frontmatter slice:
173
+ * - `name` and `description` present (hard error if missing).
174
+ * - at least 5 trigger phrases — fewer is a hard error.
175
+ * - a negative-boundary clause — hard error if absent. The clause may live
176
+ * in the frontmatter `description` OR in the skill body (passed via the
177
+ * optional `body` argument); either location satisfies the rule.
178
+ * - more than 12 trigger phrases is a non-blocking WARNING: many shipped
179
+ * skills carry a deliberately broad multilingual activation set, so the
180
+ * upper bound is advisory rather than a build-breaker.
181
+ *
182
+ * @param {object} obj parsed frontmatter
183
+ * @param {string} [body] optional skill body — checked for a body-level
184
+ * negative-boundary section when the description lacks one
185
+ * @returns {{ ok: boolean, errors: string[], warnings: string[] }}
186
+ */
187
+ function validateSkillFrontmatter(obj, body = '') {
188
+ const errors = [];
189
+ const warnings = [];
190
+ const parsed = skillFrontmatterSchema.safeParse(obj || {});
191
+ if (!parsed.success) {
192
+ for (const issue of parsed.error.issues) {
193
+ const field = issue.path.length ? issue.path.join('.') : '(root)';
194
+ errors.push(`${field}: ${issue.message}`);
195
+ }
196
+ }
197
+
198
+ if (obj && typeof obj === 'object') {
199
+ const count = countTriggerPhrases(obj);
200
+ if (count < 5) {
201
+ errors.push(`too few trigger phrases: found ${count}, need at least 5`);
202
+ } else if (count > 12) {
203
+ warnings.push(`many trigger phrases: found ${count}, recommended max is 12`);
204
+ }
205
+
206
+ const desc = typeof obj.description === 'string' ? obj.description : '';
207
+ const bodyText = typeof body === 'string' ? body : '';
208
+ // Satisfied by a boundary phrase in the description, a "## Do NOT use"-style
209
+ // heading, or an explicit "Do NOT include:" exclusion list in the body.
210
+ const hasBoundary =
211
+ NEGATIVE_BOUNDARY_RE.test(desc) ||
212
+ /##[^\n]*\bnot\b/i.test(bodyText) ||
213
+ /\bdo not (use|include)\b/i.test(bodyText) ||
214
+ /\bdon't touch\b/i.test(bodyText);
215
+ if (!hasBoundary) {
216
+ errors.push(
217
+ 'missing negative-boundary clause (e.g. "Do NOT use for: ..." in the description or a "## Do NOT use" section)',
218
+ );
219
+ }
220
+ }
221
+
222
+ return { ok: errors.length === 0, errors, warnings };
223
+ }
224
+
225
+ // ---------- Agent frontmatter ----------
226
+
227
+ const agentFrontmatterSchema = z.object({
228
+ name: z
229
+ .string()
230
+ .min(1, 'name is required')
231
+ .refine((v) => v.startsWith('rihal-'), 'name must start with the "rihal-" prefix'),
232
+ description: z.string().min(1, 'description is required'),
233
+ color: z.string().min(1, 'color is required'),
234
+ });
235
+
236
+ /**
237
+ * Validate a parsed agent (`rihal/agents/*.md`) frontmatter object.
238
+ *
239
+ * Requires `name` (rihal- prefixed), `description`, `tools` (comma-list or
240
+ * array), and `color`.
241
+ *
242
+ * @param {object} obj parsed frontmatter
243
+ * @returns {{ ok: boolean, errors: string[] }}
244
+ */
245
+ function validateAgentFrontmatter(obj) {
246
+ const errors = [];
247
+ const parsed = agentFrontmatterSchema.safeParse(obj || {});
248
+ if (!parsed.success) {
249
+ for (const issue of parsed.error.issues) {
250
+ const field = issue.path.length ? issue.path.join('.') : '(root)';
251
+ errors.push(`${field}: ${issue.message}`);
252
+ }
253
+ }
254
+
255
+ if (obj && typeof obj === 'object') {
256
+ const tools = obj.tools;
257
+ const hasTools = Array.isArray(tools)
258
+ ? tools.length > 0
259
+ : typeof tools === 'string' && tools.trim().length > 0;
260
+ if (!hasTools) {
261
+ errors.push('tools is required (comma-separated string or array)');
262
+ }
263
+ } else {
264
+ errors.push('tools is required (comma-separated string or array)');
265
+ }
266
+
267
+ return { ok: errors.length === 0, errors };
268
+ }
269
+
270
+ // ---------- state.json ----------
271
+
272
+ // NOTE: keep this schema coordinated with `.rihal/references/state-schema.md`
273
+ // and the canonical shape written by rihal-tools.cjs — see issue #735
274
+ // (coordinate state.json schema across producers/consumers). Optional and
275
+ // unknown keys are permitted on purpose so state.json can evolve without
276
+ // breaking this validator; only the load-bearing top-level keys are required.
277
+ const stateSchema = z
278
+ .object({
279
+ version: z.union([z.string(), z.number()]),
280
+ project: z.string().min(1),
281
+ phases: z.array(z.any()),
282
+ schema_version: z.number(),
283
+ current_phase: z.union([z.string(), z.number()]).optional(),
284
+ current_plan: z.union([z.string(), z.number()]).optional(),
285
+ current_sprint: z.union([z.string(), z.number()]).nullable().optional(),
286
+ velocity_history: z.array(z.any()).optional(),
287
+ milestones: z.array(z.any()).optional(),
288
+ })
289
+ .passthrough();
290
+
291
+ /**
292
+ * Validate the top-level shape of `.rihal/state.json`.
293
+ *
294
+ * @param {object} obj parsed state.json
295
+ * @returns {{ ok: boolean, errors: string[] }}
296
+ */
297
+ function validateState(obj) {
298
+ const errors = [];
299
+ const parsed = stateSchema.safeParse(obj || {});
300
+ if (!parsed.success) {
301
+ for (const issue of parsed.error.issues) {
302
+ const where = issue.path.length ? issue.path.join('.') : '(root)';
303
+ errors.push(`${where}: ${issue.message}`);
304
+ }
305
+ }
306
+ return { ok: errors.length === 0, errors };
307
+ }
308
+
309
+ module.exports = {
310
+ parseFrontmatter,
311
+ countTriggerPhrases,
312
+ skillFrontmatterSchema,
313
+ agentFrontmatterSchema,
314
+ stateSchema,
315
+ validateSkillFrontmatter,
316
+ validateAgentFrontmatter,
317
+ validateState,
318
+ };
@@ -70,10 +70,26 @@ if (isGlobalInstall(process.env, __dirname, process.cwd())) {
70
70
  });
71
71
  child.on('close', (code) => {
72
72
  if (code === 0) {
73
- console.log(`\n✓ Rihal commands + skills installed globally ${globalTarget}`);
74
- console.log(' All /rihal-* commands are now available in every project.\n');
73
+ // #662 npm 10+ hides postinstall stdout by default. Emit to stderr
74
+ // (npm shows it) AND write a receipt file rcode doctor/version can
75
+ // surface later for users who never saw the original line.
76
+ const receiptLine = `✓ rcode installed → ${globalTarget} (run 'rcode install' in a project to configure)`;
77
+ process.stderr.write('\n' + receiptLine + '\n');
78
+ process.stderr.write(' All /rihal-* commands are now available in every project.\n\n');
79
+ try {
80
+ const fs = require('fs');
81
+ const receiptPath = path.join(globalTarget, '.rihal-install-receipt');
82
+ const receipt = {
83
+ ts: new Date().toISOString(),
84
+ target: globalTarget,
85
+ source: 'postinstall',
86
+ message: receiptLine,
87
+ };
88
+ fs.mkdirSync(globalTarget, { recursive: true });
89
+ fs.writeFileSync(receiptPath, JSON.stringify(receipt, null, 2) + '\n');
90
+ } catch (_) { /* receipt is best-effort, never fail postinstall */ }
75
91
  } else {
76
- console.warn(`\n⚠ Global auto-install exited with code ${code}. Run 'rcode install' manually if needed.\n`);
92
+ process.stderr.write(`\n⚠ Global auto-install exited with code ${code}. Run 'rcode install' manually if needed.\n\n`);
77
93
  }
78
94
  printWelcome();
79
95
  });