pi-soly 1.11.2 → 1.12.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.
Files changed (54) hide show
  1. package/README.md +60 -14
  2. package/artifact/index.ts +241 -0
  3. package/artifact/render.ts +239 -0
  4. package/artifact/server.ts +384 -0
  5. package/artifact/session.ts +368 -0
  6. package/ask/README.md +20 -8
  7. package/ask/index.ts +100 -36
  8. package/ask/picker.ts +345 -29
  9. package/commands.ts +246 -30
  10. package/config.ts +124 -8
  11. package/core.ts +48 -236
  12. package/deck/deck.ts +386 -0
  13. package/deck/index.ts +113 -0
  14. package/index.ts +153 -37
  15. package/iteration.ts +1 -9
  16. package/mcp/commands.ts +12 -0
  17. package/mcp/direct-tools.ts +19 -2
  18. package/mcp/index.ts +144 -2
  19. package/mcp/init.ts +11 -0
  20. package/mcp/lifecycle.ts +9 -2
  21. package/mcp/server-manager.ts +30 -18
  22. package/mcp/state.ts +7 -0
  23. package/mcp/tool-cache.ts +135 -0
  24. package/nudge.ts +20 -3
  25. package/package.json +10 -3
  26. package/skills/soly-framework/SKILL.md +64 -37
  27. package/tool-hints.ts +62 -0
  28. package/tools.ts +28 -100
  29. package/util.ts +250 -0
  30. package/visual/chrome.ts +166 -0
  31. package/visual/colors.ts +24 -0
  32. package/visual/data.ts +62 -0
  33. package/visual/footer.ts +129 -0
  34. package/visual/format.ts +73 -0
  35. package/visual/glyphs.ts +35 -0
  36. package/visual/gradient.ts +122 -0
  37. package/visual/index.ts +18 -0
  38. package/visual/list-panel.ts +207 -0
  39. package/visual/segments.ts +136 -0
  40. package/visual/style.ts +34 -0
  41. package/visual/topbar.ts +55 -0
  42. package/visual/welcome.ts +265 -0
  43. package/visual/working.ts +66 -0
  44. package/workflows/execute.ts +17 -7
  45. package/workflows/index.ts +91 -44
  46. package/workflows/inspect.ts +23 -26
  47. package/workflows/migrate.ts +85 -0
  48. package/workflows/parser.ts +4 -2
  49. package/workflows/planning.ts +9 -8
  50. package/workflows/verify.ts +194 -0
  51. package/workflows-data/discuss-phase.md +10 -6
  52. package/agents-install.ts +0 -106
  53. package/ask/prompt.ts +0 -41
  54. package/ask/tests/prompt.test.ts +0 -54
@@ -1,3 +1,9 @@
1
+ ---
2
+ name: soly-framework
3
+ description: Use when the user invokes soly workflow commands (`/plan`, `/execute`, `/discuss`, `/inspect`, `/pause`, `/resume`, `/quick`, `/soly-init`, `/soly-migrate`, `/soly-status`, `/soly-log`) or asks how to use soly in pi-coding-agent — phases, plans, tasks, intent docs, ROADMAP/STATE state machine, rules, close-out order, and the available soly_* tools. Loaded as the complete reference for managing a soly project end-to-end (init → plan → execute → summary → state update).
4
+ priority: high
5
+ ---
6
+
1
7
  # soly framework
2
8
 
3
9
  The **soly** extension adds project-management workflow to [pi-coding-agent](https://github.com/nicobailon/pi-coding-agent): intent docs, ROADMAP/STATE/PHASE state machine, and LLM-driven plan execution. This skill is your complete reference for using it.
@@ -10,40 +16,49 @@ The **soly** extension adds project-management workflow to [pi-coding-agent](htt
10
16
  2. **Project state** (`.soly/STATE.md`, `ROADMAP.md`) — where we are, current phase, recent decisions.
11
17
  3. **Project rules** (`.soly/rules/`, `~/.soly/rules/`) — how to behave in this project.
12
18
 
13
- **Workflow model — phases and plans:**
14
-
15
- - A **phase** is a milestone (e.g. "01-foundation"). Has one or more `PLAN.md` files.
16
- - A **plan** is one ordered execution unit. Has `<task>` blocks.
17
- - A **task** is the smallest unit. Has type, description, verify, accept.
18
- - **Close-out**: production code commits → `SUMMARY.md` → `STATE.md` updated → ROADMAP check.
19
+ **Workflow model — phases and tasks (unified):**
19
20
 
20
- ## Quick reference slash commands
21
+ - A **phase** is a milestone (e.g. "01-foundation"). It groups **tasks** under `phases/<NN>-<slug>/tasks/`.
22
+ - A **task** is the unit of execution: its own dir with `PLAN.md` (frontmatter: `id`, `kind`, `status`, `depends-on`, optional `feature`) and, when done, `SUMMARY.md`. Tasks run in dependency order.
23
+ - `soly plan <N>` writes a phase's tasks · `soly execute <N>` runs its ready tasks (deps met) in order · `soly verify` reviews.
24
+ - **Close-out** (per task): production commits → task `SUMMARY.md` → flip `status: done` → `STATE.md` / ROADMAP check.
25
+ - Legacy projects (standalone `NN-MM-PLAN.md` files, or a `features/` dir) still load and run; `soly migrate` converts them to this layout.
21
26
 
22
- | Command | What it does |
23
- |---|---|
24
- | `/plan [N]` | Generate or update `PLAN.md` for phase N (or current phase) |
25
- | `/execute [N[.MM]]` | Execute plan(s) in phase N. `N` = all plans. `N.MM` = specific plan. The LLM (you) executes directly. |
26
- | `/discuss N` | Discussion-driven scoping for phase N — capture decisions before planning |
27
- | `/inspect` | One-screen summary: position, phases, recent decisions |
28
- | `/pause` | Save handoff (`HANDOFF.json` + `.continue-here.md`) for later resume |
29
- | `/resume` | Restore from a paused handoff |
30
- | `/quick <task>` | One-shot task that doesn't need a full plan — direct dispatch |
31
- | `/soly` | Project state inspection (alias for `/inspect`) |
32
- | `/why` | Show what context the LLM's last turn was based on |
33
- | `/soly-init` | Scaffold a new soly project (interactive template picker) |
34
- | `/soly-migrate` | Move legacy `.soly/` to `.agents/` (atomic) |
35
- | `/soly-status` | Comprehensive one-screen report |
36
- | `/soly-log [N]` | Show last N notifications from the log |
27
+ ## Commands
37
28
 
38
- `/soly <verb>` plain-text aliases also work for some verbs (legacy compat).
29
+ Workflow verbs are **plain text** type `soly <verb>` (NOT a slash command):
39
30
 
40
- ## No rotors (removed in 1.4.0)
41
-
42
- As of 1.4.0, soly no longer ships rotors. No `/rotor` command, no `Ctrl+Tab` cycle, no footer pill. The LLM picks the right subagent based on the task brief — use `subagent(...)` with `agent: "worker"` for implementation, `"oracle"` for decisions, `"scout"` for recon, `"reviewer"` for adversarial review.
31
+ | Verb | What it does |
32
+ |---|---|
33
+ | `soly discuss <N>` | Discussion-driven scoping for phase N interactive, uses the `ask_pro` picker |
34
+ | `soly plan <N>` | Generate `PLAN.md` for phase N (also `<task-id>`, `--new-task`, `--feature`) |
35
+ | `soly execute <N[.MM]>` | Execute a phase / one plan / a task / `--all` / `--feature` |
36
+ | `soly verify [N] [fresh]` | Self-review loop until "No issues found." (max N; `verify stop` to exit; `fresh` = fresh-context) |
37
+ | `soly pause` · `soly compact` | Save a handoff (compact also compresses the session) |
38
+ | `soly resume [N]` | Restore from a handoff |
39
+ | `soly status` · `log` · `diff` · `doctor` · `iterations` · `todos` | Quick read-outs (no LLM round-trip) |
43
40
 
44
- **Why drop them?** Rotors were a UX shortcut (Ctrl+Tab) that pi itself doesn't support well. pi has its own subagent system (`worker`, `oracle`, `scout`, `reviewer`); wrapping it in a "cycle" was over-engineering. The LLM in the main session is the executor; pi's subagents are helpers, not cycle modes.
41
+ **Slash commands** (pi's command surface):
45
42
 
46
- **For soly work**, the LLM does the work itself — it reads PLAN.md, runs commands, commits, writes SUMMARY.md. It does NOT spawn a soly subagent. Use `subagent(...)` only for read-only research (e.g. `agent: "scout"` for "find all files using X").
43
+ | Command | What it does |
44
+ |---|---|
45
+ | `/rules` · `/docs` | Open the rules / intent-docs **modal** (fuzzy list + preview; `e/d/r` enable·disable·reload on rules). A subcommand (`/rules stats`, `/docs stats`, …) prints to chat instead of opening the modal. |
46
+ | `/soly [<sub>]` | Project-state inspection (position, state, plan, roadmap, phases, tasks, …); bare `/soly` opens the picker |
47
+ | `/soly-init` · `/soly-migrate` | Scaffold a project · migrate `.soly/` → `.agents/` |
48
+ | `/why` | What rules + state grounded the last turn |
49
+ | `/rulewizard` | Rule vs .editorconfig vs linter guide |
50
+
51
+ ## Delegation
52
+
53
+ `soly plan` and `soly execute` delegate the heavy work to a `worker` subagent
54
+ (via the `subagent(...)` tool from pi-subagents); the parent session keeps the
55
+ close-out (production commits → `SUMMARY.md` → `STATE.md` → then `soly verify`).
56
+ If the `subagent` tool is NOT installed they run **inline** in the main session
57
+ instead — `soly doctor` reports which mode is active. First-party delegation is
58
+ on the roadmap. Other agents are read-only helpers: `oracle` (second opinion),
59
+ `scout` (recon), `reviewer` (adversarial review). `soly discuss` is always
60
+ interactive in the main session (not delegated). Rotors / `Ctrl+Tab` cycling
61
+ were removed in 1.4.0.
47
62
 
48
63
  ## File structure
49
64
 
@@ -64,10 +79,12 @@ As of 1.4.0, soly no longer ships rotors. No `/rotor` command, no `Ctrl+Tab` cyc
64
79
  │ │ ├── 01-foundation/
65
80
  │ │ │ ├── 01-CONTEXT.md # domain + decisions for phase 1
66
81
  │ │ │ ├── 01-RESEARCH.md # what we looked up
67
- │ │ │ ├── 01-PLAN-01.md # plan 1
68
- │ │ │ ├── 01-PLAN-01-SUMMARY.md
69
- │ │ │ ├── 01-PLAN-02.md
70
- │ │ │ └── 01-PLAN-02-SUMMARY.md
82
+ │ │ │ └── tasks/ # unified model: one dir per task
83
+ │ │ │ ├── auth-login-a3f9/
84
+ │ │ │ ├── PLAN.md # frontmatter: id, kind, status, depends-on
85
+ │ │ │ └── SUMMARY.md
86
+ │ │ │ └── auth-token-b1c2/
87
+ │ │ │ └── PLAN.md
71
88
  │ │ └── 02-feature-x/
72
89
  │ │ └── ...
73
90
  │ ├── iterations/ # per-execution context bundles (auto)
@@ -211,10 +228,22 @@ Once production commits exist, returning without a committed `SUMMARY.md` is an
211
228
  | `soly_env()` | Detect runtime (package manager, runtimes, services, scripts) |
212
229
  | `soly_snippet(path, offset, limit)` | Read bounded line range with line numbers |
213
230
  | `soly_doc_search(query, limit)` | Search .md/.html under cwd (prioritizes intent docs) |
214
- | `soly_intent()` | List 0-point intent docs from `.soly/docs/` |
215
231
  | `soly_scratchpad(limit)` | Recent conversation recap (one line per turn) |
216
- | `ask_pro(questions)` | Multi-question picker (tabbed, single/multi-select, ⭐, Other…) |
217
- | `todo_update(todos)` | Update task list rendered in footer |
232
+ | `ask_pro(questions)` | Multi-question picker (tabbed, single/multi-select, ⭐, `preview` w/ code highlight, `allowOther`, `freeText`, `minSelect`/`maxSelect`, `s` to skip, notes) — preferred for structured input |
233
+ | `decision_deck(options)` | Full-screen TUI deck one card per option with a highlighted code snippet + pros/cons. For design/architecture forks where the choice hinges on the concrete code shape |
234
+ | `html_artifact(title, html, id?, assets?)` | Render HTML (full doc or body fragment) and serve it from a per-session browser gallery SPA (sidebar + iframe + filter + live updates, one stable URL) — soly's "artifacts". `id` updates in place; `assets` writes sibling files. For visual output: example galleries, comparisons, diagrams |
235
+ | `soly_save_discuss_checkpoint(...)` · `soly_finish_discuss(...)` | Save / finalize a `soly discuss` session (writes CONTEXT.md) |
236
+ | `soly_ask_user(...)` | Single-question picker — **deprecated**, prefer `ask_pro` |
237
+
238
+ ### Visual & interaction tools — when to reach for each
239
+
240
+ The main system prompt only points at these; the detail is here.
241
+
242
+ **`ask_pro`** — multi-question picker. Use for 1–6 related questions where the user picks concrete answers to move forward (e.g. `soly discuss` scoping). NOT for simple yes/no, a single open-ended prompt, questions already answered, or trivial clarifications — use plain text. Per option, `preview` shows a side panel while focused (put a code/API/config snippet there; fenced ```code is highlighted). `freeText` = optional typed answer; `allowOther` = escape hatch; `minSelect`/`maxSelect` bound multi-select. The user can skip (`s`, returned `(skipped)`) or note (`n`, returned `// note:` — treat as a hard constraint).
243
+
244
+ **`decision_deck`** — full-screen cards (one per option) with a highlighted code snippet + pros/cons. Reach for it when the choice hinges on the concrete code/structure of each option, not a label, and there are 2–6 alternatives worth comparing side-by-side. Prefer `ask_pro` for short-label choices; don't use it for trivial/yes-no.
245
+
246
+ **`html_artifact`** — render HTML served from a per-session browser gallery SPA (sidebar + iframe + filter + live SSE updates, one stable localhost URL). Use when a visual rendered result beats terminal text: example galleries, before/after or side-by-side comparisons, tables, diagrams, a small HTML/CSS/SVG demo. Pass `title` + `html` (a body fragment is fine — it's wrapped in a themed skeleton; put code in `<pre><code>`). Pass `id` to re-render/update an artifact in place (instead of piling up copies); pass `assets: [{path, content, encoding?}]` to write sibling files the HTML references by relative path (e.g. an image or `data.json`). The theme is overridable per project via `.soly/artifact-theme.css`. Keep it self-contained unless you use `assets` — no external/CDN requests. Don't use it for a single snippet (markdown code block) or prose. Mention the returned URL to the user.
218
247
 
219
248
  ## Common workflows
220
249
 
@@ -247,8 +276,6 @@ You are a data scientist. ...
247
276
  3. `~/.agents/` — user vendor-neutral (preferred)
248
277
  4. `~/.pi/agent/agents/` — user pi native (legacy)
249
278
 
250
- `Ctrl+Tab` to see them in the cycle. (Removed in 1.4.0 — use `subagent(...)` directly.)
251
-
252
279
  ### Add a feature to an existing phase
253
280
 
254
281
  1. `/plan 1.05` (next plan number)
package/tool-hints.ts ADDED
@@ -0,0 +1,62 @@
1
+ // =============================================================================
2
+ // tool-hints.ts — per-turn affordance hints for soly's interactive tools
3
+ // =============================================================================
4
+ //
5
+ // Scans the user's prompt for trigger words (bilingual RU/EN) and, when matched,
6
+ // returns a small system-prompt section nudging the LLM to consider the right
7
+ // interactive tool: html_artifact (examples / visual output), decision_deck
8
+ // (comparing options), or ask_pro (clarifying questions).
9
+ //
10
+ // Soft + dynamic: the hint is only injected on turns that actually mention these
11
+ // things (zero token cost otherwise) and explicitly says "only if it helps", so
12
+ // it steers without forcing. Pure: prompt string in, hint string out.
13
+ // =============================================================================
14
+
15
+ export type ToolHint = { artifact: boolean; deck: boolean; ask: boolean };
16
+
17
+ // Trigger words. Stems chosen to catch RU inflections (вариант/варианты/…) and
18
+ // common EN forms. Kept narrow enough to avoid firing on every prompt.
19
+ const TRIGGERS = {
20
+ artifact: [
21
+ /пример/i, /образец/i, /галере/i, /шпаргалк/i, /таблиц/i, /диаграмм/i,
22
+ /визуализ/i, /макет/i, /дашборд/i,
23
+ /\bexamples?\b/i, /\bsamples?\b/i, /\bdemo\b/i, /\bgallery\b/i,
24
+ /cheat\s*sheet/i, /\btables?\b/i, /\bdiagram/i, /\bvisuali[sz]/i,
25
+ /\bmock\s?up/i, /\bdashboard\b/i,
26
+ ],
27
+ deck: [
28
+ /вариант/i, /сравн/i, /альтернатив/i, /\bлибо\b/i, /какой подход/i,
29
+ /что лучше/i, /выбрать (?:между|из)/i,
30
+ /\boptions?\b/i, /\bcompare\b/i, /\bcomparison\b/i, /\bvs\.?\b/i,
31
+ /\btrade-?offs?\b/i, /which approach/i, /\bpros and cons\b/i,
32
+ ],
33
+ ask: [
34
+ /уточн/i, /несколько вопрос/i, /опросник/i,
35
+ /\bclarif/i, /\ba few questions\b/i,
36
+ ],
37
+ };
38
+
39
+ /** Which interactive tools the prompt's wording hints at. */
40
+ export function detectToolHints(prompt: string): ToolHint {
41
+ const has = (res: RegExp[]): boolean => res.some((r) => r.test(prompt));
42
+ return {
43
+ artifact: has(TRIGGERS.artifact),
44
+ deck: has(TRIGGERS.deck),
45
+ ask: has(TRIGGERS.ask),
46
+ };
47
+ }
48
+
49
+ /** Build the affordance section, or null when nothing matched. */
50
+ export function buildToolHintSection(h: ToolHint): string | null {
51
+ const bits: string[] = [];
52
+ if (h.artifact)
53
+ bits.push("- Examples / a table / a visual result: **ask the user first** — render it in the browser as an artifact (`html_artifact`), or just as text here? Then proceed accordingly.");
54
+ if (h.deck)
55
+ bits.push("- Comparing options/alternatives: **ask the user first** — show them as a full-screen deck (`decision_deck`, code + pros/cons), or inline as text? Then proceed.");
56
+ if (h.ask)
57
+ bits.push("- Several things to clarify → use `ask_pro` (one batched picker).");
58
+ if (bits.length === 0) return null;
59
+ return `\n## soly — interactive output for this turn\n\n${bits.join(
60
+ "\n",
61
+ )}\nAsk once and briefly (a one-line question is fine). If the content is tiny or the format is obvious, just answer in text.`;
62
+ }
package/tools.ts CHANGED
@@ -22,7 +22,6 @@ import { detectEnv, type EnvSummary } from "./env.ts";
22
22
  import type { SolyConfig } from "./config.ts";
23
23
  import { buildDocIndex, searchDocs, readSnippet, stripHtml } from "./docs.ts";
24
24
  import { buildScratchpad, SCRATCHPAD_LIMITS } from "./scratchpad.ts";
25
- import { loadIntentDocs, buildIntentSection, type IntentDoc } from "./intent.ts";
26
25
 
27
26
  const execFileAsync = promisify(execFile);
28
27
 
@@ -67,7 +66,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
67
66
  name: "soly_read",
68
67
  label: "soly read",
69
68
  description:
70
- "read a .soly/ artifact. Use when you need to inspect what the project knows — current state, the active plan, phase context/research, the roadmap, requirements, project overview, or milestone. Pass `phase` (number) to target a specific phase's plan/context/research; defaults to the current phase. Returns the file content as text.",
69
+ "Read a .soly/ artifact (state, plan, context, research, roadmap, requirements, project, milestone, task). `phase` targets a specific phase (default: current); `taskId` for the task artifact. Returns the file text.",
71
70
  parameters: Type.Object({
72
71
  artifact: StringEnum([
73
72
  "state",
@@ -190,7 +189,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
190
189
  name: "soly_log_decision",
191
190
  label: "soly log decision",
192
191
  description:
193
- "append a row to the Decisions table in .soly/STATE.md (creates the table if missing). Use this for meaningful choices the future-you should know about — scope cuts, library picks, trade-off resolutions. Pass `phase` (number) to attach to a specific phase; defaults to the current phase. Decision and rationale should each be one line.",
192
+ "Append a one-line decision + rationale to the Decisions table in .soly/STATE.md (creates it if missing). For meaningful choices: scope cuts, library picks, trade-offs. `phase` defaults to current.",
194
193
  parameters: Type.Object({
195
194
  decision: Type.String({ description: "The decision made (one line)." }),
196
195
  rationale: Type.String({ description: "Why this decision was made (one line)." }),
@@ -275,7 +274,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
275
274
  name: "soly_list_tasks",
276
275
  label: "soly list tasks",
277
276
  description:
278
- "list all tasks across all features with kind, status, priority, and dependencies. Use when you need an overview of available tasks — e.g. before `soly execute <task-id>` or `soly execute --all`.",
277
+ "List all tasks across features (kind, status, priority, deps). Use before `soly execute <task-id>` / `--all`.",
279
278
  parameters: Type.Object({}),
280
279
  async execute(_id, _params, _signal, _onUpdate, _ctx) {
281
280
  const state = getState();
@@ -301,7 +300,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
301
300
  name: "soly_list_phases",
302
301
  label: "soly list phases",
303
302
  description:
304
- "list all phases in .soly/phases/ with plan count, CONTEXT/RESEARCH presence markers (C/R), and the current position marker (→). Use when you need an overview of which phases exist, which have plans, and which have research/context notes — e.g. before suggesting `soly plan <N>` or `soly execute <N>`.",
303
+ "List phases with plan count, C/R (context/research) markers, and current-position marker (→). Use before `soly plan <N>` / `execute <N>`.",
305
304
  parameters: Type.Object({}),
306
305
  async execute(_id, _params, _signal, _onUpdate, _ctx) {
307
306
  const state = getState();
@@ -330,7 +329,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
330
329
  name: "soly_todos",
331
330
  label: "soly todos",
332
331
  description:
333
- "scan the working tree for TODO / FIXME / HACK / XXX / NOTE comments, grouped by file. Use for a quick audit of outstanding cleanup work. Scans .ts/.tsx/.js/.jsx/.py/.go/.rs only, excluding node_modules, .git, dist, build, .soly, coverage. Requires `rg` (ripgrep) on PATH — silently returns empty if missing. Pass `paths` to override the scan root, `limit` to cap (default 200).",
332
+ "Scan the tree for TODO/FIXME/HACK/XXX/NOTE comments, grouped by file (common source extensions; excludes node_modules/.git/dist/build/.soly). Needs ripgrep on PATH. `paths` overrides root, `limit` caps (default 200).",
334
333
  parameters: Type.Object({
335
334
  paths: Type.Optional(
336
335
  Type.Array(Type.String(), {
@@ -462,7 +461,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
462
461
  name: "soly_env",
463
462
  label: "soly env",
464
463
  description:
465
- "detect the project's runtime environment and return it as a one-screen summary. Use to answer 'what test runner?', 'is docker used here?', 'what's the package manager?' etc. Detects: package manager, runtimes (node/bun), key dependencies, available scripts (dev/build/test/lint/...), services (postgres/redis/etc from compose), tooling flags (ts/tests/docker/ci).",
464
+ "Detect the project's environment as a one-screen summary: package manager, runtimes, key deps, scripts, services (from compose), and tooling flags (ts/tests/docker/ci). Answers 'what test runner / package manager / is docker used'.",
466
465
  parameters: Type.Object({}),
467
466
  async execute(_id, _params, _signal, _onUpdate, ctx) {
468
467
  const env = detectEnv(ctx.cwd);
@@ -500,19 +499,14 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
500
499
  name: "soly_snippet",
501
500
  label: "soly snippet",
502
501
  description:
503
- "read a bounded range of lines from a file with line numbers (lazy context). Use when you need a specific function/class/section without loading the whole file. Path is relative to cwd unless absolute. `offset` is 0-indexed start, `limit` defaults to 100 (cap 500). For .html files, pass `format=\"stripped\"` to strip HTML tags (text-only output, useful for intent docs).",
502
+ "Read a bounded line range from a file with line numbers a specific function/section without the whole file. `offset` 0-indexed, `limit` default 100 (cap 500). For .html, `format=\"stripped\"` removes tags.",
504
503
  parameters: Type.Object({
505
504
  path: Type.String({ description: "File path (relative to cwd or absolute)." }),
506
- offset: Type.Optional(
507
- Type.Number({ description: "0-indexed start line. Default 0." }),
508
- ),
509
- limit: Type.Optional(
510
- Type.Number({ description: "Max lines to return. Default 100, cap 500." }),
511
- ),
505
+ offset: Type.Optional(Type.Number({ description: "0-indexed start line. Default 0." })),
506
+ limit: Type.Optional(Type.Number({ description: "Max lines. Default 100, cap 500." })),
512
507
  format: Type.Optional(
513
508
  StringEnum(["raw", "stripped"] as const, {
514
- description:
515
- "Output format. \"raw\" returns content as-is. \"stripped\" removes HTML tags (for .html files). Default \"raw\".",
509
+ description: '"raw" (default) or "stripped" (remove HTML tags).',
516
510
  }),
517
511
  ),
518
512
  }),
@@ -575,7 +569,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
575
569
  name: "soly_doc_search",
576
570
  label: "soly doc search",
577
571
  description:
578
- "search .md and .html files under cwd (excluding node_modules, dist, etc.) for a query. Use to discover relevant docs before loading a specific file with soly_snippet. Intent docs (from `.soly/docs/` and `.soly/phases/<N>/docs/`) are prioritized via a source-priority bonus — hits are tagged `[intent]`, `[phase-intent]`, or `[project]` so you can tell at a glance. `limit` defaults to 10 (cap 50).",
572
+ "Search .md/.html under cwd for a query (intent docs prioritized, hits tagged [intent]/[phase-intent]/[project]). Use to find docs before loading one with soly_snippet. `limit` default 10 (cap 50).",
579
573
  parameters: Type.Object({
580
574
  query: Type.String({ description: "Search query (substring, case-insensitive)." }),
581
575
  limit: Type.Optional(
@@ -633,7 +627,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
633
627
  name: "soly_scratchpad",
634
628
  label: "soly scratchpad",
635
629
  description:
636
- "return a compact summary of the recent conversation (one line per turn: user prompt + first line of assistant response). Use to recover shared context after a long break, or to brief a sibling subagent without sharing the full session history. `limit` defaults to 20 user-turns (cap 50).",
630
+ "Compact recap of the recent conversation (one line per turn). Use to recover context after a break or brief a sibling subagent. `limit` default 20 user-turns (cap 50).",
637
631
  parameters: Type.Object({
638
632
  limit: Type.Optional(
639
633
  Type.Number({
@@ -682,51 +676,6 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
682
676
  },
683
677
  });
684
678
 
685
- // ============================================================================
686
- // soly_intent — refresh / list the project's 0-point intent docs
687
- // ============================================================================
688
-
689
- pi.registerTool({
690
- name: "soly_intent",
691
- label: "soly intent",
692
- description:
693
- "list the project's 0-point intent docs from `.soly/docs/` — the business/domain/tech-intent documents the user wrote BEFORE any soly plans. Use before planning, discussing, or executing a phase to ensure alignment with the user's vision. Supports `.md` and `.html` (parsed for `<title>`/`<h1>`/`<meta description>`). Phase-specific docs under `.soly/phases/<N>/docs/` are also included if present. Pass no arguments — this tool always returns the full list.",
694
- parameters: Type.Object({}),
695
- async execute(_id, _params, _signal, _onUpdate, ctx) {
696
- const docs = loadIntentDocs(ctx.cwd);
697
- const { section } = buildIntentSection(docs);
698
-
699
- if (docs.length === 0) {
700
- return {
701
- content: [
702
- {
703
- type: "text",
704
- text:
705
- `soly_intent: no docs found in ${path.join(ctx.cwd, ".soly", "docs")}.\n\n` +
706
- `Project intent is the user's vision written BEFORE soly plans. If empty, ask the user to drop their intent docs (business glossary, design vision, domain concepts) into .soly/docs/ as .md or .html files, then run soly_intent again.`,
707
- },
708
- ],
709
- details: { count: 0 },
710
- };
711
- }
712
-
713
- // Render the same section that goes into the system prompt, so the
714
- // model sees a consistent view whether it asks or not.
715
- const out: string[] = [];
716
- out.push(`soly_intent: ${docs.length} document(s) in .soly/docs/:`);
717
- out.push("");
718
- out.push(section);
719
- out.push("");
720
- out.push(
721
- "To read a specific doc, use soly_snippet(path=\".soly/docs/<name>\", offset=N, limit=M). For HTML files, pass format=\"stripped\" to get plain text.",
722
- );
723
- return {
724
- content: [{ type: "text", text: out.join("\n") }],
725
- details: { count: docs.length, paths: docs.map((d) => d.relPath) },
726
- };
727
- },
728
- });
729
-
730
679
  // ============================================================================
731
680
  // soly_ask_user — multiple-choice picker (for `soly discuss` interactive flow)
732
681
  // ============================================================================
@@ -735,29 +684,18 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
735
684
  name: "soly_ask_user",
736
685
  label: "soly ask user",
737
686
  description:
738
- "Ask the user a multiple-choice question via pi's UI picker. Use this for progressive Q&A flows (e.g. `soly discuss <N>`). The first option is treated as the recommended answer — mark it with a ⭐ prefix in its label and add a 1-sentence rationale in the `rationale` parameter. Returns the chosen option text (or the custom string if `allowOther: true` and the user picked 'Other…'). The picker has ↑/↓/j/k navigation, Enter to confirm, Esc to cancel (cancelled returns 'cancelled' in details).",
687
+ "DEPRECATED prefer `ask_pro` (multi-question picker). Fallback only. Asks one multiple-choice question via pi's picker; option #1 is the recommended answer (⭐ prefix + a `rationale`). `allowOther` adds a custom-text 'Other…'. Returns the chosen text (or custom string); Esc → cancelled.",
739
688
  parameters: Type.Object({
740
- title: Type.String({
741
- description: "Short title shown above the picker (e.g. 'Q1: Session handling').",
742
- }),
743
- question: Type.String({
744
- description: "The question to ask the user (one short sentence).",
745
- }),
689
+ title: Type.String({ description: "Title above the picker." }),
690
+ question: Type.String({ description: "The question (one sentence)." }),
746
691
  options: Type.Array(Type.String(), {
747
- description:
748
- "2-4 concrete options. Option #1 is the recommended answer (mark with ⭐ prefix in the label).",
692
+ description: "2-4 options; #1 is recommended (⭐ prefix).",
749
693
  }),
750
694
  rationale: Type.Optional(
751
- Type.String({
752
- description:
753
- "1-2 sentence note explaining why option #1 is recommended. Shown above the picker.",
754
- }),
695
+ Type.String({ description: "Why #1 is recommended (shown above the picker)." }),
755
696
  ),
756
697
  allowOther: Type.Optional(
757
- Type.Boolean({
758
- description:
759
- "If true, appends an 'Other…' option that opens a text-input dialog for a custom answer. The custom string is returned in place of the option index.",
760
- }),
698
+ Type.Boolean({ description: "Add a custom-text 'Other…' choice." }),
761
699
  ),
762
700
  }),
763
701
  async execute(_id, params, _signal, _onUpdate, ctx) {
@@ -880,45 +818,35 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
880
818
  name: "soly_finish_discuss",
881
819
  label: "soly finish discuss",
882
820
  description:
883
- "Finalize a `soly discuss <N>` session: write the canonical `<phase>-CONTEXT.md` with all captured decisions, and delete the checkpoint file (if any). Call this AFTER asking all gray-area questions and recording the user's answers via `soly_ask_user`. Do NOT call this for partial progress use the checkpoint file (auto-saved by soly_finish_discuss_checkpoint) for resume.",
821
+ "Finalize a `soly discuss <N>` session: write `<phase>-CONTEXT.md` with all decisions and delete the checkpoint. Call AFTER all gray-area questions are answered not for partial progress (use soly_save_discuss_checkpoint for that).",
884
822
  parameters: Type.Object({
885
- phase_number: Type.Number({
886
- description: "Phase number being discussed (e.g. 5).",
887
- }),
823
+ phase_number: Type.Number({ description: "Phase number being discussed." }),
888
824
  domain: Type.String({
889
825
  description:
890
- "1-2 paragraphs: what this phase delivers, grounded in ROADMAP + intent. No implementation details.",
826
+ "1-2 paragraphs: what this phase delivers (grounded in ROADMAP + intent, no implementation details).",
891
827
  }),
892
828
  decisions: Type.Array(
893
829
  Type.Object({
894
- category: Type.String({
895
- description:
896
- "Category of the decision (e.g. 'Session handling', 'Error responses', 'Token storage').",
897
- }),
830
+ category: Type.String({ description: "Decision category (e.g. 'Session handling')." }),
898
831
  choice: Type.String({ description: "What was chosen." }),
899
832
  rationale: Type.Optional(
900
- Type.String({
901
- description: "Why this choice — default 'user discretion' if not specified.",
902
- }),
833
+ Type.String({ description: "Why (default 'user discretion')." }),
903
834
  ),
904
835
  }),
905
- { description: "All decisions captured during this discussion round." },
836
+ { description: "All decisions captured this round." },
906
837
  ),
907
838
  canonical_refs: Type.Optional(
908
839
  Type.Array(Type.String(), {
909
840
  description:
910
- "MANDATORY. List of files the planner will need (intent docs, REQUIREMENTS, contracts, prior CONTEXT.md). Full relative paths starting with `.soly/`.",
841
+ "MANDATORY. Files the planner needs (intent docs, REQUIREMENTS, contracts), full paths from `.soly/`.",
911
842
  }),
912
843
  ),
913
844
  deferred_ideas: Type.Optional(
914
- Type.Array(Type.String(), {
915
- description: "Scope-creep items for future phases (or empty array).",
916
- }),
845
+ Type.Array(Type.String(), { description: "Scope-creep items for future phases." }),
917
846
  ),
918
847
  codebase_context: Type.Optional(
919
848
  Type.Array(Type.String(), {
920
- description:
921
- "Reusable assets/patterns the planner should know (e.g. 'src/components/Card.tsx — already has rounded/shadow variants, reuse for consistency').",
849
+ description: "Reusable assets/patterns the planner should know (path — what to reuse).",
922
850
  }),
923
851
  ),
924
852
  }),
@@ -1074,7 +1002,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
1074
1002
  name: "soly_save_discuss_checkpoint",
1075
1003
  label: "soly save discuss checkpoint",
1076
1004
  description:
1077
- "Save a partial-progress checkpoint for `soly discuss <N>`. Call after each captured decision (so a session quit doesn't lose progress). The next `soly discuss <N>` invocation will pick up from the checkpoint. After all decisions captured, call `soly_finish_discuss` (which deletes the checkpoint and writes CONTEXT.md).",
1005
+ "Save a partial-progress checkpoint for `soly discuss <N>` (call after each decision so a quit doesn't lose progress; the next `soly discuss <N>` resumes from it). When done, call `soly_finish_discuss`.",
1078
1006
  parameters: Type.Object({
1079
1007
  phase_number: Type.Number({ description: "Phase number being discussed." }),
1080
1008
  decisions: Type.Array(