@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.
- package/AGENTS.md +6 -6
- package/CONTRIBUTING.md +2 -0
- package/LICENSE +21 -0
- package/README.md +66 -403
- package/cli/doctor.js +87 -1
- package/cli/install.js +122 -31
- package/cli/lib/schemas.cjs +318 -0
- package/cli/postinstall.js +19 -3
- package/dist/rcode.js +316 -23
- package/package.json +14 -4
- package/rihal/agents/rihal-cross-platform-auditor.md +1 -1
- package/rihal/agents/rihal-dep-auditor.md +1 -1
- package/rihal/agents/rihal-docs-auditor.md +3 -145
- package/rihal/agents/rihal-i18n-auditor.md +1 -1
- package/rihal/agents/rihal-nyquist-auditor.md +4 -156
- package/rihal/agents/rihal-observability-auditor.md +1 -1
- package/rihal/bin/rihal-hooks.cjs +394 -4
- package/rihal/bin/rihal-tools.cjs +891 -24
- package/rihal/commands/create-prd.md +18 -0
- package/rihal/commands/execute-milestone.md +18 -0
- package/rihal/commands/plan-milestone.md +18 -0
- package/rihal/commands/scaffold-milestone.md +18 -0
- package/rihal/commands/scaffold-skill.md +18 -0
- package/rihal/references/REFERENCES_INDEX.md +49 -7
- package/rihal/references/agent-contracts.md +10 -0
- package/rihal/references/design-tokens.md +98 -0
- package/rihal/references/docs-auditor-playbook.md +148 -0
- package/rihal/references/git-preflight.md +117 -0
- package/rihal/references/iterative-retrieval.md +85 -0
- package/rihal/references/nyquist-auditor-playbook.md +157 -0
- package/rihal/references/workstream-flag.md +2 -2
- package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +9 -0
- package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +9 -0
- package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +4 -0
- package/rihal/skills/actions/4-implementation/rihal-code-review/steps/step-02-review.md +2 -2
- package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +4 -0
- package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +4 -0
- package/rihal/skills/agents/haitham-frontend/SKILL.md +2 -0
- package/rihal/templates/settings-hooks.json +39 -0
- package/rihal/workflows/check-todos.md +4 -0
- package/rihal/workflows/code-review-fix.md +4 -3
- package/rihal/workflows/code-review.md +1 -1
- package/rihal/workflows/debug.md +1 -1
- package/rihal/workflows/dev-story.md +4 -0
- package/rihal/workflows/diff.md +2 -2
- package/rihal/workflows/do.md +16 -8
- package/rihal/workflows/docs-update.md +2 -2
- package/rihal/workflows/enable-hooks.md +6 -1
- package/rihal/workflows/execute-milestone.md +139 -0
- package/rihal/workflows/execute-regression-gates.md +1 -1
- package/rihal/workflows/execute-sprint.md +54 -2
- package/rihal/workflows/execute-verify-phase-goal.md +31 -4
- package/rihal/workflows/execute-waves.md +33 -5
- package/rihal/workflows/execute.md +40 -6
- package/rihal/workflows/help.md +1 -1
- package/rihal/workflows/import.md +1 -1
- package/rihal/workflows/lens-audit.md +39 -23
- package/rihal/workflows/list-workspaces.md +1 -1
- package/rihal/workflows/map-codebase.md +4 -4
- package/rihal/workflows/new-milestone.md +18 -1
- package/rihal/workflows/new-project-research.md +53 -1
- package/rihal/workflows/new-workspace.md +1 -1
- package/rihal/workflows/plan-milestone.md +105 -0
- package/rihal/workflows/plan-research-validation.md +1 -1
- package/rihal/workflows/plan-spawn-planner.md +1 -1
- package/rihal/workflows/plan.md +31 -3
- package/rihal/workflows/plant-seed.md +6 -0
- package/rihal/workflows/quick.md +11 -5
- package/rihal/workflows/research-phase.md +24 -0
- package/rihal/workflows/scaffold-milestone.md +60 -0
- package/rihal/workflows/scaffold-skill.md +137 -0
- package/rihal/workflows/scan.md +1 -1
- package/rihal/workflows/session-report.md +43 -3
- package/rihal/workflows/verify-work.md +3 -3
- package/server/dashboard.js +154 -5
- package/server/lib/html/client/agents-data.js +27 -0
- package/server/lib/html/client/app.js +15 -0
- package/server/lib/html/client/components/App.js +211 -0
- package/server/lib/html/client/components/OrchPanel.js +293 -0
- package/server/lib/html/client/components/Sidebar.js +73 -0
- package/server/lib/html/client/components/Topbar.js +53 -0
- package/server/lib/html/client/components/XtermPanel.js +220 -0
- package/server/lib/html/client/components/shared.js +330 -0
- package/server/lib/html/client/icons-client.js +85 -0
- package/server/lib/html/client/orchestrator.js +279 -0
- package/server/lib/html/client/preact.js +34 -0
- package/server/lib/html/client/store.js +91 -0
- package/server/lib/html/client/util.js +186 -0
- package/server/lib/html/client/views/AgentsView.js +83 -0
- package/server/lib/html/client/views/DecisionsView.js +102 -0
- package/server/lib/html/client/views/FilesView.js +223 -0
- package/server/lib/html/client/views/KanbanView.js +236 -0
- package/server/lib/html/client/views/MemoryView.js +157 -0
- package/server/lib/html/client/views/MilestonesView.js +136 -0
- package/server/lib/html/client/views/OrchestrationView.js +167 -0
- package/server/lib/html/client/views/OverviewView.js +221 -0
- package/server/lib/html/client/views/PhasesView.js +184 -0
- package/server/lib/html/client/views/RoadmapView.js +238 -0
- package/server/lib/html/client/views/SprintsView.js +178 -0
- package/server/lib/html/client/views/TasksView.js +148 -0
- package/server/lib/html/client.js +42 -1064
- package/server/lib/html/css.js +2266 -466
- package/server/lib/html/icons.js +68 -0
- package/server/lib/html/shell.js +16 -210
- package/server/lib/scanner.js +109 -0
- 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
|
+
};
|
package/cli/postinstall.js
CHANGED
|
@@ -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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
});
|