@hanzlaa/rcode 2.3.6 → 2.4.1
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/cli/index.js +0 -0
- package/package.json +10 -10
- package/rihal/agents/rihal-codebase-mapper.md +108 -35
- package/rihal/bin/rihal-tools.cjs +94 -15
- package/rihal/commands/progress.md +3 -4
- package/rihal/references/dispatch-banner.md +157 -0
- package/rihal/workflows/dashboard.md +36 -5
- package/rihal/workflows/do.md +38 -1
- package/rihal/workflows/init.md +4 -1
- package/rihal/workflows/progress.md +19 -157
- package/rihal/workflows/scan.md +94 -9
package/cli/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Rihal Code (rcode) — installable context-brain for Rihalians. 43 agents, 99 slash commands, 56 skills, pullable Rihal standards. Unified install for Claude Code, Cursor, and Gemini.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,14 @@
|
|
|
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
|
+
},
|
|
11
19
|
"files": [
|
|
12
20
|
"cli/",
|
|
13
21
|
"rihal/",
|
|
@@ -62,13 +70,5 @@
|
|
|
62
70
|
},
|
|
63
71
|
"publishConfig": {
|
|
64
72
|
"access": "public"
|
|
65
|
-
},
|
|
66
|
-
"scripts": {
|
|
67
|
-
"dashboard": "node server/dashboard.js",
|
|
68
|
-
"test": "node --test",
|
|
69
|
-
"test:ci": "node --test --test-reporter=spec",
|
|
70
|
-
"postinstall": "node cli/postinstall.js",
|
|
71
|
-
"build:cli": "node scripts/build.cjs",
|
|
72
|
-
"build": "node scripts/build.cjs"
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|
|
@@ -10,15 +10,19 @@ color: cyan
|
|
|
10
10
|
@.rihal/references/karpathy-guidelines-full.md
|
|
11
11
|
|
|
12
12
|
<role>
|
|
13
|
-
You are
|
|
13
|
+
You are **Dalil (دليل) — Codebase Scout** 🧭. The name means "guide" in Arabic; that's exactly your job: walk a repo, find what's actually there, and report it honestly.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**Voice:** First-person, calm, observational. Open every response with a one-line continuity beat — `Dalil here — starting the scan.` for fresh dispatches, `Dalil — back at it.` for follow-ups. Sign your closing summary with `— Dalil`.
|
|
16
|
+
|
|
17
|
+
**Honesty about scope is the core of this role.** You are the agent users blame when a future plan rests on a falsehood like "no Sentry SDK in `backend/`" — when there was. Your Scan Scope section exists so that lie can never happen again. If you didn't search a directory, say so. If you found zero matches for a topic phrase, double-check with case-insensitive grep AND the canonical SDK name before claiming "not present."
|
|
18
|
+
|
|
19
|
+
You are spawned by `/rihal:scan` and `/rihal:map-codebase` with one of four focus areas:
|
|
16
20
|
- **tech**: Analyze technology stack and external integrations → write STACK.md and INTEGRATIONS.md
|
|
17
21
|
- **arch**: Analyze architecture and file structure → write ARCHITECTURE.md and STRUCTURE.md
|
|
18
22
|
- **quality**: Analyze coding conventions and testing patterns → write CONVENTIONS.md and TESTING.md
|
|
19
23
|
- **concerns**: Identify technical debt and issues → write CONCERNS.md
|
|
20
24
|
|
|
21
|
-
Your job: Explore thoroughly, then write document(s) directly. Return confirmation only.
|
|
25
|
+
Your job: Explore thoroughly across ALL source roots (never assume `src/` is the only one), then write document(s) directly. Return confirmation only — but in your own voice.
|
|
22
26
|
|
|
23
27
|
**CRITICAL: Mandatory Initial Read**
|
|
24
28
|
If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
|
@@ -83,62 +87,114 @@ Based on focus, determine which documents you'll write:
|
|
|
83
87
|
- `concerns` → CONCERNS.md
|
|
84
88
|
</step>
|
|
85
89
|
|
|
90
|
+
<step name="discover_source_roots">
|
|
91
|
+
**MANDATORY FIRST STEP — never skip.** Do not assume `src/` exists or that the project is single-language. Discover the real layout before searching anything.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 1. Top-level source roots (excluding vendored / build / VCS / cache)
|
|
95
|
+
find . -maxdepth 1 -type d \
|
|
96
|
+
-not -name '.' -not -name '.git' -not -name 'node_modules' \
|
|
97
|
+
-not -name '.next' -not -name 'dist' -not -name 'build' \
|
|
98
|
+
-not -name '__pycache__' -not -name '.venv' -not -name 'venv' \
|
|
99
|
+
-not -name '.cache' -not -name 'coverage' \
|
|
100
|
+
| sort
|
|
101
|
+
|
|
102
|
+
# 2. Language detection from manifests at any depth (up to 3 levels)
|
|
103
|
+
find . -maxdepth 3 \
|
|
104
|
+
\( -name 'package.json' -o -name 'pyproject.toml' -o -name 'requirements.txt' \
|
|
105
|
+
-o -name 'Cargo.toml' -o -name 'go.mod' -o -name 'Gemfile' -o -name 'pom.xml' \
|
|
106
|
+
-o -name 'build.gradle' -o -name 'composer.json' \) \
|
|
107
|
+
-not -path '*/node_modules/*' -not -path '*/.venv/*' 2>/dev/null
|
|
108
|
+
|
|
109
|
+
# 3. Monorepo detection
|
|
110
|
+
ls pnpm-workspace.yaml turbo.json nx.json lerna.json rush.json 2>/dev/null
|
|
111
|
+
cat package.json 2>/dev/null | grep -E '"workspaces"' -A 5
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Record the result as `$SOURCE_ROOTS` (list of dirs to search) and `$LANGUAGES` (set of detected languages). These drive every subsequent grep — never grep only `src/` unless `src/` is the only discovered root.
|
|
115
|
+
|
|
116
|
+
**If a topic phrase was passed in your prompt** (e.g. "Sentry instrumentation", "GraphQL resolvers", "Redis caching"), run a literal sweep across ALL discovered roots BEFORE focus-specific exploration:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
TOPIC="<phrase from prompt>"
|
|
120
|
+
for ROOT in $SOURCE_ROOTS; do
|
|
121
|
+
echo "=== $ROOT ==="
|
|
122
|
+
grep -rli "$TOPIC" "$ROOT" \
|
|
123
|
+
--include='*.py' --include='*.ts' --include='*.tsx' --include='*.js' \
|
|
124
|
+
--include='*.jsx' --include='*.go' --include='*.rs' --include='*.rb' \
|
|
125
|
+
2>/dev/null | head -50
|
|
126
|
+
done
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The file list this returns becomes your PRIMARY analysis target. Do not narrow it to one subdirectory based on assumed conventions.
|
|
130
|
+
</step>
|
|
131
|
+
|
|
86
132
|
<step name="explore_codebase">
|
|
87
|
-
Explore the codebase thoroughly for your focus area.
|
|
133
|
+
Explore the codebase thoroughly for your focus area, iterating across ALL `$SOURCE_ROOTS` discovered above. Adapt globs to `$LANGUAGES` — if Python is in the language set, search `*.py`; if TypeScript, `*.ts`/`*.tsx`; etc.
|
|
88
134
|
|
|
89
135
|
**For tech focus:**
|
|
90
136
|
```bash
|
|
91
|
-
# Package manifests
|
|
92
|
-
ls package.json requirements.txt Cargo.toml go.mod pyproject.toml 2>/dev/null
|
|
93
|
-
cat package.json 2>/dev/null | head -100
|
|
94
|
-
|
|
137
|
+
# Package manifests across ALL roots (already gathered in discover_source_roots)
|
|
95
138
|
# Config files (list only - DO NOT read .env contents)
|
|
96
139
|
ls -la *.config.* tsconfig.json .nvmrc .python-version 2>/dev/null
|
|
97
140
|
ls .env* 2>/dev/null # Note existence only, never read contents
|
|
98
141
|
|
|
99
|
-
#
|
|
100
|
-
|
|
142
|
+
# SDK/API imports — iterate over every source root
|
|
143
|
+
for ROOT in $SOURCE_ROOTS; do
|
|
144
|
+
grep -rE "^(import|from) (.*stripe|.*supabase|.*aws|.*sentry|.*@)" "$ROOT" \
|
|
145
|
+
--include='*.py' --include='*.ts' --include='*.tsx' --include='*.js' 2>/dev/null | head -30
|
|
146
|
+
done
|
|
101
147
|
```
|
|
102
148
|
|
|
103
149
|
**For arch focus:**
|
|
104
150
|
```bash
|
|
105
|
-
# Directory
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
151
|
+
# Directory tree of each source root
|
|
152
|
+
for ROOT in $SOURCE_ROOTS; do
|
|
153
|
+
find "$ROOT" -type d \
|
|
154
|
+
-not -path '*/node_modules/*' -not -path '*/.venv/*' -not -path '*/__pycache__/*' \
|
|
155
|
+
| head -40
|
|
156
|
+
done
|
|
157
|
+
|
|
158
|
+
# Entry points across languages
|
|
109
159
|
ls src/index.* src/main.* src/app.* src/server.* app/page.* 2>/dev/null
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
160
|
+
find . -maxdepth 4 -name 'main.py' -o -name '__main__.py' -o -name 'manage.py' \
|
|
161
|
+
-o -name 'app.py' -o -name 'wsgi.py' -o -name 'asgi.py' \
|
|
162
|
+
-not -path '*/.venv/*' -not -path '*/node_modules/*' 2>/dev/null
|
|
113
163
|
```
|
|
114
164
|
|
|
115
165
|
**For quality focus:**
|
|
116
166
|
```bash
|
|
117
|
-
|
|
118
|
-
ls .eslintrc* .prettierrc* eslint.config.* biome.json 2>/dev/null
|
|
119
|
-
cat .prettierrc 2>/dev/null
|
|
167
|
+
ls .eslintrc* .prettierrc* eslint.config.* biome.json ruff.toml .flake8 mypy.ini pyrightconfig.json 2>/dev/null
|
|
120
168
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
find
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
ls src/**/*.ts 2>/dev/null | head -10
|
|
169
|
+
# Tests across all roots and languages
|
|
170
|
+
for ROOT in $SOURCE_ROOTS; do
|
|
171
|
+
find "$ROOT" \( -name '*.test.*' -o -name '*.spec.*' -o -name 'test_*.py' -o -name '*_test.py' \) \
|
|
172
|
+
-not -path '*/node_modules/*' -not -path '*/.venv/*' 2>/dev/null | head -20
|
|
173
|
+
done
|
|
127
174
|
```
|
|
128
175
|
|
|
129
176
|
**For concerns focus:**
|
|
130
177
|
```bash
|
|
131
|
-
# TODO/FIXME comments
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
# TODO/FIXME comments — search every root, every primary language
|
|
179
|
+
for ROOT in $SOURCE_ROOTS; do
|
|
180
|
+
grep -rnE "TODO|FIXME|HACK|XXX" "$ROOT" \
|
|
181
|
+
--include='*.py' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' \
|
|
182
|
+
--include='*.go' --include='*.rs' \
|
|
183
|
+
-not -path '*/node_modules/*' 2>/dev/null | head -50
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
# Large files (potential complexity) — language-aware
|
|
187
|
+
for ROOT in $SOURCE_ROOTS; do
|
|
188
|
+
find "$ROOT" \( -name '*.py' -o -name '*.ts' -o -name '*.tsx' -o -name '*.go' \) \
|
|
189
|
+
-not -path '*/node_modules/*' -not -path '*/.venv/*' \
|
|
190
|
+
| xargs wc -l 2>/dev/null | sort -rn | head -10
|
|
191
|
+
done
|
|
192
|
+
|
|
193
|
+
# If the orchestrator passed a topic phrase, the file list from discover_source_roots
|
|
194
|
+
# is your primary input — analyze each of those files directly.
|
|
139
195
|
```
|
|
140
196
|
|
|
141
|
-
Read key files identified during exploration. Use Glob and Grep liberally.
|
|
197
|
+
Read key files identified during exploration. Use Glob and Grep liberally — but always iterate across `$SOURCE_ROOTS`, never assume `src/` is the only place code lives.
|
|
142
198
|
</step>
|
|
143
199
|
|
|
144
200
|
<step name="write_documents">
|
|
@@ -153,6 +209,23 @@ Write document(s) to `.rihal/codebase/` using the templates below.
|
|
|
153
209
|
4. Always include file paths with backticks
|
|
154
210
|
|
|
155
211
|
**ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
|
|
212
|
+
|
|
213
|
+
**MANDATORY — Scan Scope section.** Every document you write must open with this block before any other content. The orchestrator will reject documents missing it.
|
|
214
|
+
|
|
215
|
+
```markdown
|
|
216
|
+
## Scan Scope
|
|
217
|
+
|
|
218
|
+
**Source roots discovered:** `<list from discover_source_roots step 1>`
|
|
219
|
+
**Source roots searched:** `<subset actually iterated by greps>`
|
|
220
|
+
**Source roots NOT searched:** `<any discovered root not searched>` — Reason: `<vendored / out-of-scope / time / etc.>`
|
|
221
|
+
**Languages detected:** `<from manifests, e.g. Python 3.11, TypeScript 5.x>`
|
|
222
|
+
**Topic phrase (if any):** `<literal phrase from orchestrator prompt, or "none">`
|
|
223
|
+
**Topic-phrase sweep result:** `<file count + 5-10 sample paths from grep -rl, or "n/a">`
|
|
224
|
+
|
|
225
|
+
**Blind-spot acknowledgment:** If you searched only a subset (e.g. only `src/` while `backend/` and `services/` exist), state it explicitly here. If you found ZERO matches for a topic phrase, run a second sweep with case-insensitive `grep -rli` and a third with the canonical SDK/package name (e.g. `sentry_sdk`, `sentry-sdk`, `@sentry/`) before claiming "not present" — false negatives at this step poison every downstream phase.
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
If the topic-phrase sweep returns matches in a directory you did not analyze in depth, you MUST either (a) extend the analysis to cover it, or (b) explicitly note in the document body which findings might exist there but were not investigated. Never silently exclude a directory that contains topic-phrase hits.
|
|
156
229
|
</step>
|
|
157
230
|
|
|
158
231
|
<step name="return_confirmation">
|
|
@@ -3043,6 +3043,9 @@ function cmdProgress(args) {
|
|
|
3043
3043
|
if (!fs.existsSync(roadmapPath)) return [];
|
|
3044
3044
|
const text = fs.readFileSync(roadmapPath, 'utf8');
|
|
3045
3045
|
const phases = [];
|
|
3046
|
+
const seen = new Set();
|
|
3047
|
+
|
|
3048
|
+
// Format A — markdown pipe tables: | 07 | Name | Goal |
|
|
3046
3049
|
const rowRe = /^\|\s*(\d{1,3}(?:\.\d+)?)\s*\|\s*([^|]+?)\s*\|\s*([^|]*?)\s*\|/gm;
|
|
3047
3050
|
let m;
|
|
3048
3051
|
while ((m = rowRe.exec(text)) !== null) {
|
|
@@ -3051,16 +3054,54 @@ function cmdProgress(args) {
|
|
|
3051
3054
|
const goal = m[3].trim();
|
|
3052
3055
|
if (!/^\d/.test(num)) continue;
|
|
3053
3056
|
if (name.toLowerCase() === 'phase') continue;
|
|
3057
|
+
if (seen.has(num)) continue;
|
|
3058
|
+
seen.add(num);
|
|
3054
3059
|
phases.push({ number: num, name, goal });
|
|
3055
3060
|
}
|
|
3061
|
+
|
|
3062
|
+
// Format B — heading style: ## Phase 07 — Name / ### Phase 07: Name / ## Phase 07 - Name
|
|
3063
|
+
const headRe = /^#{2,4}\s*Phase\s+(\d{1,3}(?:\.\d+)?)\s*[—\-:]\s*([^\n]+)$/gm;
|
|
3064
|
+
while ((m = headRe.exec(text)) !== null) {
|
|
3065
|
+
const num = m[1].trim();
|
|
3066
|
+
const name = m[2].trim();
|
|
3067
|
+
if (seen.has(num)) continue;
|
|
3068
|
+
seen.add(num);
|
|
3069
|
+
// Goal: pull the first non-empty line after the heading that starts with **Goal:** or is plain text
|
|
3070
|
+
const after = text.slice(headRe.lastIndex).split(/\n/).slice(0, 8).join('\n');
|
|
3071
|
+
const goalMatch = after.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
3072
|
+
phases.push({ number: num, name, goal: goalMatch ? goalMatch[1].trim() : '' });
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
// Sort numerically (handles "07" vs "10" string ordering correctly)
|
|
3076
|
+
phases.sort((a, b) => parseFloat(a.number) - parseFloat(b.number));
|
|
3056
3077
|
return phases;
|
|
3057
3078
|
}
|
|
3058
3079
|
|
|
3059
3080
|
function extractMilestoneName() {
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3081
|
+
// 1. Try ROADMAP.md headings — match any milestone header form
|
|
3082
|
+
if (fs.existsSync(roadmapPath)) {
|
|
3083
|
+
const text = fs.readFileSync(roadmapPath, 'utf8');
|
|
3084
|
+
// Bold form: **Milestone: v1.0 — Name** or **Milestone v1.0 — Name**
|
|
3085
|
+
let m = text.match(/\*\*\s*Milestone\s*:?\s*([^\n*]+?)\s*\*\*/i);
|
|
3086
|
+
if (m) return m[1].trim();
|
|
3087
|
+
// Header form: ## Milestone v1.0 — Name / ## Milestone: v1.0 — Name
|
|
3088
|
+
m = text.match(/^#{1,4}\s+Milestone\s*:?\s*([^\n]+)$/m);
|
|
3089
|
+
if (m) return m[1].trim();
|
|
3090
|
+
}
|
|
3091
|
+
// 2. Fall back to state.json milestone field
|
|
3092
|
+
try {
|
|
3093
|
+
if (fs.existsSync(statePath)) {
|
|
3094
|
+
const s = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
3095
|
+
if (s && s.milestone) return String(s.milestone).trim();
|
|
3096
|
+
}
|
|
3097
|
+
} catch { /* ignore */ }
|
|
3098
|
+
return null;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// Treat any of `number`, `id`, or `name` as the phase identifier.
|
|
3102
|
+
// Different commands historically write different field names — accept all.
|
|
3103
|
+
function phaseKey(p) {
|
|
3104
|
+
return String(p?.number ?? p?.id ?? p?.name ?? '').trim();
|
|
3064
3105
|
}
|
|
3065
3106
|
|
|
3066
3107
|
function walkPhaseDirs() {
|
|
@@ -3099,10 +3140,13 @@ function cmdProgress(args) {
|
|
|
3099
3140
|
});
|
|
3100
3141
|
}
|
|
3101
3142
|
|
|
3102
|
-
// Undercount: phases that exist on disk but not in state
|
|
3103
|
-
|
|
3143
|
+
// Undercount: phases that exist on disk but not in state.
|
|
3144
|
+
// Accept any of `number`, `id`, or `name` as the phase identifier — the codebase historically writes different fields.
|
|
3145
|
+
// Also normalize "07" / "7" / 7 to a comparable form.
|
|
3146
|
+
const norm = (k) => String(k ?? '').replace(/^0+(\d)/, '$1');
|
|
3147
|
+
const statePhaseNums = new Set(statePhases.map(p => norm(phaseKey(p))));
|
|
3104
3148
|
const diskPhaseNums = Object.keys(diskByNum);
|
|
3105
|
-
const missingFromState = diskPhaseNums.filter(n => !statePhaseNums.has(n));
|
|
3149
|
+
const missingFromState = diskPhaseNums.filter(n => !statePhaseNums.has(norm(n)));
|
|
3106
3150
|
if (missingFromState.length > 0) {
|
|
3107
3151
|
insights.push({
|
|
3108
3152
|
kind: 'undercount',
|
|
@@ -3132,14 +3176,15 @@ function cmdProgress(args) {
|
|
|
3132
3176
|
|
|
3133
3177
|
// Route A — phases with pending plans (ready to execute)
|
|
3134
3178
|
const pendingExec = statePhases.filter(p => {
|
|
3135
|
-
const disk = diskByNum[
|
|
3179
|
+
const disk = diskByNum[phaseKey(p)];
|
|
3136
3180
|
return disk && disk.plan_count > disk.summary_count;
|
|
3137
3181
|
}).slice(0, 3);
|
|
3138
3182
|
for (const p of pendingExec) {
|
|
3183
|
+
const k = phaseKey(p);
|
|
3139
3184
|
routes.push({
|
|
3140
3185
|
letter: 'A',
|
|
3141
|
-
label: `Execute phase ${
|
|
3142
|
-
command: `/rihal:execute-phase ${
|
|
3186
|
+
label: `Execute phase ${k} — unfinished plans`,
|
|
3187
|
+
command: `/rihal:execute-phase ${k}`,
|
|
3143
3188
|
});
|
|
3144
3189
|
}
|
|
3145
3190
|
|
|
@@ -3155,6 +3200,23 @@ function cmdProgress(args) {
|
|
|
3155
3200
|
});
|
|
3156
3201
|
}
|
|
3157
3202
|
|
|
3203
|
+
// Route B' — in-progress phases without plans (the user is actively working but no SPRINT.md exists yet)
|
|
3204
|
+
const inProgressNoPlan = statePhases
|
|
3205
|
+
.filter(p => (p.status === 'in_progress' || p.status === 'in-progress'))
|
|
3206
|
+
.filter(p => {
|
|
3207
|
+
const disk = diskByNum[phaseKey(p)];
|
|
3208
|
+
return !disk || disk.plan_count === 0;
|
|
3209
|
+
})
|
|
3210
|
+
.slice(0, 2);
|
|
3211
|
+
for (const p of inProgressNoPlan) {
|
|
3212
|
+
const k = phaseKey(p);
|
|
3213
|
+
routes.push({
|
|
3214
|
+
letter: 'B',
|
|
3215
|
+
label: `Plan phase ${k} — in progress without SPRINT.md`,
|
|
3216
|
+
command: `/rihal:plan ${k}`,
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3158
3220
|
// Route C — close out milestone if everything seems done
|
|
3159
3221
|
const allDone = statePhases.length > 0 && statePhases.every(p => p.status === 'complete' || p.completed);
|
|
3160
3222
|
if (allDone) {
|
|
@@ -3215,11 +3277,28 @@ function cmdProgress(args) {
|
|
|
3215
3277
|
phase_count: phaseCount,
|
|
3216
3278
|
completed_count: completedCount,
|
|
3217
3279
|
bar: buildBar(completedCount, phaseCount),
|
|
3218
|
-
phases:
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3280
|
+
phases: (() => {
|
|
3281
|
+
// Prefer ROADMAP-parsed phases when available; fall back to state.phases
|
|
3282
|
+
// when the roadmap doesn't use a parseable format. Normalize "07" / "7" / 7.
|
|
3283
|
+
const norm = (k) => String(k ?? '').replace(/^0+(\d)/, '$1');
|
|
3284
|
+
const source = roadmapPhases.length > 0 ? roadmapPhases : statePhases.map(p => ({
|
|
3285
|
+
number: phaseKey(p),
|
|
3286
|
+
name: p.name || '',
|
|
3287
|
+
goal: p.goal || '',
|
|
3288
|
+
status: p.status,
|
|
3289
|
+
}));
|
|
3290
|
+
return source.map(p => {
|
|
3291
|
+
const k = phaseKey(p);
|
|
3292
|
+
const sp = statePhases.find(x => norm(phaseKey(x)) === norm(k));
|
|
3293
|
+
return {
|
|
3294
|
+
...p,
|
|
3295
|
+
number: k,
|
|
3296
|
+
status: p.status || (sp && sp.status) || null,
|
|
3297
|
+
disk: diskByNum[k] || null,
|
|
3298
|
+
in_state: !!sp,
|
|
3299
|
+
};
|
|
3300
|
+
});
|
|
3301
|
+
})(),
|
|
3223
3302
|
decisions: state ? (state.decisions || []).slice(-3) : [],
|
|
3224
3303
|
blockers: state ? (state.blockers || []).filter(b => !b.resolved).slice(0, 5) : [],
|
|
3225
3304
|
insights,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rihal:progress
|
|
3
|
-
description:
|
|
3
|
+
description: Alias of /rihal:status --verbose — full project state dashboard with decisions, blockers, and next-step routes
|
|
4
4
|
argument-hint: ""
|
|
5
5
|
allowed-tools: bash, read, grep
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
<objective>
|
|
9
|
-
|
|
9
|
+
Alias of `/rihal:status --verbose`. Produces the verbose project state dashboard — phase, sprint progress, recent decisions, blockers, last council session, and a Next Up route tree.
|
|
10
10
|
</objective>
|
|
11
11
|
|
|
12
12
|
<execution_context>
|
|
@@ -14,6 +14,5 @@ Check project progress narrative — what was done, what is next, and route to t
|
|
|
14
14
|
</execution_context>
|
|
15
15
|
|
|
16
16
|
<process>
|
|
17
|
-
Execute the progress workflow from @.rihal/workflows/progress.md end-to-end.
|
|
18
|
-
Load state, summarize recent work, suggest next command to run.
|
|
17
|
+
Execute the progress workflow from @.rihal/workflows/progress.md end-to-end. That workflow delegates to `.rihal/workflows/status.md` in verbose mode — single source of truth via `rihal-tools progress init`.
|
|
19
18
|
</process>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Dispatch Banner — Persona-driven hand-off format
|
|
2
|
+
|
|
3
|
+
**Purpose:** every time a Rihal workflow spawns a sub-agent (mapper, planner, executor, council member, etc.), the user must see WHO is taking over, in their voice, with what scope. Inspired by BMAD's PM-introduces-itself pattern. No silent dispatches.
|
|
4
|
+
|
|
5
|
+
This banner format is mandatory for every `Task(subagent_type=...)` invocation in any Rihal workflow.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Persona registry
|
|
10
|
+
|
|
11
|
+
Each agent has a persona name and a one-line role tag used in the banner. Pull from `rihal/team.yaml` (`name` + `role` fields). For utility agents not in team.yaml, use the fallback table below.
|
|
12
|
+
|
|
13
|
+
| Agent ID | Persona name | Role tag | Glyph |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `rihal-sadiq` | Sadiq (صادق) | Director of Strategy | 🧭 |
|
|
16
|
+
| `rihal-waleed` | Waleed (وليد) | CTO / Architect | 🏛️ |
|
|
17
|
+
| `rihal-hussain-pm` | Hussain (حسين) | Product Manager | 📋 |
|
|
18
|
+
| `rihal-mariam` | Mariam (مريم) | Marketing & Growth | 📣 |
|
|
19
|
+
| `rihal-fatima` | Fatima (فاطمة) | QA Lead | 🔍 |
|
|
20
|
+
| `rihal-yousef` | Yousef (يوسف) | Senior Backend Engineer | 🛠️ |
|
|
21
|
+
| `rihal-haitham` | Haitham (هيثم) | Senior Frontend Engineer | 🎨 |
|
|
22
|
+
| `rihal-layla` | Layla (ليلى) | UX Designer | ✏️ |
|
|
23
|
+
| `rihal-zahra` | Zahra (زهراء) | Branding & Creative | 🎭 |
|
|
24
|
+
| `rihal-zayd` | Zayd (زيد) | Senior ML Engineer | 🧠 |
|
|
25
|
+
| `rihal-khalid` | Khalid (خالد) | DevOps & Infrastructure | 🚢 |
|
|
26
|
+
| `rihal-nasser` | Nasser (ناصر) | Engineering Manager | 🤝 |
|
|
27
|
+
| `rihal-ahmed-hassani-director` | Ahmed (أحمد) | Tech & Delivery Director | 🧩 |
|
|
28
|
+
| `rihal-noor` | Noor (نور) | Technical Writer | ✒️ |
|
|
29
|
+
| `rihal-omar` | Omar (عمر) | Software Engineer | ⚙️ |
|
|
30
|
+
| `rihal-hanzla` | Hanzla | Senior Full-Stack Engineer | ⚡ |
|
|
31
|
+
| `rihal-codebase-mapper` | Dalil (دليل) | Codebase Scout | 🧭 |
|
|
32
|
+
| `rihal-planner` | Khattat (خطّاط) | Sprint Planner | 📐 |
|
|
33
|
+
| `rihal-executor` | Munaffidh (منفّذ) | Plan Executor | 🔨 |
|
|
34
|
+
| `rihal-phase-researcher` | Bahith (باحث) | Phase Researcher | 🔬 |
|
|
35
|
+
| `rihal-verifier` | Muhaqqiq (محقّق) | Goal Verifier | ✅ |
|
|
36
|
+
| `rihal-security-auditor` | Hamid (حامد) | Security Auditor | 🛡️ |
|
|
37
|
+
| `rihal-debugger` | Mufattish (مفتّش) | Debug Investigator | 🐛 |
|
|
38
|
+
|
|
39
|
+
If an agent is not in this table, derive: `Persona = Title-cased role from team.yaml`, glyph `🤖`.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Banner format — DISPATCH (before spawn)
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
╭─────────────────────────────────────────────────────────╮
|
|
47
|
+
│ {glyph} {Persona name} — {Role tag} │
|
|
48
|
+
╰─────────────────────────────────────────────────────────╯
|
|
49
|
+
|
|
50
|
+
السلام عليكم — I'm {Persona-first-name}, your {short role}.
|
|
51
|
+
|
|
52
|
+
{One-sentence first-person statement of what this agent will do,
|
|
53
|
+
referencing the user's request directly. No abstract jargon.}
|
|
54
|
+
|
|
55
|
+
Scope:
|
|
56
|
+
• {bullet — what's in scope}
|
|
57
|
+
• {bullet — what's in scope}
|
|
58
|
+
|
|
59
|
+
Output:
|
|
60
|
+
→ {file path or deliverable}
|
|
61
|
+
|
|
62
|
+
{Optional: any caveat the user should know up-front, e.g. "I'll
|
|
63
|
+
only read code — no edits, no commits." }
|
|
64
|
+
|
|
65
|
+
Working now — I'll surface anything I'm unsure about before
|
|
66
|
+
returning.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Rules:**
|
|
70
|
+
- Always first-person (`I'm`, `I'll`, `I have`). Never "the agent will…"
|
|
71
|
+
- Always include `السلام عليكم` for the first dispatch in a session; for subsequent dispatches in the same conversation, replace with `Back at it —` or similar continuity phrase.
|
|
72
|
+
- Always state the deliverable path so the user can grep / open the file later.
|
|
73
|
+
- Keep total banner ≤ 14 lines. If scope needs more bullets, that's a sign the spawn is too broad — narrow it first.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Banner format — RETURNED (after spawn)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
╭─────────────────────────────────────────────────────────╮
|
|
81
|
+
│ ✓ {Persona name} — back │
|
|
82
|
+
╰─────────────────────────────────────────────────────────╯
|
|
83
|
+
|
|
84
|
+
{One-sentence first-person summary of what got done.}
|
|
85
|
+
|
|
86
|
+
Covered:
|
|
87
|
+
• {bullet — concrete coverage, e.g. "Searched: web/, backend/, ml/"}
|
|
88
|
+
• {bullet}
|
|
89
|
+
|
|
90
|
+
Skipped / blind spots:
|
|
91
|
+
• {bullet — explicit acknowledgment of what was NOT done}
|
|
92
|
+
|
|
93
|
+
Wrote:
|
|
94
|
+
→ {file path} ({N} lines)
|
|
95
|
+
|
|
96
|
+
{Optional: 1-2 follow-up questions the persona surfaces, in their
|
|
97
|
+
own voice, e.g. "I noticed X — want me to dig deeper?"}
|
|
98
|
+
|
|
99
|
+
I'm still here for follow-up questions about this work until the
|
|
100
|
+
next dispatch.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Rules:**
|
|
104
|
+
- Always declare blind spots, even if "none". An empty `Skipped` section is a code smell — it usually means the agent didn't honestly account for what it skipped.
|
|
105
|
+
- Follow-up questions stay in the persona's voice; the orchestrator does not strip them.
|
|
106
|
+
- The "still here for follow-up" line tells the user that subsequent questions in this conversation will be answered by THIS persona until a new dispatch fires.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Follow-up framing
|
|
111
|
+
|
|
112
|
+
When the user asks a follow-up question after a RETURNED banner and BEFORE the next dispatch, the orchestrator answers AS the persona that just returned — first person, persona name in any signature line. Example:
|
|
113
|
+
|
|
114
|
+
> User: "kya tumne backend/onyx/ check kiya tha?"
|
|
115
|
+
>
|
|
116
|
+
> Orchestrator response (as Dalil):
|
|
117
|
+
>
|
|
118
|
+
> > Yes — I swept it. Found 47 files importing `sentry_sdk`. Want me to drill into the LoggingIntegration init points specifically? — Dalil
|
|
119
|
+
|
|
120
|
+
When the topic shifts to something OUTSIDE the persona's scope, hand off explicitly:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
This is outside my scope as Dalil (codebase mapping). Handing off to
|
|
124
|
+
{next persona} for {topic}…
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Then print a fresh DISPATCH banner.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Concurrency: parallel dispatches
|
|
132
|
+
|
|
133
|
+
If a workflow spawns multiple agents in parallel (e.g. `/rihal:council` summons 4 specialists), print ONE combined banner:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
╭─────────────────────────────────────────────────────────╮
|
|
137
|
+
│ Majlis (مجلس) — convening 4 voices │
|
|
138
|
+
╰─────────────────────────────────────────────────────────╯
|
|
139
|
+
|
|
140
|
+
I'm convening: Sadiq · Waleed · Hussain · Fatima
|
|
141
|
+
Topic: {one-line topic}
|
|
142
|
+
Each will weigh in independently — I'll compile their answers
|
|
143
|
+
and surface where they agree, disagree, or push back.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Each individual agent's response is then prefixed with their persona glyph + name, e.g.:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
🏛️ Waleed: …
|
|
150
|
+
🔍 Fatima: …
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## When to skip the banner
|
|
156
|
+
|
|
157
|
+
Never. If a workflow chooses to skip, it must justify in code review — the only legitimate reason is a sub-millisecond pure-data-shaping helper that produces no user-facing output. Anything that does real work gets a banner.
|
|
@@ -23,18 +23,49 @@ STOP — do not proceed.
|
|
|
23
23
|
|
|
24
24
|
## Step 1 — Resolve dashboard script
|
|
25
25
|
|
|
26
|
-
Check for the dashboard server in priority order:
|
|
26
|
+
Check for the dashboard server in priority order. Use a single bash block so all fallbacks run before failing:
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
```bash
|
|
29
|
+
DASHBOARD=""
|
|
30
|
+
# 1. Source repo
|
|
31
|
+
if [ -f ./server/dashboard.js ]; then
|
|
32
|
+
DASHBOARD="./server/dashboard.js"
|
|
33
|
+
# 2. Installed package copy inside project
|
|
34
|
+
elif [ -f ./.rihal/lib/server/dashboard.js ]; then
|
|
35
|
+
DASHBOARD="./.rihal/lib/server/dashboard.js"
|
|
36
|
+
else
|
|
37
|
+
# 3. Global installs — check npm, pnpm, and yarn roots
|
|
38
|
+
for ROOT in "$(npm root -g 2>/dev/null)" "$(pnpm root -g 2>/dev/null)" "$(yarn global dir 2>/dev/null)/node_modules"; do
|
|
39
|
+
[ -z "$ROOT" ] && continue
|
|
40
|
+
if [ -f "$ROOT/@hanzlaa/rcode/server/dashboard.js" ]; then
|
|
41
|
+
DASHBOARD="$ROOT/@hanzlaa/rcode/server/dashboard.js"
|
|
42
|
+
break
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
# 4. Last resort — resolve via the rcode/rihal binary symlink
|
|
46
|
+
if [ -z "$DASHBOARD" ]; then
|
|
47
|
+
for BIN in rcode rihal rihal-code; do
|
|
48
|
+
BIN_PATH="$(command -v $BIN 2>/dev/null)" || continue
|
|
49
|
+
REAL_BIN="$(readlink -f "$BIN_PATH" 2>/dev/null)"
|
|
50
|
+
[ -z "$REAL_BIN" ] && continue
|
|
51
|
+
# Walk up from dist/rcode.js → package root → server/dashboard.js
|
|
52
|
+
PKG_ROOT="$(dirname "$(dirname "$REAL_BIN")")"
|
|
53
|
+
if [ -f "$PKG_ROOT/server/dashboard.js" ]; then
|
|
54
|
+
DASHBOARD="$PKG_ROOT/server/dashboard.js"
|
|
55
|
+
break
|
|
56
|
+
fi
|
|
57
|
+
done
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
echo "DASHBOARD=$DASHBOARD"
|
|
61
|
+
```
|
|
31
62
|
|
|
32
63
|
Store the resolved path as `$DASHBOARD`.
|
|
33
64
|
|
|
34
65
|
If none found, print:
|
|
35
66
|
```
|
|
36
67
|
❌ Dashboard script not found.
|
|
37
|
-
Run `npx @hanzlaa/rcode install` to install the package, or check you're inside a project with .rihal/.
|
|
68
|
+
Run `npx @hanzlaa/rcode install` (or `pnpm add -g @hanzlaa/rcode`) to install the package, or check you're inside a project with .rihal/.
|
|
38
69
|
```
|
|
39
70
|
Exit.
|
|
40
71
|
|
package/rihal/workflows/do.md
CHANGED
|
@@ -99,10 +99,47 @@ Then dispatch to the prerequisite command instead of the originally-matched rout
|
|
|
99
99
|
The guard never silently rejects intent — it always either dispatches to a sensible alternative OR explicitly tells the user what flag overrides it (e.g. `--skip-prerequisites` for the rare legitimate use case).
|
|
100
100
|
</step>
|
|
101
101
|
|
|
102
|
+
<step name="external_data_guard" priority="first-match">
|
|
103
|
+
**Block code-only routing when the actionable signal lives in an external system.**
|
|
104
|
+
|
|
105
|
+
Some tasks reference systems whose data is NOT in the repo — observability platforms, issue trackers, analytics, and product dashboards. A pure codebase scan can map *instrumentation* but cannot classify *what is actually firing*. Routing such requests to `/rihal:scan` or `/rihal:map-codebase` without first establishing a data source produces theoretical output (violates the codebase-first rule).
|
|
106
|
+
|
|
107
|
+
**External-data signals** — match if `$QUESTION` contains any of:
|
|
108
|
+
|
|
109
|
+
- Observability: `sentry`, `datadog`, `new relic`, `newrelic`, `bugsnag`, `rollbar`, `honeycomb`, `grafana`, `prometheus`, `splunk`, `cloudwatch`
|
|
110
|
+
- Analytics: `google analytics`, ` GA4`, `mixpanel`, `amplitude`, `posthog`, `heap`
|
|
111
|
+
- Issue/support: `linear`, `jira`, `zendesk`, `intercom`, `freshdesk`, `pagerduty`
|
|
112
|
+
- Product/CRM: `stripe dashboard`, `hubspot`, `salesforce`
|
|
113
|
+
|
|
114
|
+
**Action verbs** — match if `$QUESTION` contains any of: `audit`, `clean up`, `cleanup`, `classify`, `triage`, `review errors`, `noisy`, `top errors`, `which errors`, `production errors`, `dashboard`.
|
|
115
|
+
|
|
116
|
+
If BOTH a system signal AND an action verb match, fire the guard. Ask via AskUserQuestion BEFORE choosing a route:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
The task involves {detected system} — the actionable data lives there, not in the repo.
|
|
120
|
+
A codebase scan alone will only show instrumentation, not which errors are firing.
|
|
121
|
+
|
|
122
|
+
How should we access the external data?
|
|
123
|
+
|
|
124
|
+
1. MCP/API access available — pull the data and analyze it
|
|
125
|
+
2. I'll paste the top errors / dashboard export manually
|
|
126
|
+
3. Codebase-only scan is fine — I just want the instrumentation map
|
|
127
|
+
4. Cancel — let me reformulate
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then route accordingly:
|
|
131
|
+
- **Option 1:** Continue to `route` step but tag the chosen command with a note to use the external data source. If no Rihal command natively reads the external system, route to `/rihal:discuss` and have the agent guide MCP/API setup.
|
|
132
|
+
- **Option 2:** Route to `/rihal:discuss` so the user can paste data into a focused conversation, OR `/rihal:note` to capture, then re-run.
|
|
133
|
+
- **Option 3:** Continue to `route` step normally (likely `/rihal:scan` or `/rihal:map-codebase`) but display a clear caveat: *"Output will be an instrumentation map only — it cannot classify which errors are noisy vs critical."*
|
|
134
|
+
- **Option 4:** Stop. Print the original input back so the user can rephrase.
|
|
135
|
+
|
|
136
|
+
Skip this guard when `AUTO_MODE=true` AND the input explicitly contains `--codebase-only` or `instrumentation map` — those signal the user already accepted the limitation.
|
|
137
|
+
</step>
|
|
138
|
+
|
|
102
139
|
<step name="route">
|
|
103
140
|
**Match intent to command.**
|
|
104
141
|
|
|
105
|
-
(Run only after greenfield_guard
|
|
142
|
+
(Run only after greenfield_guard AND external_data_guard have cleared.)
|
|
106
143
|
|
|
107
144
|
Evaluate `$QUESTION` against these routing rules. Apply the **first matching** rule:
|
|
108
145
|
|
package/rihal/workflows/init.md
CHANGED
|
@@ -142,9 +142,12 @@ git remote -v 2>/dev/null | head -2
|
|
|
142
142
|
find . -maxdepth 3 -type d ! -path "./node_modules*" ! -path "./.git*" ! -path "./.rihal*" ! -path "./.claude*" ! -path "./.planning*" 2>/dev/null | head -20
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
Write `.rihal/RIHLA.md` following this template (don't over-interpret — just record what's seen)
|
|
145
|
+
Write `.rihal/RIHLA.md` following this template (don't over-interpret — just record what's seen).
|
|
146
|
+
|
|
147
|
+
**Naming note (do NOT remove from the template):** the file is `RIHLA.md`, not `RIHAL.md`. This is intentional — same Arabic root, different word. **Rihal (رحّال)** = the traveler/tool. **Rihla (رحلة)** = the journey/voyage. The product is *Rihal* (the tool you use); the per-project artifact is *Rihla* (your project's journey). The HTML comment in the template below preserves this reminder for anyone who later wonders if it's a typo.
|
|
146
148
|
|
|
147
149
|
```markdown
|
|
150
|
+
<!-- RIHLA (رحلة) = "the journey". Not a typo of RIHAL (رحّال) = "the traveler" / the tool itself. Same root, different word. This file documents your project's journey; Rihal is the tool that walks it with you. -->
|
|
148
151
|
# RIHLA — Project journey baseline
|
|
149
152
|
|
|
150
153
|
**Written by:** /rihal:init
|
|
@@ -1,184 +1,46 @@
|
|
|
1
|
+
# Workflow: rihal:progress
|
|
2
|
+
|
|
1
3
|
<purpose>
|
|
2
|
-
|
|
4
|
+
**`/rihal:progress` is an alias of `/rihal:status --verbose`.**
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
Historically this was a separate workflow with overlapping data and a heavier render. They both read the same source of truth (`rihal-tools progress init`), so we collapsed them: `/rihal:status` is the canonical renderer with built-in slim/verbose modes, and `/rihal:progress` is a thin alias that always runs in verbose mode.
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
Use whichever name you prefer — they produce the same output.
|
|
7
9
|
</purpose>
|
|
8
10
|
|
|
9
11
|
<required_reading>
|
|
10
|
-
@.rihal/
|
|
11
|
-
Read all files referenced by the invoking prompt's execution_context before starting.
|
|
12
|
+
@.rihal/workflows/status.md
|
|
12
13
|
</required_reading>
|
|
13
14
|
|
|
14
|
-
<output_format>
|
|
15
|
-
Banner from output-format.md:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
19
|
-
RIHAL ► PROGRESS
|
|
20
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Use ✓ complete / ◆ in_progress / ○ planned / 🅿 parking-lot throughout.
|
|
24
|
-
End with a Next Up block rendered from the CLI's `routes[]` array.
|
|
25
|
-
</output_format>
|
|
26
|
-
|
|
27
15
|
<process>
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## 1. Init context
|
|
32
|
-
|
|
33
|
-
Fetch the full progress snapshot in a single call:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
SNAPSHOT=$(node .rihal/bin/rihal-tools.cjs progress init)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Parse as JSON.
|
|
40
|
-
|
|
41
|
-
If `SNAPSHOT.project` is null AND `SNAPSHOT.phases[]` is empty:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
No planning structure found.
|
|
45
|
-
|
|
46
|
-
Run /rihal:new-project to start a new project.
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Exit.
|
|
17
|
+
## Step 1 — Delegate to /rihal:status in verbose mode
|
|
50
18
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
DISCUSS_MODE=$(node .rihal/bin/rihal-tools.cjs config 2>/dev/null | grep -oE '"discuss_mode"\s*:\s*"[^"]*"' | cut -d'"' -f4 || echo "discuss")
|
|
55
|
-
```
|
|
19
|
+
Execute the workflow defined in `.rihal/workflows/status.md` end-to-end, with one override:
|
|
56
20
|
|
|
57
|
-
|
|
21
|
+
- Always render in **verbose mode** (full Steps 2–6 output: banner + phases + insights + decisions + blockers + Next Up route tree).
|
|
22
|
+
- Treat `$ARGUMENTS` exactly as `/rihal:status --verbose $ARGUMENTS` would.
|
|
58
23
|
|
|
59
|
-
|
|
24
|
+
Do not parse ROADMAP.md, walk SUMMARY.md files, or grep state.json directly. If the underlying CLI reports a drift insight, surface it — do not silently compensate.
|
|
60
25
|
|
|
61
|
-
## 2
|
|
26
|
+
## Step 2 — Footer note (one-time alias hint)
|
|
62
27
|
|
|
63
|
-
|
|
28
|
+
After the verbose status output, append a single grey line:
|
|
64
29
|
|
|
65
|
-
```bash
|
|
66
|
-
# find the 3 most recent SUMMARY.md files
|
|
67
|
-
(find .planning/phases -name "SUMMARY.md" -o -name "*-SUMMARY.md" 2>/dev/null) | xargs -r ls -t 2>/dev/null | head -3 | while read f; do
|
|
68
|
-
node .rihal/bin/rihal-tools.cjs summary-extract "$f" --fields one_liner,status
|
|
69
|
-
done
|
|
70
30
|
```
|
|
71
|
-
|
|
72
|
-
Each call returns `{ ok: true, one_liner: "...", status: "..." }`. Collect into an in-memory list for rendering. This avoids loading full SUMMARY.md bodies — context-expensive and unnecessary.
|
|
73
|
-
|
|
74
|
-
</step>
|
|
75
|
-
|
|
76
|
-
<step name="insights">
|
|
77
|
-
|
|
78
|
-
## 3. Insights — surface what the CLI noticed
|
|
79
|
-
|
|
80
|
-
`SNAPSHOT.insights[]` contains drift warnings, between-milestone detection, phase-dir undercount. Render above the progress bar so the user sees divergences immediately:
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
⚠ {insight.message} (severity: warn)
|
|
84
|
-
ℹ {insight.message} (severity: info)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Each insight that mentions a fix command should have it surfaced exactly as-is — e.g. "Run: node .rihal/bin/rihal-tools.cjs state sync --from-disk".
|
|
88
|
-
|
|
89
|
-
</step>
|
|
90
|
-
|
|
91
|
-
<step name="report">
|
|
92
|
-
|
|
93
|
-
## 4. Render the progress view
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
97
|
-
RIHAL ► PROGRESS
|
|
98
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
99
|
-
|
|
100
|
-
# {SNAPSHOT.project}
|
|
101
|
-
|
|
102
|
-
{insights block — printed FIRST if present}
|
|
103
|
-
|
|
104
|
-
**Progress:** {SNAPSHOT.bar}
|
|
105
|
-
**Milestone:** {SNAPSHOT.milestone or "—"}
|
|
106
|
-
**Discuss mode:** {DISCUSS_MODE}
|
|
107
|
-
|
|
108
|
-
## Recent Work
|
|
109
|
-
- [Phase {N}]: {one_liner extracted in step 2}
|
|
110
|
-
- [Phase {N}]: {one_liner}
|
|
111
|
-
|
|
112
|
-
## Current Position
|
|
113
|
-
Phase [{SNAPSHOT.current_phase}] of [{SNAPSHOT.phase_count}]
|
|
114
|
-
Plan progress: {completed_count}/{phase_count}
|
|
115
|
-
|
|
116
|
-
## Key Decisions
|
|
117
|
-
- {SNAPSHOT.decisions[].summary} — [{phase}.{plan}], {date}
|
|
118
|
-
|
|
119
|
-
## Blockers
|
|
120
|
-
- ⚠ {SNAPSHOT.blockers[].description} — [{phase}.{plan}]
|
|
121
|
-
|
|
122
|
-
## Pending Todos
|
|
123
|
-
- {todo count} pending — /rihal:check-todos to review
|
|
124
|
-
(Skip if count = 0)
|
|
125
|
-
|
|
126
|
-
## Active Debug Sessions
|
|
127
|
-
- {count} active — /rihal:debug to continue
|
|
128
|
-
(Skip if count = 0)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Omit any section whose underlying array is empty — don't print "Key Decisions" with zero entries.
|
|
132
|
-
|
|
133
|
-
</step>
|
|
134
|
-
|
|
135
|
-
<step name="next_up">
|
|
136
|
-
|
|
137
|
-
## 5. Next Up — intent tree
|
|
138
|
-
|
|
139
|
-
Render `SNAPSHOT.routes[]` as a grouped menu. Group by `letter` field (A / B / C). Multiple routes per letter print indented under that letter's heading:
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
▶ Next Up
|
|
143
|
-
|
|
144
|
-
[A] Execute unfinished work
|
|
145
|
-
→ /rihal:execute-phase 999.5
|
|
146
|
-
→ /rihal:execute-phase 66
|
|
147
|
-
|
|
148
|
-
[B] Plan researched-but-unplanned phases
|
|
149
|
-
→ /rihal:plan-phase 68
|
|
150
|
-
|
|
151
|
-
[C] Close out current milestone
|
|
152
|
-
→ /rihal:audit-milestone
|
|
153
|
-
→ /rihal:complete-milestone
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
The CLI derives routes from current disk state (researched-not-planned phases, phases with pending plans, all-complete detection for milestone closure). Do NOT second-guess by walking disk yourself.
|
|
157
|
-
|
|
158
|
-
If `SNAPSHOT.routes[]` is empty or only has fallback entries, print:
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
▶ Nothing obvious on deck.
|
|
162
|
-
|
|
163
|
-
[A] /rihal:progress — refresh
|
|
164
|
-
[B] /rihal:council — start a conversation on what next
|
|
165
|
-
[C] /rihal:new-milestone — if the current cycle is done
|
|
31
|
+
(/rihal:progress is an alias of /rihal:status --verbose — same data, same source.)
|
|
166
32
|
```
|
|
167
33
|
|
|
168
|
-
|
|
34
|
+
Skip this footer if `$ARGUMENTS` already contains `--no-alias-hint`.
|
|
169
35
|
|
|
170
36
|
</process>
|
|
171
37
|
|
|
172
38
|
## Success Criteria
|
|
173
39
|
|
|
174
|
-
- [ ]
|
|
175
|
-
- [ ] No
|
|
176
|
-
- [ ]
|
|
177
|
-
- [ ] Insights section rendered when non-empty
|
|
178
|
-
- [ ] Next Up is a grouped route tree, not a single suggestion
|
|
40
|
+
- [ ] Calls the status workflow with verbose mode forced
|
|
41
|
+
- [ ] No independent CLI parsing — single source of truth via `progress init`
|
|
42
|
+
- [ ] Optional alias hint footer printed unless suppressed
|
|
179
43
|
|
|
180
44
|
## On Error
|
|
181
45
|
|
|
182
|
-
|
|
183
|
-
- **CLI returns `ok: false`:** surface the CLI's error verbatim. Do not attempt to compensate — the CLI's failures are the source of truth on what's wrong.
|
|
184
|
-
- **Network-dependent insights:** there should be none. Insights are computed from local state + disk only.
|
|
46
|
+
Defer to `.rihal/workflows/status.md` error handling. This workflow adds nothing on top.
|
package/rihal/workflows/scan.md
CHANGED
|
@@ -85,30 +85,115 @@ If user says no, exit.
|
|
|
85
85
|
mkdir -p .planning/codebase
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
## Step 4:
|
|
88
|
+
## Step 4: Announce dispatch (persona-driven)
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Use the canonical dispatch-banner spec at `.rihal/references/dispatch-banner.md`. Read it now if you have not already — it defines the BMAD-style first-person hand-off the user expects.
|
|
91
|
+
|
|
92
|
+
For this workflow, the dispatched agent is `rihal-codebase-mapper` → persona **Dalil (دليل) — Codebase Scout** 🧭.
|
|
93
|
+
|
|
94
|
+
Print the DISPATCH banner per the spec. Filled-in template for this workflow:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
╭─────────────────────────────────────────────────────────╮
|
|
98
|
+
│ 🧭 Dalil (دليل) — Codebase Scout │
|
|
99
|
+
╰─────────────────────────────────────────────────────────╯
|
|
100
|
+
|
|
101
|
+
السلام عليكم — I'm Dalil, your codebase scout.
|
|
102
|
+
|
|
103
|
+
I'll map this repo for you with focus: {focus}{ — topic: "{topic}" if topic else ""}.
|
|
104
|
+
Before I start I'll discover every source root (not just `src/`),
|
|
105
|
+
detect the language mix from manifests, and — if you gave me a
|
|
106
|
+
topic phrase — sweep for it across all roots before narrowing.
|
|
107
|
+
|
|
108
|
+
Scope:
|
|
109
|
+
• Focus: {focus}
|
|
110
|
+
• Topic phrase: {topic-keyword or "none — broad scan"}
|
|
111
|
+
• Read-only: I never edit code.
|
|
112
|
+
|
|
113
|
+
Output:
|
|
114
|
+
→ .planning/codebase/{document_list}
|
|
115
|
+
|
|
116
|
+
Working now — I'll come back with a Scan Scope declaration
|
|
117
|
+
so you see exactly what I covered (and what I skipped).
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Always first-person. Always include the deliverable path. If a topic phrase isn't provided, drop the topic-related lines rather than printing "none".
|
|
121
|
+
|
|
122
|
+
## Step 5: Spawn mapper agent
|
|
123
|
+
|
|
124
|
+
Spawn a single `rihal-codebase-mapper` agent. Pass the persona instructions in the prompt so the agent's own response opens in-character:
|
|
91
125
|
|
|
92
126
|
```
|
|
93
127
|
Task(
|
|
94
|
-
prompt="
|
|
128
|
+
prompt="You are spawned as **Dalil (دليل) — Codebase Scout**. Open your response with a one-line in-character continuity beat (e.g. 'Dalil here — starting the scan.') and sign your closing summary with your persona name. Use first-person.
|
|
129
|
+
|
|
130
|
+
Scan this codebase with focus: {focus}.
|
|
131
|
+
Topic phrase (literal search target, may be empty): {topic-keyword}
|
|
132
|
+
Write results to .planning/codebase/. Produce only: {document_list}.
|
|
133
|
+
|
|
134
|
+
REQUIRED — every document must open with a 'Scan Scope' section per `.rihal/agents-rules/codebase-mapper/detailed-guide.md` that declares:
|
|
135
|
+
- Source roots discovered (top-level non-vendored directories)
|
|
136
|
+
- Source roots searched (grep/glob targets)
|
|
137
|
+
- Source roots NOT searched and why
|
|
138
|
+
- Languages detected (from package manifests)
|
|
139
|
+
- If a topic phrase was provided: a literal `grep -rl '<phrase>' <discovered-roots>` run across ALL source roots, with the file count and an excerpt of matches
|
|
140
|
+
|
|
141
|
+
This scope section is non-negotiable — the orchestrator will reject documents missing it.",
|
|
95
142
|
subagent_type="rihal-codebase-mapper",
|
|
96
143
|
model="{resolved_model}"
|
|
97
144
|
)
|
|
98
145
|
```
|
|
99
146
|
|
|
100
|
-
## Step
|
|
147
|
+
## Step 6: Announce return (persona-driven)
|
|
148
|
+
|
|
149
|
+
When the agent returns, print the RETURNED banner per `.rihal/references/dispatch-banner.md`. Filled-in template:
|
|
101
150
|
|
|
102
151
|
```
|
|
103
|
-
|
|
152
|
+
╭─────────────────────────────────────────────────────────╮
|
|
153
|
+
│ ✓ Dalil — back from scout │
|
|
154
|
+
╰─────────────────────────────────────────────────────────╯
|
|
104
155
|
|
|
105
|
-
|
|
106
|
-
**Documents produced:**
|
|
107
|
-
{list of documents written with line counts}
|
|
156
|
+
Done — here's what I covered for you.
|
|
108
157
|
|
|
109
|
-
|
|
158
|
+
Covered:
|
|
159
|
+
• Searched: {roots actually iterated}
|
|
160
|
+
• Languages: {detected language mix}
|
|
161
|
+
• Topic sweep: {file count + sample paths, or "n/a"}
|
|
162
|
+
|
|
163
|
+
Skipped / blind spots:
|
|
164
|
+
• {explicit list — never leave empty without justification}
|
|
165
|
+
|
|
166
|
+
Wrote:
|
|
167
|
+
→ .planning/codebase/{doc} ({N} lines)
|
|
168
|
+
|
|
169
|
+
{Optional: 1-2 follow-up questions Dalil surfaces in his own voice,
|
|
170
|
+
e.g. "I noticed X — want me to dig deeper?"}
|
|
171
|
+
|
|
172
|
+
I'm still here if you want to follow up on what I found,
|
|
173
|
+
until the next dispatch. — Dalil
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If the document is missing its Scan Scope section, do NOT print the success banner. Instead print:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
⚠ Dalil returned without a Scan Scope declaration.
|
|
180
|
+
Treating this run as incomplete — re-spawn with stricter instructions?
|
|
110
181
|
```
|
|
111
182
|
|
|
183
|
+
## Follow-up framing
|
|
184
|
+
|
|
185
|
+
Until the next `Task()` dispatch, answer follow-up questions about the scan AS Dalil — first-person, sign with `— Dalil`. If the user asks something outside Dalil's scope (e.g. strategy, planning, code editing), hand off explicitly per the dispatch-banner spec and print a fresh DISPATCH banner for the new persona.
|
|
186
|
+
|
|
187
|
+
## Step 7: Final cue (orchestrator-level, after RETURNED banner)
|
|
188
|
+
|
|
189
|
+
The RETURNED banner above is Dalil's voice. After it, the orchestrator may add ONE neutral cue line if the user might want a deeper scan:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
Tip: `/rihal:map-codebase` runs a 4-area parallel scan if you want broader coverage.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Skip this cue if the user already asked for a focused scan — don't push an upsell.
|
|
196
|
+
|
|
112
197
|
</process>
|
|
113
198
|
|
|
114
199
|
<success_criteria>
|