@hanzlaa/rcode 4.12.1 → 4.14.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 +1 -1
- package/CLAUDE.md +1 -1
- package/CONTRIBUTING.md +1 -0
- package/cli/doctor.js +40 -5
- package/cli/install.js +6 -1
- package/dist/rcode.js +87 -87
- package/package.json +1 -1
- package/rcode/agents/rcode-hussain-pm.md +37 -3
- package/rcode/agents/rcode-orchestrator.md +91 -0
- package/rcode/agents/rcode-project-researcher.md +19 -1
- package/rcode/agents/rules/executor/correctness-hazard-scan.md +98 -0
- package/rcode/agents/rules/executor/execution-flow.md +8 -0
- package/rcode/agents/rules/executor/self-check.md +8 -0
- package/rcode/agents/rules/orchestrator/contract.md +76 -0
- package/rcode/agents/rules/sprint-checker/dimensions.md +38 -0
- package/rcode/agents/rules/verifier/reachability-check.md +45 -2
- package/rcode/bin/lib/progress.cjs +41 -13
- package/rcode/bin/lib/roadmap.cjs +62 -22
- package/rcode/bin/lib/state-digest.cjs +88 -0
- package/rcode/bin/rcode-hooks.cjs +192 -23
- package/rcode/bin/rcode-tools.cjs +278 -7
- package/rcode/references/REFERENCES_INDEX.md +3 -1
- package/rcode/references/agent-shared-rules.md +123 -0
- package/rcode/references/code-reviewer-playbook.md +5 -0
- package/rcode/references/executor-playbook.md +2 -0
- package/rcode/references/github-comment-style.md +57 -0
- package/rcode/references/persona-executor-mode.md +61 -0
- package/rcode/references/planner-playbook.md +11 -0
- package/rcode/references/questioning.md +100 -2
- package/rcode/references/response-style.md +21 -4
- package/rcode/references/roadmapper-playbook.md +14 -0
- package/rcode/references/verifier-playbook.md +26 -0
- package/rcode/skills/SKILLS_INDEX.md +1 -1
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/references.md +7 -0
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/rules/merge-strategy.md +19 -3
- package/rcode/skills/actions/4-implementation/rcode-herdr-orchestration/templates/wave-prompt.md +3 -1
- package/rcode/skills/agents/{raees-orchestrator → orchestrator}/SKILL.md +1 -1
- package/rcode/team.yaml +20 -1
- package/rcode/workflows/audit-worktrees.md +15 -1
- package/rcode/workflows/execute-verify-phase-goal.md +58 -2
- package/rcode/workflows/execute.md +43 -8
- package/rcode/workflows/new-project-define-requirements.md +36 -0
- package/rcode/workflows/new-project-research-decision.md +61 -1
- package/rcode/workflows/new-project.md +95 -6
- package/rcode/workflows/plan-research-validation.md +8 -2
- package/rcode/workflows/plan-spawn-planner.md +32 -4
- package/rcode/workflows/plan.md +208 -18
- package/rcode/workflows/pr-branch.md +2 -0
- package/rcode/workflows/research-phase.md +12 -4
- package/rcode/workflows/resume-work.md +18 -0
- package/rcode/workflows/ship.md +4 -0
- package/rcode/workflows/verify-phase.md +40 -0
- package/server/dashboard.js +57 -17
- package/server/lib/html/client/components/OrchPanel.js +6 -2
- package/server/lib/html/client/components/XtermPanel.js +7 -2
- package/server/lib/html/client/orchestrator.js +58 -21
- package/server/lib/html/client/views/MemoryView.js +59 -3
- package/server/lib/html/css.js +40 -0
- package/server/lib/html/shell.js +10 -4
- package/server/lib/scanner.js +150 -3
- package/server/lib/view-only.js +32 -0
- package/server/orchestrator.js +63 -4
- /package/rcode/skills/agents/{raees-orchestrator → orchestrator}/references.md +0 -0
package/rcode/workflows/plan.md
CHANGED
|
@@ -72,7 +72,44 @@ When `--from-stub` is passed:
|
|
|
72
72
|
|
|
73
73
|
This mode exists to skip expensive re-derivation when a human or prior agent has already produced a planning skeleton.
|
|
74
74
|
|
|
75
|
-
## 0.
|
|
75
|
+
## 0. You are Raees for this run (orchestrator contract)
|
|
76
|
+
|
|
77
|
+
@.rcode/agents-rules/orchestrator/contract.md
|
|
78
|
+
|
|
79
|
+
Planning is orchestration too: it spawns a researcher, a planner, a specialist
|
|
80
|
+
panel, and a checker, and it decides what each one gets. Adopt the contract and
|
|
81
|
+
hold it for the whole run.
|
|
82
|
+
|
|
83
|
+
**Open with the orientation banner before the first subagent is spawned**, filled
|
|
84
|
+
from the INIT JSON and the phase's artifacts — not from memory:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
88
|
+
rcode ► RAEES — {project}
|
|
89
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
90
|
+
|
|
91
|
+
Where you are Phase {N} — {name} · {status} · {existing plans, if any}
|
|
92
|
+
What I read {ROADMAP, CONTEXT.md, RESEARCH.md — whichever you actually opened}
|
|
93
|
+
What I'll do {the agents this run will spawn, in order, each named}
|
|
94
|
+
What I need {CONTEXT.md gaps or decisions blocked on the user, or "nothing — starting now"}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Raees does not write the plan.** `rcode-planner` writes SPRINT.md files; the
|
|
98
|
+
panel reviews them; `rcode-sprint-checker` grades them. If you find yourself
|
|
99
|
+
drafting tasks inline, the run has lost its orchestrator — spawn the planner
|
|
100
|
+
instead. A SPRINT.md with no planner `Task()` behind it is the failure this rule
|
|
101
|
+
exists to prevent (see step 8).
|
|
102
|
+
|
|
103
|
+
## 0.4. Record the authorized scope
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
node ".rcode/bin/rcode-tools.cjs" state set-intent plan --source plan.md
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This is what the user asked for on THIS invocation, and it is what `resume-work`
|
|
110
|
+
will restore later. Planning does not authorize building — see step 15.
|
|
111
|
+
|
|
112
|
+
## 0.5. Project-Status Preflight
|
|
76
113
|
|
|
77
114
|
```bash
|
|
78
115
|
PROJECT_STATUS=$(node .rcode/bin/rcode-tools.cjs project-status 2>/dev/null || echo uninitialized)
|
|
@@ -124,11 +161,6 @@ Load all context in one call (paths only to minimize orchestrator context):
|
|
|
124
161
|
```bash
|
|
125
162
|
INIT=$(node ".rcode/bin/rcode-tools.cjs" init sprint-plan "$PHASE" 2>/dev/null)
|
|
126
163
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
127
|
-
AGENT_SKILLS_RESEARCHER=$(node ".rcode/bin/rcode-tools.cjs" agent-skills rcode-phase-researcher 2>/dev/null || echo "")
|
|
128
|
-
AGENT_SKILLS_PLANNER=$(node ".rcode/bin/rcode-tools.cjs" agent-skills rcode-planner 2>/dev/null || echo "")
|
|
129
|
-
AGENT_SKILLS_CHECKER=$(node ".rcode/bin/rcode-tools.cjs" agent-skills rcode-sprint-checker 2>/dev/null || echo "")
|
|
130
|
-
CONTEXT_WINDOW=$(node ".rcode/bin/rcode-tools.cjs" config-get context_window 2>/dev/null)
|
|
131
|
-
CONTEXT_WINDOW=${CONTEXT_WINDOW:-200000} # config-get exits 0 with empty output when key absent; || fallback won't fire
|
|
132
164
|
|
|
133
165
|
# Detect UI signals in phase goal + CONTEXT.md to decide whether to load ui-brand.md (254 lines)
|
|
134
166
|
PHASE_GOAL_HAS_UI=$(grep -iEl "frontend|ui|component|design|style|brand" \
|
|
@@ -141,9 +173,11 @@ If `INIT` is empty, or `INIT.ok` is false or absent (null/undefined — `init sp
|
|
|
141
173
|
Error: rcode-tools init failed. Verify .rcode/ is installed and state.json is valid.
|
|
142
174
|
```
|
|
143
175
|
|
|
176
|
+
**#949 — no separate `agent-skills` / `config-get context_window` calls.** `init sprint-plan` already returns `agent_skills.researcher`, `agent_skills.planner`, `agent_skills.checker` (the same manifest rows the standalone `agent-skills <id>` command returns) and `context_window`, folded in to avoid 4 extra cold Node starts per plan run. Read `agent_skills.researcher` / `.planner` / `.checker` directly from `$INIT` wherever this doc previously referenced `$AGENT_SKILLS_RESEARCHER` / `$AGENT_SKILLS_PLANNER` / `$AGENT_SKILLS_CHECKER`. `CONTEXT_WINDOW` defaults to `200000` when `context_window` is null (config key absent).
|
|
177
|
+
|
|
144
178
|
When `CONTEXT_WINDOW >= 500000`, the planner prompt includes prior phase CONTEXT.md files so cross-phase decisions are consistent (e.g., "use library X for all data fetching" from Phase 2 is visible to Phase 5's planner).
|
|
145
179
|
|
|
146
|
-
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status`, `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`.
|
|
180
|
+
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `specialist_review_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status`, `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`, `context_window`, `agent_skills`, `state_digest`.
|
|
147
181
|
|
|
148
182
|
**If `response_language` is set:** Include `response_language: {value}` in all spawned subagent prompts so any user-facing output stays in the configured language.
|
|
149
183
|
|
|
@@ -153,7 +187,7 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
|
|
|
153
187
|
|
|
154
188
|
## 2. Parse and Normalize Arguments
|
|
155
189
|
|
|
156
|
-
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--from-stub`, `--prd <filepath>`, `--reviews`, `--text`).
|
|
190
|
+
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--from-stub`, `--prd <filepath>`, `--reviews`, `--text`, `--no-panel`).
|
|
157
191
|
|
|
158
192
|
Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from init JSON is `true`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for Claude Code remote sessions (`/rc` mode) where TUI menus don't work through the Claude App.
|
|
159
193
|
|
|
@@ -281,11 +315,11 @@ If `TEXT_MODE` is true, present as a plain-text numbered list:
|
|
|
281
315
|
```
|
|
282
316
|
No CONTEXT.md found for Phase {X}. Plans will use research and requirements only — your design preferences won't be included.
|
|
283
317
|
|
|
284
|
-
1. Continue without context — Plan using research + requirements only
|
|
285
318
|
[If DISCUSS_MODE is "assumptions":]
|
|
286
|
-
|
|
319
|
+
1. Gather context (assumptions mode) [recommended] — Analyze codebase and surface assumptions before planning
|
|
287
320
|
[If DISCUSS_MODE is "discuss" or unset:]
|
|
288
|
-
|
|
321
|
+
1. Run discuss-phase first [recommended] — Capture design decisions before planning
|
|
322
|
+
2. Continue without context — Plan using research + requirements only; your design preferences will not be in the plan
|
|
289
323
|
|
|
290
324
|
Enter number:
|
|
291
325
|
```
|
|
@@ -294,11 +328,13 @@ Otherwise use AskUserQuestion:
|
|
|
294
328
|
- header: "No context"
|
|
295
329
|
- question: "No CONTEXT.md found for Phase {X}. Plans will use research and requirements only — your design preferences won't be included. Continue or capture context first?"
|
|
296
330
|
- options:
|
|
297
|
-
|
|
331
|
+
(Recommended option FIRST — rcode was recommending the skip, which is how phases
|
|
332
|
+
got planned with the user's design decisions never captured.)
|
|
298
333
|
If `DISCUSS_MODE` is `"assumptions"`:
|
|
299
|
-
- "Gather context (assumptions mode)" — Analyze codebase and surface assumptions before planning
|
|
334
|
+
- "Gather context (assumptions mode) (Recommended)" — Analyze codebase and surface assumptions before planning
|
|
300
335
|
If `DISCUSS_MODE` is `"discuss"` (or unset):
|
|
301
|
-
- "Run discuss-phase first" — Capture design decisions before planning
|
|
336
|
+
- "Run discuss-phase first (Recommended)" — Capture design decisions before planning
|
|
337
|
+
- "Continue without context" — Plan using research + requirements only; your design preferences will not be in the plan
|
|
302
338
|
|
|
303
339
|
If "Continue without context": Proceed to step 5.
|
|
304
340
|
If "Run discuss-phase first":
|
|
@@ -571,6 +607,108 @@ Use AskUserQuestion with these 3 options.
|
|
|
571
607
|
**If "Proceed":** Return to planner with instruction to attempt all decisions at full fidelity, accepting more plans/tasks.
|
|
572
608
|
**If "Prioritize":** Use AskUserQuestion (multiSelect) to let user pick which D-XX are "now" vs "later". Create CONTEXT.md for each sub-phase with the selected decisions.
|
|
573
609
|
|
|
610
|
+
## 9.5. Specialist Review Panel (domain-routed)
|
|
611
|
+
|
|
612
|
+
**Why this step exists.** Until now one generalist (`rcode-planner`) produced the
|
|
613
|
+
entire plan and one generalist (`rcode-sprint-checker`) graded it against the
|
|
614
|
+
phase goal. Nobody asked *"is this design wrong"* or *"what will this guard
|
|
615
|
+
miss"*. Confirmed live on a real project: nine phases planned and shipped this
|
|
616
|
+
way, and the first hour of specialist review found an inert RLS backstop, an
|
|
617
|
+
authorization mutation with no relationship check, a core feature whose only
|
|
618
|
+
importer was its own test, and real personal data committed to the repo. The
|
|
619
|
+
sprint-checker missed all four because none of them is a goal-coverage question.
|
|
620
|
+
|
|
621
|
+
**Skip only if:** `specialist_review_enabled` from the INIT JSON is `false`
|
|
622
|
+
(set `workflow.specialist_review: false` in `.rcode/config.yaml`; absent key =
|
|
623
|
+
enabled), or `--no-panel` was passed. In
|
|
624
|
+
autonomous/yolo mode this step is **NOT skippable** — yolo removes the human
|
|
625
|
+
mid-loop check, so plan-time review is the only review left.
|
|
626
|
+
|
|
627
|
+
### 9.5a — Pick the panel
|
|
628
|
+
|
|
629
|
+
**You route this, from context — not from a keyword table.** Read the evidence
|
|
630
|
+
first, then decide:
|
|
631
|
+
|
|
632
|
+
1. The plans' `<files>` fields — every path this phase will actually touch.
|
|
633
|
+
2. Migrations, schema files, and config the plans create or alter.
|
|
634
|
+
3. CONTEXT.md decisions (D-XX) and the phase goal.
|
|
635
|
+
4. The installed roster and what each persona actually owns:
|
|
636
|
+
```bash
|
|
637
|
+
node ".rcode/bin/rcode-tools.cjs" list-agents
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Then pick the domain seats by asking, per candidate: *given these files and
|
|
641
|
+
these decisions, does this persona's lens see something the others cannot?* If
|
|
642
|
+
the answer is no, do not seat them.
|
|
643
|
+
|
|
644
|
+
Run the keyword scorer as **one input, never the verdict**:
|
|
645
|
+
|
|
646
|
+
```bash
|
|
647
|
+
node ".rcode/bin/rcode-tools.cjs" select-panel \
|
|
648
|
+
"${PHASE_GOAL}. ${CONTEXT_DECISIONS_SUMMARY}" --explain
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
It is a weighted keyword table, so it routes on the words the phase text happens
|
|
652
|
+
to use, not on what the phase touches — a phase full of `drizzle/*.sql` RLS
|
|
653
|
+
policies that never writes the word "security" scores near zero for the security
|
|
654
|
+
lens. That is the same enumerate-a-location shape this panel exists to hunt for,
|
|
655
|
+
so treat a high score as corroboration and a zero score as no information.
|
|
656
|
+
|
|
657
|
+
**When your reading and the scorer disagree, your reading wins — and you must
|
|
658
|
+
say so in the panel report**: which persona you seated or dropped against the
|
|
659
|
+
score, and the file or decision that made you do it. A routing override with no
|
|
660
|
+
stated reason is indistinguishable from a coin flip.
|
|
661
|
+
|
|
662
|
+
Panel composition:
|
|
663
|
+
|
|
664
|
+
- **Always include `rcode-waleed`** (architecture lens) — the "is this design
|
|
665
|
+
wrong" seat. Every phase gets it.
|
|
666
|
+
- **Always include `rcode-fatima`** (quality lens) — the "what will this guard
|
|
667
|
+
miss" seat. Every phase gets it.
|
|
668
|
+
- **Plus 1-2 domain personas** chosen by the reading above. Seat a persona only
|
|
669
|
+
when you can name the file, migration, or decision that needs their lens. A
|
|
670
|
+
seat filled to reach a quota costs tokens and adds nothing.
|
|
671
|
+
|
|
672
|
+
Cap the panel at 4. If nothing in the plans needs a third lens, the two standing
|
|
673
|
+
seats ARE the panel — a correct outcome, not a failure to route.
|
|
674
|
+
|
|
675
|
+
### 9.5b — Run the panel in parallel
|
|
676
|
+
|
|
677
|
+
Spawn all panel members in a single message so they run concurrently. Each gets
|
|
678
|
+
the same narrow contract:
|
|
679
|
+
|
|
680
|
+
```
|
|
681
|
+
You are reviewing SPRINT plans BEFORE execution, through your lens only.
|
|
682
|
+
|
|
683
|
+
Read: {PHASE_DIR}/*-SPRINT.md, {PHASE_DIR}/CONTEXT.md, ROADMAP.md phase {N}.
|
|
684
|
+
|
|
685
|
+
Return ONLY blocking issues — things that would make the executed phase wrong,
|
|
686
|
+
unsafe, or unverifiable. Not style, not preferences, not "consider also".
|
|
687
|
+
For each issue: what is wrong, the file:line or task id it lives in, and the
|
|
688
|
+
specific change that fixes it.
|
|
689
|
+
|
|
690
|
+
Two questions you MUST answer explicitly, even if the answer is "none":
|
|
691
|
+
1. Which task in this plan enumerates a LOCATION where it should derive from a
|
|
692
|
+
PROPERTY? (a glob, a single filename, one role, one directory) — that shape
|
|
693
|
+
is how a guard ends up green while pointing where the problem is not.
|
|
694
|
+
2. Which delivered module would have no production importer after this phase
|
|
695
|
+
executes — reachable only from its own test?
|
|
696
|
+
|
|
697
|
+
If you have no blocking issues, return exactly: NO BLOCKING ISSUES.
|
|
698
|
+
Do not restate the plan. Do not summarize. {response_language pass-through}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 9.5c — Handle panel return
|
|
702
|
+
|
|
703
|
+
- All members return `NO BLOCKING ISSUES` → proceed to step 10.
|
|
704
|
+
- Any blocking issue → feed it into the **existing revision loop (step 12)**
|
|
705
|
+
alongside the checker's issues. Do not build a second revision mechanism.
|
|
706
|
+
- Panel issues and checker issues are deduped by task id before re-spawning the
|
|
707
|
+
planner.
|
|
708
|
+
|
|
709
|
+
Report to the user which agents sat on the panel and what each blocked on — a
|
|
710
|
+
silent panel is indistinguishable from a skipped one.
|
|
711
|
+
|
|
574
712
|
## 10. Spawn rcode-sprint-checker Agent
|
|
575
713
|
|
|
576
714
|
Display banner:
|
|
@@ -597,7 +735,7 @@ Checker prompt:
|
|
|
597
735
|
- {research_path} (Technical Research — includes Validation Architecture)
|
|
598
736
|
</files_to_read>
|
|
599
737
|
|
|
600
|
-
|
|
738
|
+
{agent_skills.checker}
|
|
601
739
|
|
|
602
740
|
**Phase requirement IDs (MUST ALL be covered):** {phase_req_ids}
|
|
603
741
|
|
|
@@ -721,7 +859,7 @@ Revision prompt:
|
|
|
721
859
|
- {context_path} (USER DECISIONS from /rcode-discuss-phase)
|
|
722
860
|
</files_to_read>
|
|
723
861
|
|
|
724
|
-
|
|
862
|
+
{agent_skills.planner}
|
|
725
863
|
|
|
726
864
|
**Checker issues:** {structured_issues_from_checker}
|
|
727
865
|
</revision_context>
|
|
@@ -799,7 +937,30 @@ Returns (else branch only):
|
|
|
799
937
|
|
|
800
938
|
After plans pass the checker (or checker is skipped), verify that all phase requirements are covered by at least one plan.
|
|
801
939
|
|
|
802
|
-
**
|
|
940
|
+
**If `phase_req_ids` is empty, the gate does NOT silently skip — it reports why.**
|
|
941
|
+
An empty array has two very different causes and they must not look the same:
|
|
942
|
+
|
|
943
|
+
1. This phase genuinely maps to no requirements. Fine, say so and continue.
|
|
944
|
+
2. REQUIREMENTS.md HAS a traceability table and nothing parsed out of it. That is
|
|
945
|
+
a broken gate reporting as a passing one.
|
|
946
|
+
|
|
947
|
+
Distinguish them before proceeding:
|
|
948
|
+
|
|
949
|
+
```bash
|
|
950
|
+
if [ -f .planning/REQUIREMENTS.md ] && grep -qE '\b[A-Z][A-Z0-9]{1,15}-[0-9]+\b' .planning/REQUIREMENTS.md; then
|
|
951
|
+
echo "⚠ Requirements coverage gate SKIPPED but REQUIREMENTS.md contains requirement IDs."
|
|
952
|
+
echo " phase_req_ids came back empty — the phase→requirement mapping in ROADMAP.md"
|
|
953
|
+
echo " is missing or unparseable, so nothing is verifying coverage for this phase."
|
|
954
|
+
echo " Fix the phase's **Requirements:** line in ROADMAP.md, then re-run."
|
|
955
|
+
fi
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
Surface that warning to the user; do not bury it. Confirmed live: a project's
|
|
959
|
+
requirement IDs were all domain-prefixed (`FOUND-01`, `RENT-04`), the extractor
|
|
960
|
+
only matched `REQ-*`, and this gate skipped itself on every phase while
|
|
961
|
+
appearing to pass.
|
|
962
|
+
|
|
963
|
+
Then proceed to step 14 when the array really is empty.
|
|
803
964
|
|
|
804
965
|
**Step 1: Extract requirement IDs claimed by plans**
|
|
805
966
|
```bash
|
|
@@ -901,7 +1062,34 @@ if ([[ "$ARGUMENTS" =~ --auto ]] || [[ "$ARGUMENTS" =~ --chain ]]) && [[ "$AUTO_
|
|
|
901
1062
|
fi
|
|
902
1063
|
```
|
|
903
1064
|
|
|
904
|
-
|
|
1065
|
+
**`AUTO_CFG` alone is NOT sufficient.** A persistent `workflow.auto_advance: true`
|
|
1066
|
+
in settings must never silently turn "plan this" into "plan and build this". The
|
|
1067
|
+
user's invocation is their declared scope: they typed a planning command, so
|
|
1068
|
+
planning is what was authorized. A config flag set weeks ago is not consent for
|
|
1069
|
+
this build.
|
|
1070
|
+
|
|
1071
|
+
Confirmed live: a user asked for a project to be planned, `auto_advance` was on,
|
|
1072
|
+
and the session planned and then built a WordPress theme, then migrated the whole
|
|
1073
|
+
thing to Astro to undo its own stack choice. The user's words were "plan karo".
|
|
1074
|
+
Nothing in the loop stopped at the boundary they actually drew.
|
|
1075
|
+
|
|
1076
|
+
**If `AUTO_CFG` is true but neither `--auto`/`--chain` nor `AUTO_CHAIN` is set:**
|
|
1077
|
+
ask before advancing, and default to stopping:
|
|
1078
|
+
|
|
1079
|
+
```
|
|
1080
|
+
AskUserQuestion:
|
|
1081
|
+
question: "Plans are ready. auto_advance is on in your config — execute phase {N} now?"
|
|
1082
|
+
options:
|
|
1083
|
+
- label: "Stop here (Recommended)"
|
|
1084
|
+
description: "Plans written and verified. Review them, then run /rcode-execute {N} when ready."
|
|
1085
|
+
- label: "Execute now"
|
|
1086
|
+
description: "Chain straight into execution, as auto_advance requests."
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
In `--text` mode present this as a numbered list. If the user does not answer,
|
|
1090
|
+
STOP — an unanswered question is not approval.
|
|
1091
|
+
|
|
1092
|
+
**If `--auto` or `--chain` flag present OR `AUTO_CHAIN` is true:**
|
|
905
1093
|
|
|
906
1094
|
Display banner:
|
|
907
1095
|
```
|
|
@@ -1029,6 +1217,8 @@ ${WINDOWS === 'true' ? '@.rcode/references/plan-windows-troubleshooting.md' : ''
|
|
|
1029
1217
|
- [ ] Phase directory created if needed
|
|
1030
1218
|
- [ ] CONTEXT.md loaded early (step 4) and passed to ALL agents
|
|
1031
1219
|
- [ ] Research completed (unless --skip-research or --gaps or exists)
|
|
1220
|
+
- [ ] Auto-advance fired only on an explicit `--auto`/`--chain` or an answered confirmation, never on `auto_advance` config alone
|
|
1221
|
+
- [ ] Specialist review panel spawned (Waleed + Fatima + domain agents) and its blocking issues fed into the revision loop, or `workflow.specialist_review: false` recorded
|
|
1032
1222
|
- [ ] rcode-phase-researcher spawned with CONTEXT.md
|
|
1033
1223
|
- [ ] Existing plans checked
|
|
1034
1224
|
- [ ] rcode-planner spawned with CONTEXT.md + RESEARCH.md
|
|
@@ -114,6 +114,8 @@ Next steps:
|
|
|
114
114
|
git push origin {PR_BRANCH}
|
|
115
115
|
gh pr create --base {TARGET} --head {PR_BRANCH}
|
|
116
116
|
|
|
117
|
+
(PR title/body must follow @rcode/references/github-comment-style.md)
|
|
118
|
+
|
|
117
119
|
Or use /rcode-ship to create the PR automatically.
|
|
118
120
|
```
|
|
119
121
|
</step>
|
|
@@ -42,11 +42,13 @@ If exists: Offer update/view/skip options.
|
|
|
42
42
|
INIT=$(node ".rcode/bin/rcode-tools.cjs" init phase-op "${PHASE}" 2>/dev/null)
|
|
43
43
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
44
44
|
# If INIT is empty or INIT.ok is false: print "Error: rcode-tools init failed." and exit.
|
|
45
|
-
# Extract: phase_dir, padded_phase, phase_number,
|
|
45
|
+
# Extract: phase_dir, padded_phase, phase_number, requirements_path, context_path, response_language,
|
|
46
|
+
# state_digest, agent_skills.researcher
|
|
46
47
|
# If response_language is set, include "Respond in {value}." in all spawned subagent prompts.
|
|
47
|
-
AGENT_SKILLS_RESEARCHER=$(node ".rcode/bin/rcode-tools.cjs" agent-skills rcode-phase-researcher 2>/dev/null || echo "")
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
**#949 — no separate `agent-skills rcode-phase-researcher` call.** `init phase-op` already returns `agent_skills.researcher` (same manifest row), folded in to avoid a second cold Node start per research run.
|
|
51
|
+
|
|
50
52
|
## Step 4: Spawn Researcher
|
|
51
53
|
|
|
52
54
|
```
|
|
@@ -69,10 +71,16 @@ Downstream consumer: /rcode-plan reads {phase}-RESEARCH.md to structure sprints.
|
|
|
69
71
|
<files_to_read>
|
|
70
72
|
- {context_path} (USER DECISIONS from /rcode-discuss-phase)
|
|
71
73
|
- {requirements_path} (Project requirements)
|
|
72
|
-
- {state_path} (Project decisions and history)
|
|
73
74
|
</files_to_read>
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
<project_state_digest>
|
|
77
|
+
{state_digest as JSON — current phase, recent decisions, open blockers. Slim
|
|
78
|
+
extract of state.json (#948); do NOT separately Read .rcode/state.json — its
|
|
79
|
+
full history (all phases, all sprints) is not needed here and costs 10-20K+
|
|
80
|
+
tokens on a mature project.}
|
|
81
|
+
</project_state_digest>
|
|
82
|
+
|
|
83
|
+
{agent_skills.researcher}
|
|
76
84
|
|
|
77
85
|
<additional_context>
|
|
78
86
|
Phase description: {description}
|
|
@@ -197,6 +197,24 @@ Based on project state, determine the most logical next action:
|
|
|
197
197
|
</step>
|
|
198
198
|
|
|
199
199
|
<step name="offer_options">
|
|
200
|
+
**"Resume" restores position, never scope.** It tells you WHERE the work stopped;
|
|
201
|
+
it does not tell you what the user authorized. Present options and wait — do not
|
|
202
|
+
pick one and start.
|
|
203
|
+
|
|
204
|
+
The failure this prevents: a user asks for a project to be planned, the session
|
|
205
|
+
plans it, the user later types "resume", and the session reads that as "keep
|
|
206
|
+
going" and starts building. The standing instruction was still "plan". Confirmed
|
|
207
|
+
live, and the cleanup cost more than the work.
|
|
208
|
+
|
|
209
|
+
Two rules:
|
|
210
|
+
|
|
211
|
+
- **Never begin implementation from a resume.** If the state says the phase is
|
|
212
|
+
ready to execute, that is an option to OFFER, not an action to take. Execution
|
|
213
|
+
starts when the user runs `/rcode-execute`, not when a menu suggests it.
|
|
214
|
+
- **Say what the last authorized scope was**, if the state records one (see
|
|
215
|
+
`state read` → `last_intent`, written by plan.md and execute.md). If it does not, say that plainly rather than
|
|
216
|
+
inferring one: `Last recorded scope: unknown — tell me plan or build.`
|
|
217
|
+
|
|
200
218
|
Present contextual options based on project state:
|
|
201
219
|
|
|
202
220
|
```
|
package/rcode/workflows/ship.md
CHANGED
|
@@ -246,6 +246,10 @@ Omit this section entirely when VERIFICATION.md status is a clean `passed` with
|
|
|
246
246
|
</step>
|
|
247
247
|
|
|
248
248
|
<step name="create_pr">
|
|
249
|
+
**Before creating the PR, apply `@rcode/references/github-comment-style.md` to `${PR_BODY}`** —
|
|
250
|
+
no em-dashes, no gates/CI block, no git-process talk, no AI attribution. Run the
|
|
251
|
+
self-check greps in that reference; they must print nothing.
|
|
252
|
+
|
|
249
253
|
Create the PR using the generated body:
|
|
250
254
|
|
|
251
255
|
```bash
|
|
@@ -380,6 +380,43 @@ Fill template sections: frontmatter (phase/timestamp/status/score), goal achieve
|
|
|
380
380
|
See .rcode/templates/verification-report.md for complete template.
|
|
381
381
|
</step>
|
|
382
382
|
|
|
383
|
+
<step name="falsification_pass">
|
|
384
|
+
**MANDATORY when `$STATUS` is `passed`.** Run the falsification pass defined in
|
|
385
|
+
`@.rcode/workflows/execute-verify-phase-goal.md` — a second agent, given the goal
|
|
386
|
+
and the codebase but NOT the summaries or this report, whose only job is to
|
|
387
|
+
refute the pass. Attack order: production reachability, guard shape, runtime
|
|
388
|
+
truth, claim-without-evidence.
|
|
389
|
+
|
|
390
|
+
- REFUTED → set `$STATUS=gaps_found`, append the finding to VERIFICATION.md's
|
|
391
|
+
gaps section, and skip the write-back below.
|
|
392
|
+
- UPHELD → record `falsification: upheld` in VERIFICATION.md frontmatter.
|
|
393
|
+
|
|
394
|
+
A `passed` VERIFICATION.md with no `falsification:` key is self-certified and
|
|
395
|
+
must not be treated as verified by any downstream command.
|
|
396
|
+
</step>
|
|
397
|
+
|
|
398
|
+
<step name="write_back_state">
|
|
399
|
+
**MANDATORY when status is `passed`. Verification is the terminal step of the
|
|
400
|
+
plan → execute → verify loop, and a terminal step with no write-back leaves
|
|
401
|
+
state.json permanently stale.** A phase can be verified green on disk while
|
|
402
|
+
state.json still says `planned` — and because `state sync --from-disk` only
|
|
403
|
+
advances status, nothing downstream ever corrects it. `/rcode-status` then
|
|
404
|
+
reports 1/13 when the real number is 9/13.
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
if [ "$STATUS" = "passed" ]; then # only reachable when falsification upheld
|
|
408
|
+
node ".rcode/bin/rcode-tools.cjs" phase complete "${PHASE_NUMBER}" 2>&1
|
|
409
|
+
fi
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Check the result. If it returns `ok: false` or a non-zero exit, say so in the
|
|
413
|
+
report to the orchestrator — do NOT report the phase as verified-and-recorded
|
|
414
|
+
when only the file was written. If the phase is not yet in `state.phases`, run
|
|
415
|
+
`node ".rcode/bin/rcode-tools.cjs" state sync --from-disk` first, then retry.
|
|
416
|
+
|
|
417
|
+
Do not run this for `gaps_found` or `human_needed` — those are not complete.
|
|
418
|
+
</step>
|
|
419
|
+
|
|
383
420
|
<step name="return_to_orchestrator">
|
|
384
421
|
Return status (`passed` | `gaps_found` | `human_needed`), score (N/M must-haves), report path.
|
|
385
422
|
|
|
@@ -404,7 +441,10 @@ Orchestrator routes: `passed` → update_roadmap | `gaps_found` → create/execu
|
|
|
404
441
|
- [ ] Overall status determined
|
|
405
442
|
- [ ] Deferred items filtered against later milestone phases (if gaps found)
|
|
406
443
|
- [ ] Fix plans generated (if gaps_found after filtering)
|
|
444
|
+
- [ ] Falsification pass run and `falsification:` recorded whenever status is `passed`
|
|
445
|
+
- [ ] Production reachability checked for every delivered non-UI module (importers classified production vs test; inline re-implementations named)
|
|
407
446
|
- [ ] VERIFICATION.md created with complete report
|
|
447
|
+
- [ ] Phase marked complete in state.json via `phase complete` when status is `passed` (write-back confirmed, not assumed)
|
|
408
448
|
- [ ] Results returned to orchestrator
|
|
409
449
|
</success_criteria>
|
|
410
450
|
|
package/server/dashboard.js
CHANGED
|
@@ -25,7 +25,7 @@ const path = require('path');
|
|
|
25
25
|
const fs = require('fs');
|
|
26
26
|
const os = require('os');
|
|
27
27
|
const crypto = require('crypto');
|
|
28
|
-
const {
|
|
28
|
+
const { fork } = require('child_process');
|
|
29
29
|
|
|
30
30
|
// Client JS modules live here and are served verbatim at /js/<name>.js
|
|
31
31
|
const CLIENT_DIR = path.join(__dirname, 'lib', 'html', 'client');
|
|
@@ -33,12 +33,17 @@ const CLIENT_DIR = path.join(__dirname, 'lib', 'html', 'client');
|
|
|
33
33
|
const { scanState } = require('./lib/scanner');
|
|
34
34
|
const { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory, handleApiAgents } = require('./lib/api');
|
|
35
35
|
const { renderHtml } = require('./lib/html/shell');
|
|
36
|
+
const { isViewOnly } = require('./lib/view-only');
|
|
36
37
|
|
|
37
38
|
// ---------- Configuration ----------
|
|
38
39
|
let PORT = parseInt(process.env.PORT || '7717', 10);
|
|
39
|
-
// #
|
|
40
|
-
//
|
|
41
|
-
|
|
40
|
+
// #1037 — the orchestrator's actual port is NOT known until the child process
|
|
41
|
+
// reports back over IPC that it bound successfully (see spawnOrchestrator()).
|
|
42
|
+
// null means "no orchestrator is currently running for this dashboard" — the
|
|
43
|
+
// client must render orchestration as disabled rather than guess a port.
|
|
44
|
+
// NEVER default this to a constant like 7718: that constant may be another
|
|
45
|
+
// project's dashboard's orchestrator.
|
|
46
|
+
let orchPort = null;
|
|
42
47
|
const RCODE_DIR = process.env.RCODE_DIR || path.join(process.cwd(), '.rcode');
|
|
43
48
|
const PROJECT_ROOT = path.dirname(RCODE_DIR);
|
|
44
49
|
// Fallback root for agent prompts when rcode is installed as a package (not run
|
|
@@ -150,7 +155,7 @@ function handleRequest(req, res) {
|
|
|
150
155
|
return;
|
|
151
156
|
}
|
|
152
157
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
153
|
-
res.end(JSON.stringify({ token: ORCH_TOKEN, orchPort:
|
|
158
|
+
res.end(JSON.stringify({ token: ORCH_TOKEN, orchPort, projectRoot: PROJECT_ROOT }));
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
161
|
|
|
@@ -181,7 +186,12 @@ function handleRequest(req, res) {
|
|
|
181
186
|
|
|
182
187
|
if (url === '/' || url === '/index.html') {
|
|
183
188
|
const state = scanState(RCODE_DIR);
|
|
184
|
-
|
|
189
|
+
// #967 — hide Run/Stop/Clean affordances in view-only mode. This is a UI
|
|
190
|
+
// convenience only; the actual refusal happens server-side in
|
|
191
|
+
// orchestrator.js's POST /api/run handler regardless of this flag.
|
|
192
|
+
// #1037 — orchPort (not ORCH_PORT) so we never advertise an orchestrator
|
|
193
|
+
// this process did not spawn; null renders orchestration as disabled.
|
|
194
|
+
const html = renderHtml(state, ORCH_TOKEN, orchPort, PROJECT_ROOT, isViewOnly(PROJECT_ROOT));
|
|
185
195
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' });
|
|
186
196
|
res.end(html);
|
|
187
197
|
return;
|
|
@@ -215,9 +225,14 @@ server.listen(PORT, '127.0.0.1', () => {
|
|
|
215
225
|
console.log(` Mode: live (read + orchestration)`);
|
|
216
226
|
console.log(` Scanning: ${RCODE_DIR}`);
|
|
217
227
|
console.log(` Refresh: 30s soft poll`);
|
|
218
|
-
console.log(` Note:
|
|
228
|
+
console.log(` Note: the orchestrator (internal API, not for the browser) starts scanning near port ${PORT + 1}`);
|
|
219
229
|
console.log(` Stop: kill $(ss -ltnp 'sport = :${PORT}' | awk 'NR>1{match($6,/pid=([0-9]+)/,m); print m[1]}')`);
|
|
220
230
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
231
|
+
// #1037 — spawn the orchestrator only now that PORT has SETTLED (any
|
|
232
|
+
// EADDRINUSE retries above have already run their course by the time this
|
|
233
|
+
// 'listening' callback fires). Spawning it earlier would derive its port
|
|
234
|
+
// from a PORT value that might still change underneath it.
|
|
235
|
+
ensurePty(spawnOrchestrator);
|
|
221
236
|
});
|
|
222
237
|
|
|
223
238
|
// ── Ensure interactive-terminal native module is present ─────────
|
|
@@ -242,16 +257,32 @@ function ensurePty(done) {
|
|
|
242
257
|
done();
|
|
243
258
|
}
|
|
244
259
|
|
|
245
|
-
// ── Auto-spawn orchestrator
|
|
260
|
+
// ── Auto-spawn orchestrator ───────────────────────────────────────
|
|
261
|
+
// #1037 — the orchestrator's port is derived from THIS dashboard's own final
|
|
262
|
+
// bound PORT (never a hardcoded constant), and the orchestrator runs its own
|
|
263
|
+
// free-port scan rather than exiting on the first conflict (see
|
|
264
|
+
// orchestrator.js's listen/retry block). We fork() (not spawn()) so the child
|
|
265
|
+
// gets an IPC channel: it reports back the port it actually bound once
|
|
266
|
+
// listening succeeds, or an error if its scan is exhausted. Until that
|
|
267
|
+
// message arrives, `orchPort` stays null and the dashboard advertises
|
|
268
|
+
// orchestration as disabled — it must never guess a port it did not spawn.
|
|
246
269
|
const ORCH_BIN = path.join(__dirname, 'orchestrator.js');
|
|
247
270
|
let _orchProc = null;
|
|
248
271
|
|
|
249
272
|
function spawnOrchestrator() {
|
|
273
|
+
orchPort = null;
|
|
250
274
|
try {
|
|
251
|
-
_orchProc =
|
|
275
|
+
_orchProc = fork(ORCH_BIN, [], {
|
|
252
276
|
cwd: path.join(__dirname, '..'),
|
|
253
|
-
env: {
|
|
254
|
-
|
|
277
|
+
env: {
|
|
278
|
+
...process.env,
|
|
279
|
+
ORCH_TOKEN, RCODE_DIR, PROJECT_ROOT,
|
|
280
|
+
DASH_PORT: String(PORT),
|
|
281
|
+
// Respect an explicit user override; otherwise start the orchestrator's
|
|
282
|
+
// own scan right after this dashboard's actual port.
|
|
283
|
+
ORCH_PORT: process.env.ORCH_PORT || String(PORT + 1),
|
|
284
|
+
},
|
|
285
|
+
silent: true,
|
|
255
286
|
});
|
|
256
287
|
_orchProc.stdout.on('data', chunk => {
|
|
257
288
|
const msg = chunk.toString().trim();
|
|
@@ -261,12 +292,23 @@ function spawnOrchestrator() {
|
|
|
261
292
|
const msg = chunk.toString().trim();
|
|
262
293
|
if (msg && !msg.includes('no stdin')) console.error('[orch]', msg);
|
|
263
294
|
});
|
|
295
|
+
_orchProc.on('message', msg => {
|
|
296
|
+
if (!msg || typeof msg !== 'object') return;
|
|
297
|
+
if (msg.type === 'orch-ready' && Number.isInteger(msg.port)) {
|
|
298
|
+
orchPort = msg.port;
|
|
299
|
+
console.log(`[orch] orchestrator ready (port ${orchPort})`);
|
|
300
|
+
} else if (msg.type === 'orch-error') {
|
|
301
|
+
orchPort = null;
|
|
302
|
+
console.log(`[orch] orchestrator failed to bind (${msg.reason}) — orchestration disabled for this dashboard.`);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
264
305
|
_orchProc.on('exit', (code, signal) => {
|
|
265
306
|
_orchProc = null;
|
|
307
|
+
orchPort = null; // never keep advertising a port nothing is listening on
|
|
266
308
|
if (signal !== 'SIGTERM' && signal !== 'SIGINT') {
|
|
267
309
|
if (code === 2) {
|
|
268
|
-
// Port-conflict exit — don't loop. Dashboard stays
|
|
269
|
-
console.log(`[orch] orchestrator
|
|
310
|
+
// Port-conflict exit (scan exhausted) — don't loop. Dashboard stays functional.
|
|
311
|
+
console.log(`[orch] orchestrator could not find a free port — not restarting. Set ORCH_PORT=<N> env var to use a different range. Dashboard is still functional.`);
|
|
270
312
|
return;
|
|
271
313
|
}
|
|
272
314
|
console.log(`[orch] exited (${code}) — restarting in 3s…`);
|
|
@@ -276,16 +318,14 @@ function spawnOrchestrator() {
|
|
|
276
318
|
_orchProc.on('error', err => {
|
|
277
319
|
console.error('[orch] spawn error:', err.message);
|
|
278
320
|
_orchProc = null;
|
|
321
|
+
orchPort = null;
|
|
279
322
|
});
|
|
280
|
-
console.log(`[orch] orchestrator started (port ${ORCH_PORT})`);
|
|
281
323
|
} catch (err) {
|
|
282
324
|
console.error('[orch] failed to start:', err.message);
|
|
325
|
+
orchPort = null;
|
|
283
326
|
}
|
|
284
327
|
}
|
|
285
328
|
|
|
286
|
-
// Orchestrator spawns only once node-pty is settled (present or installed).
|
|
287
|
-
ensurePty(spawnOrchestrator);
|
|
288
|
-
|
|
289
329
|
// Graceful shutdown
|
|
290
330
|
function shutdown() {
|
|
291
331
|
if (_orchProc) { try { _orchProc.kill('SIGTERM'); } catch {} }
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { html, useState, useEffect, useRef, useCallback } from '../preact.js';
|
|
17
17
|
import { useStore, setState } from '../store.js';
|
|
18
|
-
import { orchToken, stopSession, cleanSessions, orchWs } from '../orchestrator.js';
|
|
18
|
+
import { orchToken, stopSession, cleanSessions, orchWs, isOrchAvailable, projectRoot } from '../orchestrator.js';
|
|
19
19
|
import { showToast } from './shared.js';
|
|
20
20
|
import { Icon } from '../icons-client.js';
|
|
21
21
|
|
|
@@ -105,9 +105,13 @@ export function OrchPanel() {
|
|
|
105
105
|
showToast('No orchestrator token — restart the dashboard');
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
|
+
if (!isOrchAvailable()) {
|
|
109
|
+
showToast('Orchestration is disabled — no orchestrator is running for this project');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
108
112
|
const ws = new WebSocket(
|
|
109
113
|
orchWs() + '/ws/' + encodeURIComponent(storyId) +
|
|
110
|
-
'?token=' + encodeURIComponent(tok)
|
|
114
|
+
'?token=' + encodeURIComponent(tok) + '&root=' + encodeURIComponent(projectRoot())
|
|
111
115
|
);
|
|
112
116
|
_streams[storyId] = ws;
|
|
113
117
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { html, useEffect, useRef, useCallback } from '../preact.js';
|
|
27
27
|
import { useStore, setState } from '../store.js';
|
|
28
|
-
import { orchToken, stopSession, orchWs } from '../orchestrator.js';
|
|
28
|
+
import { orchToken, stopSession, orchWs, isOrchAvailable, projectRoot } from '../orchestrator.js';
|
|
29
29
|
|
|
30
30
|
// ── Internal state (module-scoped, one panel at a time) ──────────────────────
|
|
31
31
|
// These are NOT component state because the xterm instance (and the story it
|
|
@@ -103,8 +103,13 @@ function connectWs(storyId) {
|
|
|
103
103
|
if (_term) _term.writeln('\r\n\x1b[31m✗ No orchestrator token — restart the dashboard\x1b[0m');
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
|
+
if (!isOrchAvailable()) {
|
|
107
|
+
if (_term) _term.writeln('\r\n\x1b[31m✗ Orchestration is disabled — no orchestrator is running for this project\x1b[0m');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
106
110
|
setStatus('connecting');
|
|
107
|
-
const url = orchWs() + '/ws/' + encodeURIComponent(storyId) +
|
|
111
|
+
const url = orchWs() + '/ws/' + encodeURIComponent(storyId) +
|
|
112
|
+
'?token=' + encodeURIComponent(tok) + '&root=' + encodeURIComponent(projectRoot());
|
|
108
113
|
const ws = new WebSocket(url);
|
|
109
114
|
_termWs = ws;
|
|
110
115
|
|