@hanzlaa/rcode 2.2.0 → 2.3.2
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/CLAUDE.md +15 -0
- package/CONTRIBUTING.md +138 -0
- package/README.md +74 -15
- package/cli/install.js +312 -80
- package/cli/postinstall.js +4 -4
- package/cli/uninstall.js +8 -0
- package/dist/rcode.js +19777 -0
- package/package.json +18 -5
- package/rihal/DOCS-AUDIT.md +14 -0
- package/rihal/agents/rihal-code-reviewer.md +1 -1
- package/rihal/agents/rihal-codebase-mapper.md +1 -1
- package/rihal/agents/rihal-docs-auditor.md +1 -1
- package/rihal/agents/rihal-edge-case-hunter.md +1 -1
- package/rihal/agents/rihal-executor.md +2 -1
- package/rihal/agents/rihal-hussain-pm.md +1 -0
- package/rihal/agents/rihal-nyquist-auditor.md +1 -1
- package/rihal/agents/rihal-phase-researcher.md +2 -2
- package/rihal/agents/rihal-planner.md +3 -2
- package/rihal/agents/rihal-roadmapper.md +1 -0
- package/rihal/agents/rihal-security-adversary.md +1 -1
- package/rihal/agents/rihal-security-auditor.md +1 -1
- package/rihal/agents/rihal-sprint-checker.md +1 -1
- package/rihal/agents/rihal-verifier.md +1 -1
- package/rihal/bin/lib/roadmap.cjs +2 -3
- package/rihal/bin/rihal-tools.cjs +11 -31
- package/rihal/brain/best-practices/no-theoretical-suggestions.md +56 -0
- package/rihal/commands/add-phase.md +2 -2
- package/rihal/commands/audit.md +8 -0
- package/rihal/commands/checkpoint-preview.md +13 -0
- package/rihal/commands/cleanup.md +2 -2
- package/rihal/commands/config.md +4 -4
- package/rihal/commands/pr-branch.md +2 -2
- package/rihal/commands/prfaq.md +15 -0
- package/rihal/commands/remove-phase.md +2 -2
- package/rihal/commands/research-phase.md +2 -2
- package/rihal/commands/settings.md +2 -2
- package/rihal/commands/ship.md +15 -3
- package/rihal/commands/validate-phase.md +1 -1
- package/rihal/commands/verify-phase.md +2 -2
- package/rihal/references/agent-contracts.md +12 -0
- package/rihal/references/karpathy-guidelines-full.md +79 -0
- package/rihal/references/karpathy-guidelines.md +8 -76
- package/rihal/references/model-profile-resolution.md +8 -0
- package/rihal/references/phase-argument-parsing.md +11 -0
- package/rihal/references/revision-loop.md +11 -0
- package/rihal/references/universal-anti-patterns.md +15 -0
- package/rihal/skills/actions/1-analysis/research/rihal-domain-research/SKILL.md +11 -0
- package/rihal/skills/actions/1-analysis/research/rihal-market-research/SKILL.md +11 -0
- package/rihal/skills/actions/1-analysis/research/rihal-technical-research/SKILL.md +13 -0
- package/rihal/skills/actions/1-analysis/rihal-document-project/SKILL.md +11 -0
- package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +12 -0
- package/rihal/skills/actions/1-analysis/rihal-product-brief/SKILL.md +7 -0
- package/rihal/skills/actions/2-plan/rihal-create-epics-and-stories/SKILL.md +15 -1
- package/rihal/skills/actions/2-plan/rihal-create-milestone/SKILL.md +21 -1
- package/rihal/skills/actions/2-plan/rihal-create-milestone/steps/step-10-complete.md +1 -1
- package/rihal/skills/actions/2-plan/rihal-create-prd/SKILL.md +26 -0
- package/rihal/skills/actions/2-plan/rihal-create-story/SKILL.md +16 -2
- package/rihal/skills/actions/2-plan/rihal-create-ux-design/SKILL.md +12 -0
- package/rihal/skills/actions/2-plan/rihal-edit-prd/SKILL.md +11 -0
- package/rihal/skills/actions/2-plan/rihal-frontend-design/SKILL.md +13 -0
- package/rihal/skills/actions/2-plan/rihal-validate-prd/SKILL.md +12 -0
- package/rihal/skills/actions/3-solutioning/rihal-check-implementation-readiness/SKILL.md +12 -0
- package/rihal/skills/actions/3-solutioning/rihal-create-architecture/SKILL.md +14 -0
- package/rihal/skills/actions/3-solutioning/rihal-generate-project-context/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +16 -0
- package/rihal/skills/actions/4-implementation/rihal-code-review/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-correct-course/SKILL.md +13 -0
- package/rihal/skills/actions/4-implementation/rihal-dev-story/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-qa-generate-e2e-tests/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-retrospective/SKILL.md +11 -0
- package/rihal/skills/actions/4-implementation/rihal-scaffold-project/SKILL.md +10 -0
- package/rihal/skills/actions/4-implementation/rihal-sprint-planning/SKILL.md +14 -1
- package/rihal/skills/actions/4-implementation/rihal-sprint-status/SKILL.md +10 -0
- package/rihal/skills/agents/ahmed-hassani-director/SKILL.md +13 -1
- package/rihal/skills/agents/fatima-qa/SKILL.md +14 -1
- package/rihal/skills/agents/haitham-frontend/SKILL.md +15 -1
- package/rihal/skills/agents/hanzla-engineer/SKILL.md +14 -1
- package/rihal/skills/agents/hussain-pm/SKILL.md +22 -1
- package/rihal/skills/agents/hussain-sm/SKILL.md +22 -1
- package/rihal/skills/agents/layla-designer/SKILL.md +15 -1
- package/rihal/skills/agents/majlis-council/SKILL.md +14 -1
- package/rihal/skills/agents/mariam-marketing/SKILL.md +15 -1
- package/rihal/skills/agents/nasser-eng-manager/SKILL.md +14 -1
- package/rihal/skills/agents/noor-writer/SKILL.md +14 -1
- package/rihal/skills/agents/raees-orchestrator/SKILL.md +13 -1
- package/rihal/skills/agents/sadiq-analyst/SKILL.md +15 -1
- package/rihal/skills/agents/waleed-architect/SKILL.md +16 -1
- package/rihal/skills/agents/yousef-backend/SKILL.md +16 -1
- package/rihal/skills/agents/zahra-branding/SKILL.md +15 -1
- package/rihal/skills/agents/zayd-ml/SKILL.md +17 -1
- package/rihal/skills/core/rihal-advanced-elicitation/SKILL.md +12 -0
- package/rihal/skills/core/rihal-brainstorming/SKILL.md +16 -0
- package/rihal/skills/core/rihal-clone-website/SKILL.md +21 -0
- package/rihal/skills/core/rihal-distillator/SKILL.md +8 -0
- package/rihal/skills/core/rihal-editorial-review-prose/SKILL.md +12 -0
- package/rihal/skills/core/rihal-editorial-review-structure/SKILL.md +18 -0
- package/rihal/skills/core/rihal-help/SKILL.md +12 -0
- package/rihal/skills/core/rihal-index-docs/SKILL.md +18 -0
- package/rihal/skills/core/rihal-init/SKILL.md +8 -0
- package/rihal/skills/core/rihal-party-mode/SKILL.md +14 -0
- package/rihal/skills/core/rihal-review-adversarial-general/SKILL.md +12 -0
- package/rihal/skills/core/rihal-review-edge-case-hunter/SKILL.md +18 -0
- package/rihal/skills/core/rihal-shard-doc/SKILL.md +18 -0
- package/rihal/team.yaml +205 -0
- package/rihal/templates/UAT.md +29 -0
- package/rihal/templates/milestone.md +2 -0
- package/rihal/templates/sprint.md +11 -28
- package/rihal/templates/summary.md +30 -0
- package/rihal/templates/verification-report.md +28 -0
- package/rihal/workflows/audit-milestone.md +34 -2
- package/rihal/workflows/audit.md +172 -0
- package/rihal/workflows/autonomous.md +67 -0
- package/rihal/workflows/checkpoint-preview.md +7 -0
- package/rihal/workflows/council.md +3 -1
- package/rihal/workflows/debug.md +8 -1
- package/rihal/workflows/diagnose-issues.md +34 -0
- package/rihal/workflows/do.md +47 -3
- package/rihal/workflows/document-project.md +1 -1
- package/rihal/workflows/execute-sprint.md +11 -4
- package/rihal/workflows/execute.md +9 -3
- package/rihal/workflows/help.md +1 -1
- package/rihal/workflows/karpathy-audit.md +7 -14
- package/rihal/workflows/pause-work.md +7 -1
- package/rihal/workflows/prfaq.md +7 -0
- package/rihal/workflows/profile-user.md +2 -2
- package/rihal/workflows/settings.md +116 -117
- package/rihal/workflows/ship.md +31 -1
- package/rihal/workflows/sprint-planning.md +39 -8
- package/rihal/workflows/status.md +5 -0
- package/rihal/workflows/ui-phase.md +3 -3
- package/rihal/workflows/update.md +80 -22
- package/rihal/workflows/validate-phase.md +7 -1
- package/server/dashboard.js +34 -575
- package/server/lib/api.js +123 -0
- package/server/lib/html/client.js +969 -0
- package/server/lib/html/css.js +416 -0
- package/server/lib/html/shell.js +230 -0
- package/server/lib/scanner.js +142 -0
- package/rihal/agents/rihal-ui-designer.md +0 -6
- package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/SKILL.md +0 -148
- package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/methods.csv +0 -51
- package/rihal/skills/core/rihal-shard-doc/rihal-shard-doc/SKILL.md +0 -122
- package/rihal/workflows/config.md +0 -105
package/cli/install.js
CHANGED
|
@@ -32,18 +32,22 @@
|
|
|
32
32
|
* .planning/
|
|
33
33
|
* council-sessions/ (empty dir, populated on first council run)
|
|
34
34
|
*
|
|
35
|
-
*
|
|
35
|
+
* Bundled packages (devDeps, inlined by esbuild in dist/rcode.js):
|
|
36
|
+
* picocolors, nanospinner, fast-glob, zod, semver, diff
|
|
36
37
|
*
|
|
37
38
|
* Usage:
|
|
38
39
|
* node cli/install.js [target-project-dir]
|
|
39
40
|
* node cli/install.js --help
|
|
40
41
|
*
|
|
41
42
|
* Flags:
|
|
42
|
-
* --force
|
|
43
|
-
* --yes
|
|
44
|
-
* --user <name>
|
|
45
|
-
* --project <name>
|
|
46
|
-
* --language <lang>
|
|
43
|
+
* --force overwrite existing files without prompting
|
|
44
|
+
* --yes non-interactive, accept defaults
|
|
45
|
+
* --user <name> set user_name in config.yaml (default: $USER)
|
|
46
|
+
* --project <name> set project_name in config.yaml (default: basename of target)
|
|
47
|
+
* --language <lang> set communication_language (default: English)
|
|
48
|
+
* --show-diff print full unified diff for preserved files during update
|
|
49
|
+
* --diff-stat print +N -N summary for preserved files (default on update)
|
|
50
|
+
* --accept-all overwrite all user-modified files with source version
|
|
47
51
|
*/
|
|
48
52
|
|
|
49
53
|
const fs = require('fs');
|
|
@@ -51,9 +55,51 @@ const path = require('path');
|
|
|
51
55
|
const crypto = require('crypto');
|
|
52
56
|
const os = require('os');
|
|
53
57
|
|
|
58
|
+
// Bundled packages — devDeps inlined by esbuild, loaded from node_modules in dev.
|
|
59
|
+
const pc = require('picocolors');
|
|
60
|
+
const { createSpinner } = require('nanospinner');
|
|
61
|
+
const fg = require('fast-glob');
|
|
62
|
+
const { z } = require('zod');
|
|
63
|
+
const semver = require('semver');
|
|
64
|
+
const { createTwoFilesPatch } = require('diff');
|
|
65
|
+
|
|
66
|
+
// Output helpers: always respect NO_COLOR / non-TTY (picocolors handles this).
|
|
67
|
+
const ok = (s) => pc.green('✓') + ' ' + s;
|
|
68
|
+
const fail = (s) => pc.red('✗') + ' ' + s;
|
|
69
|
+
const warn = (s) => pc.yellow('⚠') + ' ' + s;
|
|
70
|
+
const info = (s) => pc.cyan('→') + ' ' + s;
|
|
71
|
+
const dim = (s) => pc.dim(s);
|
|
72
|
+
const bold = (s) => pc.bold(s);
|
|
73
|
+
|
|
54
74
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
55
75
|
const SOURCE_ROOT = path.join(PACKAGE_ROOT, 'rihal');
|
|
56
76
|
|
|
77
|
+
// Zod schema for .rihal/config.yaml validation (#250).
|
|
78
|
+
const ConfigSchema = z.object({
|
|
79
|
+
user_name: z.string().min(1),
|
|
80
|
+
project_name: z.string().min(1),
|
|
81
|
+
communication_language: z.string().default('English'),
|
|
82
|
+
mode: z.enum(['guided', 'yolo'], {
|
|
83
|
+
errorMap: () => ({ message: 'expected "guided" or "yolo"' }),
|
|
84
|
+
}).default('guided'),
|
|
85
|
+
model_profile: z.string().optional(),
|
|
86
|
+
commit_planning: z.boolean().optional(),
|
|
87
|
+
rihal_source_path: z.string().optional(),
|
|
88
|
+
workflow: z.object({
|
|
89
|
+
research_by_default: z.boolean().optional(),
|
|
90
|
+
plan_checker: z.boolean().optional(),
|
|
91
|
+
post_execute_gates: z.boolean().optional(),
|
|
92
|
+
ui_safety_gate: z.boolean().optional(),
|
|
93
|
+
nyquist_validation: z.boolean().optional(),
|
|
94
|
+
}).optional(),
|
|
95
|
+
output: z.object({
|
|
96
|
+
verbose: z.boolean().optional(),
|
|
97
|
+
}).optional(),
|
|
98
|
+
git: z.object({
|
|
99
|
+
branching_strategy: z.string().optional(),
|
|
100
|
+
}).optional(),
|
|
101
|
+
}).passthrough();
|
|
102
|
+
|
|
57
103
|
/**
|
|
58
104
|
* Parse command-line args into a normalized options object.
|
|
59
105
|
*/
|
|
@@ -74,6 +120,16 @@ function parseArgs(argv) {
|
|
|
74
120
|
// #189 — planning commit policy. null = ask interactively (or default true under --yes).
|
|
75
121
|
// Set true by --commit-planning, false by --no-commit-planning or --ignore-planning.
|
|
76
122
|
commitPlanning: null,
|
|
123
|
+
// #232 — non-destructive update. Preserves files the user modified after install.
|
|
124
|
+
nonDestructive: false,
|
|
125
|
+
// #232 — force-overwrite always wins.
|
|
126
|
+
forceOverwrite: false,
|
|
127
|
+
// #251 — diff display flags
|
|
128
|
+
showDiff: false,
|
|
129
|
+
diffStat: false,
|
|
130
|
+
acceptAll: false,
|
|
131
|
+
// #252 — skip update-notifier check
|
|
132
|
+
noUpdateCheck: false,
|
|
77
133
|
};
|
|
78
134
|
const positional = [];
|
|
79
135
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -90,6 +146,12 @@ function parseArgs(argv) {
|
|
|
90
146
|
else if (arg === '--module') opts.modules.push(argv[++i]);
|
|
91
147
|
else if (arg === '--commit-planning') opts.commitPlanning = true;
|
|
92
148
|
else if (arg === '--no-commit-planning' || arg === '--ignore-planning') opts.commitPlanning = false;
|
|
149
|
+
else if (arg === '--non-destructive') opts.nonDestructive = true;
|
|
150
|
+
else if (arg === '--force-overwrite') opts.forceOverwrite = true;
|
|
151
|
+
else if (arg === '--show-diff') opts.showDiff = true; // #251 full unified diff
|
|
152
|
+
else if (arg === '--diff-stat') opts.diffStat = true; // #251 +N -N summary (default)
|
|
153
|
+
else if (arg === '--accept-all') opts.acceptAll = true; // #251 overwrite all preserved
|
|
154
|
+
else if (arg === '--no-update-check') opts.noUpdateCheck = true; // #252
|
|
93
155
|
else if (!arg.startsWith('--')) positional.push(arg);
|
|
94
156
|
}
|
|
95
157
|
if (positional[0]) {
|
|
@@ -188,17 +250,32 @@ function getPathsForIde(ide, target) {
|
|
|
188
250
|
}
|
|
189
251
|
|
|
190
252
|
/**
|
|
191
|
-
*
|
|
253
|
+
* Walk a directory and return absolute file paths. Uses fast-glob so
|
|
254
|
+
* symlink cycles are never followed and patterns can be excluded via
|
|
255
|
+
* .rihalignore files (#249).
|
|
192
256
|
*/
|
|
193
|
-
function walkFiles(dir) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
257
|
+
function walkFiles(dir, extraIgnore = []) {
|
|
258
|
+
if (!fs.existsSync(dir)) return [];
|
|
259
|
+
return fg.sync('**/*', {
|
|
260
|
+
cwd: dir,
|
|
261
|
+
dot: true,
|
|
262
|
+
onlyFiles: true,
|
|
263
|
+
followSymbolicLinks: false,
|
|
264
|
+
ignore: extraIgnore,
|
|
265
|
+
}).map((rel) => path.join(dir, rel));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Read .rihalignore patterns from a given root directory.
|
|
270
|
+
* Returns an array of glob-style ignore patterns (same syntax as .gitignore).
|
|
271
|
+
*/
|
|
272
|
+
function readRihalIgnore(root) {
|
|
273
|
+
const ignoreFile = path.join(root, '.rihalignore');
|
|
274
|
+
if (!fs.existsSync(ignoreFile)) return [];
|
|
275
|
+
return fs.readFileSync(ignoreFile, 'utf8')
|
|
276
|
+
.split('\n')
|
|
277
|
+
.map((l) => l.trim())
|
|
278
|
+
.filter((l) => l && !l.startsWith('#'));
|
|
202
279
|
}
|
|
203
280
|
|
|
204
281
|
function sha256(buffer) {
|
|
@@ -817,13 +894,18 @@ function readPackageVersion() {
|
|
|
817
894
|
function generateInstallManifest(opts) {
|
|
818
895
|
const version = readPackageVersion();
|
|
819
896
|
const newModules = opts.modules.length > 0 ? opts.modules : listAvailableModules();
|
|
820
|
-
// Merge with existing manifest if present
|
|
897
|
+
// Merge with existing manifest if present; capture previous_version for rollback (#253).
|
|
821
898
|
let existingModules = [];
|
|
899
|
+
let previousVersion = null;
|
|
822
900
|
const existingPath = path.join(opts.target, '.rihal', '_config', 'manifest.yaml');
|
|
823
901
|
if (fs.existsSync(existingPath)) {
|
|
824
902
|
const text = fs.readFileSync(existingPath, 'utf8');
|
|
825
903
|
let inModules = false;
|
|
826
904
|
for (const line of text.split('\n')) {
|
|
905
|
+
if (line.startsWith('version:')) {
|
|
906
|
+
const v = line.replace('version:', '').trim();
|
|
907
|
+
if (semver.valid(v) && v !== version) previousVersion = v;
|
|
908
|
+
}
|
|
827
909
|
if (line.startsWith('modules:')) { inModules = true; continue; }
|
|
828
910
|
if (inModules && line.trim().startsWith('-')) { existingModules.push(line.trim().slice(1).trim()); }
|
|
829
911
|
else if (inModules && !line.startsWith(' ')) { inModules = false; }
|
|
@@ -831,16 +913,14 @@ function generateInstallManifest(opts) {
|
|
|
831
913
|
}
|
|
832
914
|
const allModules = [...new Set([...existingModules, ...newModules])];
|
|
833
915
|
const moduleLines = allModules.map((m) => ` - ${m}`).join('\n');
|
|
834
|
-
|
|
916
|
+
const lines = [
|
|
835
917
|
'# Rihal v2 install manifest',
|
|
836
918
|
`version: ${version}`,
|
|
837
919
|
`installDate: ${new Date().toISOString()}`,
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
'',
|
|
843
|
-
].join('\n');
|
|
920
|
+
];
|
|
921
|
+
if (previousVersion) lines.push(`previous_version: ${previousVersion}`);
|
|
922
|
+
lines.push('modules:', moduleLines, 'ides:', ' - claude-code', '');
|
|
923
|
+
return lines.join('\n');
|
|
844
924
|
}
|
|
845
925
|
|
|
846
926
|
function sanitizeYamlValue(val) {
|
|
@@ -869,6 +949,57 @@ function generateConfigYaml(opts) {
|
|
|
869
949
|
].join('\n');
|
|
870
950
|
}
|
|
871
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Validate a parsed config.yaml object against ConfigSchema (#250).
|
|
954
|
+
* Returns { valid: true } or { valid: false, errors: string[] }.
|
|
955
|
+
*/
|
|
956
|
+
function validateConfig(data) {
|
|
957
|
+
const result = ConfigSchema.safeParse(data);
|
|
958
|
+
if (result.success) return { valid: true };
|
|
959
|
+
const errors = result.error.issues.map((issue) => {
|
|
960
|
+
const field = issue.path.join('.');
|
|
961
|
+
return ` ${field || '(root)'}: ${issue.message}`;
|
|
962
|
+
});
|
|
963
|
+
return { valid: false, errors };
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Parse a minimal YAML key:value file into a plain object.
|
|
968
|
+
* Only handles scalar values — sufficient for config.yaml.
|
|
969
|
+
*/
|
|
970
|
+
function parseSimpleYaml(text) {
|
|
971
|
+
const obj = {};
|
|
972
|
+
let currentParent = null;
|
|
973
|
+
for (const raw of text.split('\n')) {
|
|
974
|
+
const line = raw.replace(/#.*$/, '');
|
|
975
|
+
if (!line.trim()) continue;
|
|
976
|
+
const indent = line.match(/^(\s*)/)[1].length;
|
|
977
|
+
if (indent === 0) {
|
|
978
|
+
const colonAt = line.indexOf(':');
|
|
979
|
+
if (colonAt === -1) continue;
|
|
980
|
+
const key = line.slice(0, colonAt).trim();
|
|
981
|
+
let val = line.slice(colonAt + 1).trim();
|
|
982
|
+
if (val === '') { currentParent = key; obj[key] = {}; continue; }
|
|
983
|
+
currentParent = null;
|
|
984
|
+
if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
|
|
985
|
+
if (val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
|
|
986
|
+
if (val === 'true') val = true;
|
|
987
|
+
else if (val === 'false') val = false;
|
|
988
|
+
obj[key] = val;
|
|
989
|
+
} else if (currentParent && indent > 0) {
|
|
990
|
+
const colonAt = line.indexOf(':');
|
|
991
|
+
if (colonAt === -1) continue;
|
|
992
|
+
const key = line.slice(0, colonAt).trim();
|
|
993
|
+
let val = line.slice(colonAt + 1).trim();
|
|
994
|
+
if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
|
|
995
|
+
if (val === 'true') val = true;
|
|
996
|
+
else if (val === 'false') val = false;
|
|
997
|
+
obj[currentParent][key] = val;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return obj;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
872
1003
|
/**
|
|
873
1004
|
* Convert a markdown command/agent file to Cursor's .mdc format.
|
|
874
1005
|
* Wraps the file with Cursor-specific rules frontmatter.
|
|
@@ -889,21 +1020,24 @@ async function install(opts) {
|
|
|
889
1020
|
// Resolve commit-planning preference (interactive prompt or flag) — #189.
|
|
890
1021
|
opts.commitPlanning = await resolveCommitPlanning(opts);
|
|
891
1022
|
|
|
892
|
-
|
|
1023
|
+
const pkgVersion = readPackageVersion();
|
|
1024
|
+
console.log(`\n🕌 ${bold('Rihal Code')} ${pc.cyan('v' + pkgVersion)} ${dim('→')} ${opts.target}`);
|
|
893
1025
|
|
|
894
1026
|
// Detect an existing install and surface it (#195).
|
|
895
1027
|
const existingManifestPath = path.join(opts.target, '.rihal', '_config', 'manifest.yaml');
|
|
896
1028
|
if (fs.existsSync(existingManifestPath)) {
|
|
897
1029
|
const m = fs.readFileSync(existingManifestPath, 'utf8').match(/^version:\s*(.+)$/m);
|
|
898
1030
|
const existingVersion = m ? m[1].trim() : 'unknown';
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
1031
|
+
const isUpgrade = semver.valid(existingVersion) && semver.valid(pkgVersion)
|
|
1032
|
+
? semver.lt(existingVersion, pkgVersion)
|
|
1033
|
+
: existingVersion !== pkgVersion;
|
|
1034
|
+
if (isUpgrade) {
|
|
1035
|
+
console.log(' ' + info(`Upgrading ${pc.dim('v' + existingVersion)} → ${pc.green('v' + pkgVersion)} (config + state + .planning preserved)`));
|
|
902
1036
|
} else {
|
|
903
|
-
console.log(
|
|
1037
|
+
console.log(' ' + info(`Refreshing v${existingVersion} (config + state + .planning preserved)`));
|
|
904
1038
|
}
|
|
905
1039
|
if (!opts.force) {
|
|
906
|
-
console.log(' Pass --force to also sweep orphaned files from the previous version.');
|
|
1040
|
+
console.log(dim(' Pass --force to also sweep orphaned files from the previous version.'));
|
|
907
1041
|
}
|
|
908
1042
|
}
|
|
909
1043
|
if (!fs.existsSync(SOURCE_ROOT)) {
|
|
@@ -971,46 +1105,96 @@ async function install(opts) {
|
|
|
971
1105
|
sweptOrphans = sweepStaleInstalledFiles(opts.target, plan);
|
|
972
1106
|
}
|
|
973
1107
|
|
|
974
|
-
//
|
|
1108
|
+
// Load previous manifest for non-destructive mode (#232).
|
|
1109
|
+
// Map<rel, expectedHashFromPriorInstall> — if a file's current hash matches
|
|
1110
|
+
// its expected-from-prior-install hash, the user hasn't touched it → safe
|
|
1111
|
+
// to overwrite. If hashes differ, user customized it → preserve.
|
|
1112
|
+
const priorManifest = new Map();
|
|
1113
|
+
if (opts.nonDestructive) {
|
|
1114
|
+
const manifestPath = path.join(opts.target, '.rihal', '_config', 'files-manifest.csv');
|
|
1115
|
+
if (fs.existsSync(manifestPath)) {
|
|
1116
|
+
try {
|
|
1117
|
+
const lines = fs.readFileSync(manifestPath, 'utf8').split('\n').slice(1).filter(Boolean);
|
|
1118
|
+
for (const line of lines) {
|
|
1119
|
+
const [rel, hash] = line.split(',');
|
|
1120
|
+
if (rel && hash) priorManifest.set(rel, hash);
|
|
1121
|
+
}
|
|
1122
|
+
} catch {
|
|
1123
|
+
// best-effort — if manifest is malformed, fall back to behaving like fresh install
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Copy files — spinner gives feedback on long installs (#248).
|
|
975
1129
|
let copied = 0;
|
|
976
1130
|
let skipped = 0;
|
|
1131
|
+
let preserved = 0;
|
|
1132
|
+
const preservedFiles = [];
|
|
1133
|
+
const preservedDiffs = []; // { rel, insertions, deletions, patch } for #251
|
|
1134
|
+
const spinner = createSpinner(dim(`Installing ${plan.length} files…`), { color: 'cyan' }).start();
|
|
1135
|
+
|
|
977
1136
|
for (const entry of plan) {
|
|
978
1137
|
const destPath = path.join(opts.target, entry.rel);
|
|
1138
|
+
const relForward = entry.rel.split(path.sep).join('/');
|
|
979
1139
|
ensureDir(path.dirname(destPath));
|
|
980
|
-
|
|
1140
|
+
|
|
1141
|
+
// Non-destructive guard (#232): preserve user-modified files.
|
|
1142
|
+
// --accept-all (#251) overrides: treat all files as pristine.
|
|
1143
|
+
if (opts.nonDestructive && !opts.forceOverwrite && !opts.acceptAll && fs.existsSync(destPath)) {
|
|
1144
|
+
const priorHash = priorManifest.get(relForward);
|
|
1145
|
+
if (priorHash) {
|
|
1146
|
+
const installedContent = fs.readFileSync(destPath, 'utf8');
|
|
1147
|
+
const currentHash = sha256(Buffer.from(installedContent));
|
|
1148
|
+
if (currentHash !== priorHash) {
|
|
1149
|
+
// Compute diff stat for display (#251)
|
|
1150
|
+
const srcContent = fs.readFileSync(entry.src, 'utf8');
|
|
1151
|
+
const patch = createTwoFilesPatch(relForward, relForward, installedContent, srcContent, 'installed', 'source');
|
|
1152
|
+
let ins = 0, del = 0;
|
|
1153
|
+
for (const line of patch.split('\n')) {
|
|
1154
|
+
if (line.startsWith('+') && !line.startsWith('+++')) ins++;
|
|
1155
|
+
if (line.startsWith('-') && !line.startsWith('---')) del++;
|
|
1156
|
+
}
|
|
1157
|
+
preserved += 1;
|
|
1158
|
+
preservedFiles.push(relForward);
|
|
1159
|
+
preservedDiffs.push({ rel: relForward, insertions: ins, deletions: del, patch });
|
|
1160
|
+
skipped += 1;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
// Hash matches prior install → pristine → safe to overwrite
|
|
1164
|
+
}
|
|
1165
|
+
// No prior hash → new file in this plan → install normally
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
if (fs.existsSync(destPath) && !opts.force && !opts.forceOverwrite) {
|
|
981
1169
|
const existingHash = sha256(fs.readFileSync(destPath));
|
|
982
1170
|
const sourceHash = sha256(fs.readFileSync(entry.src));
|
|
983
1171
|
if (existingHash === sourceHash) { skipped++; continue; }
|
|
984
|
-
if (!opts.yes) {
|
|
985
|
-
|
|
1172
|
+
if (!opts.yes && !opts.nonDestructive) {
|
|
1173
|
+
spinner.stop();
|
|
1174
|
+
console.warn(' ' + warn(`${entry.rel} differs from package version — use --force-overwrite to overwrite`));
|
|
1175
|
+
spinner.start();
|
|
986
1176
|
skipped++;
|
|
987
1177
|
continue;
|
|
988
1178
|
}
|
|
989
1179
|
}
|
|
990
1180
|
|
|
991
|
-
|
|
992
|
-
if (fs.existsSync(destPath) && opts.force) {
|
|
1181
|
+
if (fs.existsSync(destPath) && opts.forceOverwrite) {
|
|
993
1182
|
const existing = fs.readFileSync(destPath);
|
|
994
1183
|
const incoming = fs.readFileSync(entry.src);
|
|
995
1184
|
if (!existing.equals(incoming)) {
|
|
996
|
-
|
|
1185
|
+
spinner.update({ text: dim(`overwriting ${entry.rel}`) });
|
|
997
1186
|
}
|
|
998
1187
|
}
|
|
999
1188
|
|
|
1000
|
-
// Read source file
|
|
1001
1189
|
let content = fs.readFileSync(entry.src, 'utf8');
|
|
1002
|
-
|
|
1003
|
-
// Convert to Cursor .mdc format if needed
|
|
1004
|
-
if (entry.cursor) {
|
|
1005
|
-
content = convertToCursorMdc(content);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// Write to destination
|
|
1190
|
+
if (entry.cursor) content = convertToCursorMdc(content);
|
|
1009
1191
|
fs.writeFileSync(destPath, content, 'utf8');
|
|
1010
1192
|
if (entry.executable) fs.chmodSync(destPath, 0o755);
|
|
1011
1193
|
copied++;
|
|
1012
1194
|
}
|
|
1013
1195
|
|
|
1196
|
+
spinner.success({ text: ok(`${copied} files installed`) });
|
|
1197
|
+
|
|
1014
1198
|
// Write .rihal/_config/manifest.yaml + agent-manifest.csv + files-manifest.csv
|
|
1015
1199
|
const configDir = path.join(opts.target, '.rihal', '_config');
|
|
1016
1200
|
ensureDir(configDir);
|
|
@@ -1038,6 +1222,18 @@ async function install(opts) {
|
|
|
1038
1222
|
if (!fs.existsSync(configPath)) {
|
|
1039
1223
|
fs.writeFileSync(configPath, generateConfigYaml(opts));
|
|
1040
1224
|
}
|
|
1225
|
+
// Validate config.yaml with zod schema (#250) — warn but never block install.
|
|
1226
|
+
try {
|
|
1227
|
+
const configText = fs.readFileSync(configPath, 'utf8');
|
|
1228
|
+
const configData = parseSimpleYaml(configText);
|
|
1229
|
+
const validation = validateConfig(configData);
|
|
1230
|
+
if (!validation.valid) {
|
|
1231
|
+
console.log('');
|
|
1232
|
+
console.log(' ' + warn('config.yaml has validation errors:'));
|
|
1233
|
+
for (const e of validation.errors) console.log(pc.yellow(e));
|
|
1234
|
+
console.log(dim(' → Edit .rihal/config.yaml to fix, then run /rihal:status'));
|
|
1235
|
+
}
|
|
1236
|
+
} catch { /* best-effort */ }
|
|
1041
1237
|
|
|
1042
1238
|
// Seed .rihal/state.json (skip if already exists — don't overwrite on re-install unless --reset)
|
|
1043
1239
|
if (!fs.existsSync(stateDest)) {
|
|
@@ -1104,14 +1300,17 @@ async function install(opts) {
|
|
|
1104
1300
|
|
|
1105
1301
|
// Summary
|
|
1106
1302
|
console.log('');
|
|
1107
|
-
|
|
1303
|
+
if (opts.force && sweptOrphans > 0) console.log(' ' + info(`${sweptOrphans} stale files swept`));
|
|
1304
|
+
if (opts.force && existedBefore) {
|
|
1305
|
+
console.log(' ' + warn('config.yaml and state.json preserved (pass --reset to wipe)'));
|
|
1306
|
+
}
|
|
1108
1307
|
if (brainReport && brainReport.ok) {
|
|
1109
1308
|
const pulledCount = (brainReport.pulled || []).length;
|
|
1110
1309
|
const skippedCount = (brainReport.skipped || []).length;
|
|
1111
|
-
console.log(`
|
|
1112
|
-
(skippedCount ? `, ${skippedCount} skipped (placeholder URLs
|
|
1310
|
+
console.log(' ' + ok(`Brain: ${pulledCount} source${pulledCount === 1 ? '' : 's'} pulled` +
|
|
1311
|
+
(skippedCount ? `, ${skippedCount} skipped (placeholder URLs)` : '')));
|
|
1113
1312
|
} else if (brainReport && brainReport.error) {
|
|
1114
|
-
console.log(`
|
|
1313
|
+
console.log(' ' + dim(`Brain: skipped (${brainReport.error})`));
|
|
1115
1314
|
}
|
|
1116
1315
|
if (gitignoreReport) {
|
|
1117
1316
|
const gitMsg = {
|
|
@@ -1121,12 +1320,29 @@ async function install(opts) {
|
|
|
1121
1320
|
'updated': '.gitignore rcode block refreshed',
|
|
1122
1321
|
'skipped-error': `.gitignore skipped (${gitignoreReport.error})`,
|
|
1123
1322
|
}[gitignoreReport.action] || '.gitignore unchanged';
|
|
1124
|
-
console.log(
|
|
1323
|
+
console.log(' ' + dim(gitMsg));
|
|
1125
1324
|
}
|
|
1126
|
-
if (skipped > 0) console.log(
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1325
|
+
if (skipped > 0) console.log(' ' + dim(`${skipped} files skipped (unchanged)`));
|
|
1326
|
+
|
|
1327
|
+
// Diff display for preserved files (#251)
|
|
1328
|
+
if (preserved > 0 && opts.nonDestructive) {
|
|
1329
|
+
console.log('');
|
|
1330
|
+
console.log(' ' + warn(`${preserved} file${preserved === 1 ? '' : 's'} preserved (modified since install):`));
|
|
1331
|
+
for (const d of preservedDiffs.slice(0, 10)) {
|
|
1332
|
+
const stat = pc.green(`+${d.insertions}`) + ' ' + pc.red(`-${d.deletions}`);
|
|
1333
|
+
console.log(` ${dim(d.rel)} ${stat}`);
|
|
1334
|
+
if (opts.showDiff && d.patch) {
|
|
1335
|
+
for (const line of d.patch.split('\n').slice(4)) { // skip file headers
|
|
1336
|
+
if (line.startsWith('+')) process.stdout.write(pc.green(line) + '\n');
|
|
1337
|
+
else if (line.startsWith('-')) process.stdout.write(pc.red(line) + '\n');
|
|
1338
|
+
else if (line.startsWith('@')) process.stdout.write(pc.cyan(line) + '\n');
|
|
1339
|
+
else process.stdout.write(dim(line) + '\n');
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (preservedDiffs.length > 10) console.log(dim(` … and ${preservedDiffs.length - 10} more`));
|
|
1344
|
+
console.log(dim(' To overwrite: re-run with --force-overwrite | To see full diffs: --show-diff'));
|
|
1345
|
+
console.log('');
|
|
1130
1346
|
}
|
|
1131
1347
|
|
|
1132
1348
|
// Count installed agents + commands dynamically (#190).
|
|
@@ -1142,41 +1358,57 @@ async function install(opts) {
|
|
|
1142
1358
|
}
|
|
1143
1359
|
} catch {}
|
|
1144
1360
|
|
|
1361
|
+
const version = readPackageVersion();
|
|
1145
1362
|
console.log('');
|
|
1146
|
-
console.log(` Version: @hanzlaa/rcode
|
|
1147
|
-
console.log(` IDE: ${opts.ide}`);
|
|
1148
|
-
console.log(` Language: ${opts.language} (change in .rihal/config.yaml)`);
|
|
1149
|
-
console.log(` Mode: ${opts.mode} (guided=confirm at gates, yolo=autonomous)`);
|
|
1150
|
-
console.log(`
|
|
1151
|
-
console.log(` Planning: ${opts.commitPlanning !== false ? 'committed' : 'gitignored'} (flip: rihal-tools gitignore refresh)`);
|
|
1363
|
+
console.log(` ${bold('Version:')} ${pc.cyan('@hanzlaa/rcode@' + version)}`);
|
|
1364
|
+
console.log(` ${bold('IDE:')} ${opts.ide}`);
|
|
1365
|
+
console.log(` ${bold('Language:')} ${opts.language} ${dim('(change in .rihal/config.yaml)')}`);
|
|
1366
|
+
console.log(` ${bold('Mode:')} ${opts.mode} ${dim('(guided=confirm at gates, yolo=autonomous)')}`);
|
|
1367
|
+
console.log(` ${bold('Planning:')} ${opts.commitPlanning !== false ? 'committed' : 'gitignored'} ${dim('(flip: rihal-tools gitignore refresh)')}`);
|
|
1152
1368
|
console.log('');
|
|
1153
|
-
console.log(` Agents: ${agentCount}
|
|
1154
|
-
console.log(`
|
|
1155
|
-
console.log(`
|
|
1156
|
-
console.log(` Full list: ls .claude/commands/rihal/`);
|
|
1157
|
-
if (skillsInstalled > 0) {
|
|
1158
|
-
console.log(` Skills: ${skillsInstalled} phrase-activated in .claude/skills/`);
|
|
1159
|
-
}
|
|
1369
|
+
console.log(` ${bold('Agents:')} ${pc.green(String(agentCount))} in .claude/agents/`);
|
|
1370
|
+
console.log(` ${bold('Commands:')} ${pc.green(String(commandCount))} slash commands in .claude/commands/rihal/`);
|
|
1371
|
+
if (skillsInstalled > 0) console.log(` ${bold('Skills:')} ${pc.green(String(skillsInstalled))} phrase-activated in .claude/skills/`);
|
|
1160
1372
|
console.log('');
|
|
1161
1373
|
if (starterSeeded) {
|
|
1162
|
-
console.log('
|
|
1374
|
+
console.log(' ' + ok('Starter planning scaffolded in .planning/ (ROADMAP, STATE, PROJECT)'));
|
|
1163
1375
|
console.log('');
|
|
1164
1376
|
}
|
|
1165
|
-
console.log('
|
|
1377
|
+
console.log(` ${bold('Next:')}`);
|
|
1166
1378
|
console.log(` cd ${opts.target}`);
|
|
1167
1379
|
console.log(' claude # start Claude Code (reload window if already open)');
|
|
1168
1380
|
console.log(' /rihal:progress # where you are, what\'s next');
|
|
1169
1381
|
console.log(' /rihal:do # interactive command picker');
|
|
1170
1382
|
console.log(' /rihal:council <q> # multi-agent strategic answer');
|
|
1171
1383
|
console.log('');
|
|
1172
|
-
console.log(' Refresh anytime:');
|
|
1173
|
-
console.log(' npx @hanzlaa/rcode@latest install # pull the latest rcode + brain');
|
|
1174
|
-
console.log(
|
|
1384
|
+
console.log(dim(' Refresh anytime:'));
|
|
1385
|
+
console.log(dim(' npx @hanzlaa/rcode@latest install # pull the latest rcode + brain'));
|
|
1386
|
+
console.log(dim(` /rihal:update v${version} # pin rcode to a specific version`));
|
|
1175
1387
|
console.log('');
|
|
1176
|
-
console.log('
|
|
1177
|
-
console.log(' Claude Code / VS Code / Cursor: Cmd+Shift+P → Reload Window');
|
|
1388
|
+
console.log(' ' + warn('If your IDE is already open, reload the window to refresh skills/commands.'));
|
|
1389
|
+
console.log(dim(' Claude Code / VS Code / Cursor: Cmd+Shift+P → Reload Window'));
|
|
1178
1390
|
console.log('');
|
|
1179
1391
|
|
|
1392
|
+
// Lightweight update check (#252) — async background, never blocks install.
|
|
1393
|
+
// Suppressed in non-TTY / CI or when --no-update-check is passed.
|
|
1394
|
+
if (!opts.noUpdateCheck && process.stdout.isTTY && !process.env.CI && !process.env.RIHAL_NO_UPDATE_NOTIFIER) {
|
|
1395
|
+
const { execFile } = require('child_process');
|
|
1396
|
+
execFile('npm', ['view', '@hanzlaa/rcode', 'version', '--json'], { timeout: 4000 }, (err, stdout) => {
|
|
1397
|
+
if (err) return;
|
|
1398
|
+
try {
|
|
1399
|
+
const latest = JSON.parse(stdout.trim());
|
|
1400
|
+
if (semver.valid(latest) && semver.gt(latest, version)) {
|
|
1401
|
+
console.log('');
|
|
1402
|
+
console.log(' ╭──────────────────────────────────────────────────────╮');
|
|
1403
|
+
console.log(` │ ${pc.yellow('Update available:')} ${pc.dim(version)} → ${pc.green(latest)}${' '.repeat(Math.max(0, 20 - version.length - latest.length))} │`);
|
|
1404
|
+
console.log(' │ Run: npx @hanzlaa/rcode@latest install . │');
|
|
1405
|
+
console.log(' ╰──────────────────────────────────────────────────────╯');
|
|
1406
|
+
console.log('');
|
|
1407
|
+
}
|
|
1408
|
+
} catch { /* ignore parse errors */ }
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1180
1412
|
// Health check — smoke test that the install actually works (#193).
|
|
1181
1413
|
const healthPass = runInstallHealthCheck(opts.target, { agentCount, commandCount, skillsInstalled });
|
|
1182
1414
|
return healthPass ? 0 : 1;
|
|
@@ -1188,17 +1420,17 @@ async function install(opts) {
|
|
|
1188
1420
|
* Prints a clean ✓/✖ line per check.
|
|
1189
1421
|
*/
|
|
1190
1422
|
function runInstallHealthCheck(target, counts) {
|
|
1191
|
-
console.log('
|
|
1423
|
+
console.log(` ${bold('Health check:')}`);
|
|
1192
1424
|
const { execFileSync } = require('child_process');
|
|
1193
1425
|
let fails = 0;
|
|
1194
1426
|
|
|
1195
1427
|
function check(label, fn) {
|
|
1196
1428
|
try {
|
|
1197
1429
|
const out = fn();
|
|
1198
|
-
console.log(`
|
|
1430
|
+
console.log(` ${ok(label)}${out ? dim(' — ' + out) : ''}`);
|
|
1199
1431
|
} catch (err) {
|
|
1200
1432
|
fails += 1;
|
|
1201
|
-
console.log(`
|
|
1433
|
+
console.log(` ${fail(label)} ${pc.red('—')} ${String(err.message || err).slice(0, 120)}`);
|
|
1202
1434
|
}
|
|
1203
1435
|
}
|
|
1204
1436
|
|
|
@@ -1239,9 +1471,9 @@ function runInstallHealthCheck(target, counts) {
|
|
|
1239
1471
|
|
|
1240
1472
|
if (fails > 0) {
|
|
1241
1473
|
console.log('');
|
|
1242
|
-
console.log(
|
|
1243
|
-
console.log(' Debug: node .rihal/bin/rihal-tools.cjs state read && ls -la .rihal/');
|
|
1244
|
-
console.log(' Reinstall: npx @hanzlaa/rcode install . --force');
|
|
1474
|
+
console.log(' ' + fail(`${fails} health check${fails === 1 ? '' : 's'} failed — install may be broken.`));
|
|
1475
|
+
console.log(dim(' Debug: node .rihal/bin/rihal-tools.cjs state read && ls -la .rihal/'));
|
|
1476
|
+
console.log(dim(' Reinstall: npx @hanzlaa/rcode install . --force'));
|
|
1245
1477
|
console.log('');
|
|
1246
1478
|
return false;
|
|
1247
1479
|
}
|
package/cli/postinstall.js
CHANGED
|
@@ -11,8 +11,8 @@ console.log(`
|
|
|
11
11
|
🕌 Rihal Code installed.
|
|
12
12
|
|
|
13
13
|
First-time setup:
|
|
14
|
-
npx @
|
|
15
|
-
npx @
|
|
14
|
+
npx @hanzlaa/rcode install # set up agents + slash commands
|
|
15
|
+
npx @hanzlaa/rcode tiers # see the tier map
|
|
16
16
|
|
|
17
17
|
🌱 The Golden Path (say these phrases in your AI IDE):
|
|
18
18
|
1. "scaffold a new project" → rihal-scaffold-project
|
|
@@ -24,8 +24,8 @@ First-time setup:
|
|
|
24
24
|
7. "sprint status" → rihal-sprint-status
|
|
25
25
|
|
|
26
26
|
More:
|
|
27
|
-
npx @
|
|
28
|
-
npx @
|
|
27
|
+
npx @hanzlaa/rcode help # all commands (grouped)
|
|
28
|
+
npx @hanzlaa/rcode dashboard # view-only Diwan on :7717
|
|
29
29
|
|
|
30
30
|
Docs: https://github.com/hanzlahabib/rihal-code
|
|
31
31
|
Tiers: docs/TIERS.md · Standards: docs/STANDARDS.md
|
package/cli/uninstall.js
CHANGED
|
@@ -549,6 +549,14 @@ async function runUninstall(args) {
|
|
|
549
549
|
'.antigravity',
|
|
550
550
|
]);
|
|
551
551
|
|
|
552
|
+
// Always remove .rihal/brain/ — it's pulled rcode content (issue #202),
|
|
553
|
+
// not user data. Refreshed by `brain pull` on next install.
|
|
554
|
+
const brainDir = path.join(cwd, '.rihal', 'brain');
|
|
555
|
+
if (fs.existsSync(brainDir)) {
|
|
556
|
+
fs.rmSync(brainDir, { recursive: true, force: true });
|
|
557
|
+
console.log(` ✓ removed .rihal/brain/ (pulled content, will refresh on reinstall)`);
|
|
558
|
+
}
|
|
559
|
+
|
|
552
560
|
// Handle .rihal/ state directory
|
|
553
561
|
if (plan.stateDir) {
|
|
554
562
|
const rihalDir = path.join(cwd, '.rihal');
|