@hanzlaa/rcode 4.12.0 → 4.13.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/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/state-digest.cjs +88 -0
- package/rcode/bin/rcode-hooks.cjs +192 -23
- package/rcode/bin/rcode-tools.cjs +94 -4
- package/rcode/references/REFERENCES_INDEX.md +3 -1
- package/rcode/references/agent-shared-rules.md +87 -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/response-style.md +21 -4
- package/rcode/references/verifier-playbook.md +14 -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/templates/settings-hooks.json +12 -12
- package/rcode/workflows/audit-worktrees.md +15 -1
- package/rcode/workflows/enable-hooks.md +6 -0
- package/rcode/workflows/execute-verify-phase-goal.md +58 -2
- package/rcode/workflows/execute.md +37 -8
- package/rcode/workflows/plan-research-validation.md +8 -2
- package/rcode/workflows/plan-spawn-planner.md +32 -4
- package/rcode/workflows/plan.md +138 -10
- package/rcode/workflows/pr-branch.md +2 -0
- package/rcode/workflows/research-phase.md +12 -4
- 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
|
@@ -70,10 +70,16 @@ Answer: "What do I need to know to PLAN this phase well?"
|
|
|
70
70
|
<files_to_read>
|
|
71
71
|
- {context_path} (USER DECISIONS from /rcode-discuss-phase)
|
|
72
72
|
- {requirements_path} (Project requirements)
|
|
73
|
-
- {state_path} (Project decisions and history)
|
|
74
73
|
</files_to_read>
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
<project_state_digest>
|
|
76
|
+
{state_digest as JSON — current phase, recent decisions, open blockers. Slim
|
|
77
|
+
extract of state.json (#948); do NOT separately Read .rcode/state.json — its
|
|
78
|
+
full history (all phases, all sprints) is not needed here and costs 10-20K+
|
|
79
|
+
tokens on a mature project.}
|
|
80
|
+
</project_state_digest>
|
|
81
|
+
|
|
82
|
+
{agent_skills.researcher}
|
|
77
83
|
|
|
78
84
|
<additional_context>
|
|
79
85
|
**Phase description:** {phase_description}
|
|
@@ -34,14 +34,20 @@ When `GAPS_MODE=true`, use the prompt below in place of the standard planner pro
|
|
|
34
34
|
|
|
35
35
|
<files_to_read>
|
|
36
36
|
- {VERIFICATION_FILE} (Authoritative verification report — source of truth for gaps)
|
|
37
|
-
- {state_path} (Project State)
|
|
38
37
|
- {roadmap_path} (Roadmap)
|
|
39
38
|
- {requirements_path} (Requirements)
|
|
40
39
|
- Existing plan files in this phase: {EXISTING_PLAN_FILES}
|
|
41
40
|
- Existing summary files in this phase: {EXISTING_SUMMARY_FILES}
|
|
42
41
|
</files_to_read>
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
<project_state_digest>
|
|
44
|
+
{state_digest as JSON — current phase, recent decisions, open blockers. Slim
|
|
45
|
+
extract of state.json (#948); do NOT separately Read .rcode/state.json — its
|
|
46
|
+
full history (all phases, all sprints) is not needed here and costs 10-20K+
|
|
47
|
+
tokens on a mature project.}
|
|
48
|
+
</project_state_digest>
|
|
49
|
+
|
|
50
|
+
{agent_skills.planner}
|
|
45
51
|
|
|
46
52
|
<gap_list>
|
|
47
53
|
{Serialized GAP_LIST — for each gap include id, title, expected behavior, actual behavior, status (gap_found|partial), and source section.}
|
|
@@ -86,7 +92,6 @@ Default: `phase` (one SPRINT.md, up to 8 stories — see Scope-Driven Sizing in
|
|
|
86
92
|
**Self-upgrade to `initiative` mid-decomposition** if, once you've read CONTEXT.md/ROADMAP.md and started breaking down the work, it splits into independent waves or work-streams (e.g. shared-primitive foundation → feature-local migrations → cleanup/tests) and total stories would exceed 8. When that happens, emit multiple SPRINT.md files (`{phase}-1-SPRINT.md`, `{phase}-2-SPRINT.md`, ...) in this same run instead of one oversized plan. Do not wait for rcode-sprint-checker's "scope exceeds context budget" rejection to force a resharding pass — that costs a full extra planner run. Decide the split now, while you're already looking at the file/story list.
|
|
87
93
|
|
|
88
94
|
<files_to_read>
|
|
89
|
-
- {state_path} (Project State)
|
|
90
95
|
- {roadmap_path} (Roadmap)
|
|
91
96
|
- {requirements_path} (Requirements)
|
|
92
97
|
- {context_path} (USER DECISIONS from /rcode-discuss-phase — read `<decisions>` for locked choices AND `<code_context>` for existing code patterns, reusable assets, and architectural notes gathered during discuss-phase)
|
|
@@ -102,7 +107,14 @@ ${CONTEXT_WINDOW >= 500000 ? `
|
|
|
102
107
|
` : ''}
|
|
103
108
|
</files_to_read>
|
|
104
109
|
|
|
105
|
-
|
|
110
|
+
<project_state_digest>
|
|
111
|
+
{state_digest as JSON — current phase, recent decisions, open blockers. Slim
|
|
112
|
+
extract of state.json (#948); do NOT separately Read .rcode/state.json — its
|
|
113
|
+
full history (all phases, all sprints) is not needed here and costs 10-20K+
|
|
114
|
+
tokens on a mature project.}
|
|
115
|
+
</project_state_digest>
|
|
116
|
+
|
|
117
|
+
{agent_skills.planner}
|
|
106
118
|
|
|
107
119
|
**Phase requirement IDs (every ID MUST appear in a plan's `requirements` field):** {phase_req_ids}
|
|
108
120
|
|
|
@@ -266,6 +278,21 @@ Every task MUST include these fields — they are NOT optional:
|
|
|
266
278
|
4. **`<verify>`** — Shell commands that PROVE the `<done>` criteria are met. Run by executor after task completes and by verifier during post-execution check. The block MUST contain an `<automated>` child with the exact commands to run (Dimension 8 hard-blocks without it). Rules:
|
|
267
279
|
- `<automated>` commands must exit 0 on success, non-zero on failure
|
|
268
280
|
- Prefer `grep -q` for presence checks, `test -f` for file existence, project test runner for behavior
|
|
281
|
+
- **A task that adds or changes a guard test MUST prove the test fails on the
|
|
282
|
+
unfixed code first ("red first").** A guard test is any test whose job is to
|
|
283
|
+
make a class of mistake impossible: meta-tests, schema invariant tests,
|
|
284
|
+
forbidden-pattern scans, coverage/manifest tests, lint-rule tests. Write the
|
|
285
|
+
task so the `<action>` states the red step explicitly ("run the new test on
|
|
286
|
+
the current tree and record that it fails, then apply the fix") and the
|
|
287
|
+
`<verify><automated>` block asserts the green state. A guard test that has
|
|
288
|
+
never been observed red is indistinguishable from one that greps the wrong
|
|
289
|
+
path — that is exactly how a `route.ts`-only glob left every Server Action
|
|
290
|
+
unguarded for nine phases while CI stayed green the whole time.
|
|
291
|
+
- **Absence checks use `! grep -q PATTERN FILE`, never `grep -qv`.** `-v` inverts per-line
|
|
292
|
+
matching, so `grep -qv PATTERN FILE` exits 0 as soon as ANY line fails to match — i.e. it
|
|
293
|
+
passes on virtually every file, including one that contains the forbidden pattern. This is a
|
|
294
|
+
silently-always-green assertion, the worst possible verify command. Same for "file must not
|
|
295
|
+
exist": `! test -f path`.
|
|
269
296
|
- Keep commands short and composable — one check per line
|
|
270
297
|
- If test file doesn't exist yet (TDD tasks), write `<automated>MISSING</automated>` and add a Wave 0 task to create the test
|
|
271
298
|
- Example structure:
|
|
@@ -323,6 +350,7 @@ Every task MUST include these fields — they are NOT optional:
|
|
|
323
350
|
- [ ] Every task has `<files>` listing exact files this task will modify or create
|
|
324
351
|
- [ ] Every task has `<evidence>` with grep/lines/creates codebase grounding per issue #649 — not a prose checklist tag (none exists in the real plan schema)
|
|
325
352
|
- [ ] Every task has `<verify>` with an `<automated>` child containing at least one shell command (Dimension 8 blocker)
|
|
353
|
+
- [ ] Every guard-test task states its red-first step in `<action>` (Dimension 8 blocker)
|
|
326
354
|
- [ ] Every task has `<done>` with a single observable acceptance sentence (Dimension 2 requirement)
|
|
327
355
|
- [ ] Every `<action>` contains concrete values (no "align X with Y" without specifying what)
|
|
328
356
|
- [ ] Tasks extending existing code have `<interfaces>` with relevant signatures
|
package/rcode/workflows/plan.md
CHANGED
|
@@ -72,7 +72,35 @@ 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.5. Project-Status Preflight
|
|
76
104
|
|
|
77
105
|
```bash
|
|
78
106
|
PROJECT_STATUS=$(node .rcode/bin/rcode-tools.cjs project-status 2>/dev/null || echo uninitialized)
|
|
@@ -124,11 +152,6 @@ Load all context in one call (paths only to minimize orchestrator context):
|
|
|
124
152
|
```bash
|
|
125
153
|
INIT=$(node ".rcode/bin/rcode-tools.cjs" init sprint-plan "$PHASE" 2>/dev/null)
|
|
126
154
|
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
155
|
|
|
133
156
|
# Detect UI signals in phase goal + CONTEXT.md to decide whether to load ui-brand.md (254 lines)
|
|
134
157
|
PHASE_GOAL_HAS_UI=$(grep -iEl "frontend|ui|component|design|style|brand" \
|
|
@@ -141,9 +164,11 @@ If `INIT` is empty, or `INIT.ok` is false or absent (null/undefined — `init sp
|
|
|
141
164
|
Error: rcode-tools init failed. Verify .rcode/ is installed and state.json is valid.
|
|
142
165
|
```
|
|
143
166
|
|
|
167
|
+
**#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).
|
|
168
|
+
|
|
144
169
|
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
170
|
|
|
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`.
|
|
171
|
+
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
172
|
|
|
148
173
|
**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
174
|
|
|
@@ -153,7 +178,7 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
|
|
|
153
178
|
|
|
154
179
|
## 2. Parse and Normalize Arguments
|
|
155
180
|
|
|
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`).
|
|
181
|
+
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
182
|
|
|
158
183
|
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
184
|
|
|
@@ -571,6 +596,108 @@ Use AskUserQuestion with these 3 options.
|
|
|
571
596
|
**If "Proceed":** Return to planner with instruction to attempt all decisions at full fidelity, accepting more plans/tasks.
|
|
572
597
|
**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
598
|
|
|
599
|
+
## 9.5. Specialist Review Panel (domain-routed)
|
|
600
|
+
|
|
601
|
+
**Why this step exists.** Until now one generalist (`rcode-planner`) produced the
|
|
602
|
+
entire plan and one generalist (`rcode-sprint-checker`) graded it against the
|
|
603
|
+
phase goal. Nobody asked *"is this design wrong"* or *"what will this guard
|
|
604
|
+
miss"*. Confirmed live on a real project: nine phases planned and shipped this
|
|
605
|
+
way, and the first hour of specialist review found an inert RLS backstop, an
|
|
606
|
+
authorization mutation with no relationship check, a core feature whose only
|
|
607
|
+
importer was its own test, and real personal data committed to the repo. The
|
|
608
|
+
sprint-checker missed all four because none of them is a goal-coverage question.
|
|
609
|
+
|
|
610
|
+
**Skip only if:** `specialist_review_enabled` from the INIT JSON is `false`
|
|
611
|
+
(set `workflow.specialist_review: false` in `.rcode/config.yaml`; absent key =
|
|
612
|
+
enabled), or `--no-panel` was passed. In
|
|
613
|
+
autonomous/yolo mode this step is **NOT skippable** — yolo removes the human
|
|
614
|
+
mid-loop check, so plan-time review is the only review left.
|
|
615
|
+
|
|
616
|
+
### 9.5a — Pick the panel
|
|
617
|
+
|
|
618
|
+
**You route this, from context — not from a keyword table.** Read the evidence
|
|
619
|
+
first, then decide:
|
|
620
|
+
|
|
621
|
+
1. The plans' `<files>` fields — every path this phase will actually touch.
|
|
622
|
+
2. Migrations, schema files, and config the plans create or alter.
|
|
623
|
+
3. CONTEXT.md decisions (D-XX) and the phase goal.
|
|
624
|
+
4. The installed roster and what each persona actually owns:
|
|
625
|
+
```bash
|
|
626
|
+
node ".rcode/bin/rcode-tools.cjs" list-agents
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Then pick the domain seats by asking, per candidate: *given these files and
|
|
630
|
+
these decisions, does this persona's lens see something the others cannot?* If
|
|
631
|
+
the answer is no, do not seat them.
|
|
632
|
+
|
|
633
|
+
Run the keyword scorer as **one input, never the verdict**:
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
node ".rcode/bin/rcode-tools.cjs" select-panel \
|
|
637
|
+
"${PHASE_GOAL}. ${CONTEXT_DECISIONS_SUMMARY}" --explain
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
It is a weighted keyword table, so it routes on the words the phase text happens
|
|
641
|
+
to use, not on what the phase touches — a phase full of `drizzle/*.sql` RLS
|
|
642
|
+
policies that never writes the word "security" scores near zero for the security
|
|
643
|
+
lens. That is the same enumerate-a-location shape this panel exists to hunt for,
|
|
644
|
+
so treat a high score as corroboration and a zero score as no information.
|
|
645
|
+
|
|
646
|
+
**When your reading and the scorer disagree, your reading wins — and you must
|
|
647
|
+
say so in the panel report**: which persona you seated or dropped against the
|
|
648
|
+
score, and the file or decision that made you do it. A routing override with no
|
|
649
|
+
stated reason is indistinguishable from a coin flip.
|
|
650
|
+
|
|
651
|
+
Panel composition:
|
|
652
|
+
|
|
653
|
+
- **Always include `rcode-waleed`** (architecture lens) — the "is this design
|
|
654
|
+
wrong" seat. Every phase gets it.
|
|
655
|
+
- **Always include `rcode-fatima`** (quality lens) — the "what will this guard
|
|
656
|
+
miss" seat. Every phase gets it.
|
|
657
|
+
- **Plus 1-2 domain personas** chosen by the reading above. Seat a persona only
|
|
658
|
+
when you can name the file, migration, or decision that needs their lens. A
|
|
659
|
+
seat filled to reach a quota costs tokens and adds nothing.
|
|
660
|
+
|
|
661
|
+
Cap the panel at 4. If nothing in the plans needs a third lens, the two standing
|
|
662
|
+
seats ARE the panel — a correct outcome, not a failure to route.
|
|
663
|
+
|
|
664
|
+
### 9.5b — Run the panel in parallel
|
|
665
|
+
|
|
666
|
+
Spawn all panel members in a single message so they run concurrently. Each gets
|
|
667
|
+
the same narrow contract:
|
|
668
|
+
|
|
669
|
+
```
|
|
670
|
+
You are reviewing SPRINT plans BEFORE execution, through your lens only.
|
|
671
|
+
|
|
672
|
+
Read: {PHASE_DIR}/*-SPRINT.md, {PHASE_DIR}/CONTEXT.md, ROADMAP.md phase {N}.
|
|
673
|
+
|
|
674
|
+
Return ONLY blocking issues — things that would make the executed phase wrong,
|
|
675
|
+
unsafe, or unverifiable. Not style, not preferences, not "consider also".
|
|
676
|
+
For each issue: what is wrong, the file:line or task id it lives in, and the
|
|
677
|
+
specific change that fixes it.
|
|
678
|
+
|
|
679
|
+
Two questions you MUST answer explicitly, even if the answer is "none":
|
|
680
|
+
1. Which task in this plan enumerates a LOCATION where it should derive from a
|
|
681
|
+
PROPERTY? (a glob, a single filename, one role, one directory) — that shape
|
|
682
|
+
is how a guard ends up green while pointing where the problem is not.
|
|
683
|
+
2. Which delivered module would have no production importer after this phase
|
|
684
|
+
executes — reachable only from its own test?
|
|
685
|
+
|
|
686
|
+
If you have no blocking issues, return exactly: NO BLOCKING ISSUES.
|
|
687
|
+
Do not restate the plan. Do not summarize. {response_language pass-through}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### 9.5c — Handle panel return
|
|
691
|
+
|
|
692
|
+
- All members return `NO BLOCKING ISSUES` → proceed to step 10.
|
|
693
|
+
- Any blocking issue → feed it into the **existing revision loop (step 12)**
|
|
694
|
+
alongside the checker's issues. Do not build a second revision mechanism.
|
|
695
|
+
- Panel issues and checker issues are deduped by task id before re-spawning the
|
|
696
|
+
planner.
|
|
697
|
+
|
|
698
|
+
Report to the user which agents sat on the panel and what each blocked on — a
|
|
699
|
+
silent panel is indistinguishable from a skipped one.
|
|
700
|
+
|
|
574
701
|
## 10. Spawn rcode-sprint-checker Agent
|
|
575
702
|
|
|
576
703
|
Display banner:
|
|
@@ -597,7 +724,7 @@ Checker prompt:
|
|
|
597
724
|
- {research_path} (Technical Research — includes Validation Architecture)
|
|
598
725
|
</files_to_read>
|
|
599
726
|
|
|
600
|
-
|
|
727
|
+
{agent_skills.checker}
|
|
601
728
|
|
|
602
729
|
**Phase requirement IDs (MUST ALL be covered):** {phase_req_ids}
|
|
603
730
|
|
|
@@ -721,7 +848,7 @@ Revision prompt:
|
|
|
721
848
|
- {context_path} (USER DECISIONS from /rcode-discuss-phase)
|
|
722
849
|
</files_to_read>
|
|
723
850
|
|
|
724
|
-
|
|
851
|
+
{agent_skills.planner}
|
|
725
852
|
|
|
726
853
|
**Checker issues:** {structured_issues_from_checker}
|
|
727
854
|
</revision_context>
|
|
@@ -1029,6 +1156,7 @@ ${WINDOWS === 'true' ? '@.rcode/references/plan-windows-troubleshooting.md' : ''
|
|
|
1029
1156
|
- [ ] Phase directory created if needed
|
|
1030
1157
|
- [ ] CONTEXT.md loaded early (step 4) and passed to ALL agents
|
|
1031
1158
|
- [ ] Research completed (unless --skip-research or --gaps or exists)
|
|
1159
|
+
- [ ] Specialist review panel spawned (Waleed + Fatima + domain agents) and its blocking issues fed into the revision loop, or `workflow.specialist_review: false` recorded
|
|
1032
1160
|
- [ ] rcode-phase-researcher spawned with CONTEXT.md
|
|
1033
1161
|
- [ ] Existing plans checked
|
|
1034
1162
|
- [ ] 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}
|
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
|
|