pi-soly 1.13.5 → 1.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/status.ts CHANGED
@@ -56,8 +56,8 @@ export function formatStatus(
56
56
  lines.push(" Project state");
57
57
  lines.push(" ─────────────");
58
58
  if (!state.exists) {
59
- lines.push(" no .agents/ or .soly/ found in cwd");
60
- lines.push(" → run /soly-init to scaffold");
59
+ lines.push(" no .agents/ found in cwd");
60
+ lines.push(" → run /soly init to scaffold");
61
61
  } else {
62
62
  lines.push(` milestone: ${state.milestone ?? "—"}`);
63
63
  lines.push(` current: ${state.currentPosition ?? "ready"}`);
package/tools.ts CHANGED
@@ -3,11 +3,11 @@
3
3
  // =============================================================================
4
4
  //
5
5
  // Registers three tools the LLM can call:
6
- // - soly_read — read any .soly/ artifact (state/plan/roadmap/...)
6
+ // - soly_read — read any .agents/ artifact (state/plan/roadmap/...)
7
7
  // - soly_log_decision — append a row to STATE.md Decisions table
8
8
  // - soly_list_phases — list all phases with markers
9
9
  //
10
- // All paths are relative to <cwd>/.soly/ (the soly layout — NOT .planning/).
10
+ // All paths are relative to <cwd>/.agents/ (the soly layout — NOT .planning/).
11
11
  // =============================================================================
12
12
 
13
13
  import * as fs from "node:fs";
@@ -17,7 +17,7 @@ import { promisify } from "node:util";
17
17
  import { Type } from "typebox";
18
18
  import { StringEnum } from "@earendil-works/pi-ai";
19
19
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
20
- import { readIfExists, splitFrontmatter, atomicWriteFileSync, type SolyState } from "./core.ts";
20
+ import { readIfExists, splitFrontmatter, atomicWriteFileSync, solyDirFor, type SolyState } from "./core.ts";
21
21
  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";
@@ -66,7 +66,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
66
66
  name: "soly_read",
67
67
  label: "soly read",
68
68
  description:
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.",
69
+ "Read a .agents/ 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.",
70
70
  parameters: Type.Object({
71
71
  artifact: StringEnum([
72
72
  "state",
@@ -129,7 +129,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
129
129
  if (!task) {
130
130
  return {
131
131
  content: [
132
- { type: "text", text: `soly: task ${taskId} not found in .soly/features/*/tasks/` },
132
+ { type: "text", text: `soly: task ${taskId} not found in .agents/features/*/tasks/` },
133
133
  ],
134
134
  details: { error: "task_not_found", taskId },
135
135
  };
@@ -189,7 +189,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
189
189
  name: "soly_log_decision",
190
190
  label: "soly log decision",
191
191
  description:
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.",
192
+ "Append a one-line decision + rationale to the Decisions table in .agents/STATE.md (creates it if missing). For meaningful choices: scope cuts, library picks, trade-offs. `phase` defaults to current.",
193
193
  parameters: Type.Object({
194
194
  decision: Type.String({ description: "The decision made (one line)." }),
195
195
  rationale: Type.String({ description: "Why this decision was made (one line)." }),
@@ -329,7 +329,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
329
329
  name: "soly_todos",
330
330
  label: "soly todos",
331
331
  description:
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).",
332
+ "Scan the tree for TODO/FIXME/HACK/XXX/NOTE comments, grouped by file (common source extensions; excludes node_modules/.git/dist/build/.agents). Needs ripgrep on PATH. `paths` overrides root, `limit` caps (default 200).",
333
333
  parameters: Type.Object({
334
334
  paths: Type.Optional(
335
335
  Type.Array(Type.String(), {
@@ -356,7 +356,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
356
356
  "--glob=!node_modules/**",
357
357
  "--glob=!dist/**",
358
358
  "--glob=!build/**",
359
- "--glob=!.soly/**",
359
+ "--glob=!.agents/**",
360
360
  "--glob=!coverage/**",
361
361
  "-tts",
362
362
  "-tjs",
@@ -838,7 +838,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
838
838
  canonical_refs: Type.Optional(
839
839
  Type.Array(Type.String(), {
840
840
  description:
841
- "MANDATORY. Files the planner needs (intent docs, REQUIREMENTS, contracts), full paths from `.soly/`.",
841
+ "MANDATORY. Files the planner needs (intent docs, REQUIREMENTS, contracts), full paths from `.agents/`.",
842
842
  }),
843
843
  ),
844
844
  deferred_ideas: Type.Optional(
@@ -852,10 +852,10 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
852
852
  }),
853
853
  async execute(_id, params, _signal, _onUpdate, ctx) {
854
854
  // Locate phase dir
855
- const solyDir = path.join(ctx.cwd, ".soly");
855
+ const solyDir = solyDirFor(ctx.cwd);
856
856
  if (!fs.existsSync(solyDir)) {
857
857
  return {
858
- content: [{ type: "text", text: "soly_finish_discuss: no .soly/ in cwd" }],
858
+ content: [{ type: "text", text: "soly_finish_discuss: no .agents/ in cwd" }],
859
859
  details: { error: "no_soly" },
860
860
  };
861
861
  }
@@ -877,7 +877,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
877
877
  content: [
878
878
  {
879
879
  type: "text",
880
- text: `soly_finish_discuss: phase ${phaseNum} not found in .soly/phases/`,
880
+ text: `soly_finish_discuss: phase ${phaseNum} not found in .agents/phases/`,
881
881
  },
882
882
  ],
883
883
  details: { error: "no_phase", phase: phaseNum },
@@ -1021,11 +1021,11 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
1021
1021
  ),
1022
1022
  }),
1023
1023
  async execute(_id, params, _signal, _onUpdate, ctx) {
1024
- const solyDir = path.join(ctx.cwd, ".soly");
1024
+ const solyDir = solyDirFor(ctx.cwd);
1025
1025
  const phasesRoot = path.join(solyDir, "phases");
1026
1026
  if (!fs.existsSync(phasesRoot)) {
1027
1027
  return {
1028
- content: [{ type: "text", text: "soly_save_discuss_checkpoint: no .soly/phases/ in cwd" }],
1028
+ content: [{ type: "text", text: "soly_save_discuss_checkpoint: no .agents/phases/ in cwd" }],
1029
1029
  details: { error: "no_phases" },
1030
1030
  };
1031
1031
  }
package/util.ts CHANGED
@@ -16,7 +16,7 @@ import type { ProgressInfo, RuleFrontmatter } from "./core.ts";
16
16
  // Frontmatter parsers
17
17
  // ============================================================================
18
18
 
19
- // Simple parser for .soly/rules/ frontmatter.
19
+ // Simple parser for .agents/rules/ frontmatter.
20
20
  export function parseRuleFrontmatter(raw: string): {
21
21
  meta: RuleFrontmatter;
22
22
  body: string;
@@ -68,7 +68,7 @@ export function parseRuleFrontmatter(raw: string): {
68
68
  return { meta, body };
69
69
  }
70
70
 
71
- // YAML-ish parser for .soly/STATE.md. Handles 2-level nested objects (for `progress:`).
71
+ // YAML-ish parser for .agents/STATE.md. Handles 2-level nested objects (for `progress:`).
72
72
  export function parseStateFrontmatter(yaml: string): {
73
73
  meta: Record<string, unknown>;
74
74
  progress: ProgressInfo;
package/visual/welcome.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  // soly adds plans · state · rules · workflows · ask_pro picker
11
11
  // project v1.12 · plan 2/5 — auth refactor · ≡ 4 rules · 2 docs
12
12
  // next → /execute
13
- // start here /soly-init · /plan · /soly · /why · /rules stats
13
+ // start here /soly init · /plan · /soly · /why · /rules stats
14
14
  // recent 1.11.2 … · 1.10.0 …
15
15
  //
16
16
  // pi is explicitly credited (engine) with soly as the framework on top. The
@@ -54,7 +54,7 @@ export const SOLY_ART: readonly string[] = [
54
54
  const TAGLINE = "the project framework · running on pi, the coding engine";
55
55
  const SOLY_ADDS = "plans · state · rules · workflows · ask_pro picker";
56
56
  const START_HERE: ReadonlyArray<[string, string]> = [
57
- ["/soly-init", "scaffold .agents/ in a new project"],
57
+ ["/soly init", "scaffold .agents/ in a new project"],
58
58
  ["/plan", "plan the current phase"],
59
59
  ["/soly", "state picker · /why · /rules stats"],
60
60
  ];
@@ -144,7 +144,7 @@ function bannerLines(opts: WelcomeOpts): string[] {
144
144
 
145
145
  /** The "project" row value, depending on whether a soly project exists. */
146
146
  function projectValue(input: WelcomeInput, styler: ChromeStyler): string {
147
- if (!input.hasProject) return styler.dim("no soly project here → /soly-init to scaffold");
147
+ if (!input.hasProject) return styler.dim("no soly project here → /soly init to scaffold");
148
148
  const bits = [styler.fg("accent", `v${input.version}`)];
149
149
  if (input.phaseLabel) bits.push(input.phaseLabel);
150
150
  if (input.rulesActive > 0) bits.push(`≡ ${input.rulesActive}`);
@@ -73,7 +73,7 @@ export function buildExecuteTransform(
73
73
  return {
74
74
  handled: true,
75
75
  transformedText:
76
- `soly: no .soly/ directory found in cwd (${state.solyDir || "<cwd>"}) — cannot execute phase.\n` +
76
+ `soly: no .agents/ directory found in cwd (${state.solyDir || "<cwd>"}) — cannot execute phase.\n` +
77
77
  `Initialize a soly project first (see soly quickstart) before running "soly execute".`,
78
78
  };
79
79
  }
@@ -101,7 +101,7 @@ export function buildExecuteTransform(
101
101
  return {
102
102
  handled: true,
103
103
  transformedText:
104
- `soly execute: task ${target.taskId} not found in .soly/features/*/tasks/.\n` +
104
+ `soly execute: task ${target.taskId} not found in .agents/features/*/tasks/.\n` +
105
105
  `Known tasks: ${state.tasks.map((t) => t.id).join(", ") || "(none)"}\n` +
106
106
  `Tip: use the \`soly_list_tasks\` tool to see all available tasks.`,
107
107
  };
@@ -155,7 +155,7 @@ export function buildExecuteTransform(
155
155
  const featureDir = path.dirname(path.dirname(task.dir));
156
156
 
157
157
  // Write per-iteration context bundle (B2 of the soly design).
158
- // Worker reads this file first; no need to chase 6+ .soly/ files.
158
+ // Worker reads this file first; no need to chase 6+ .agents/ files.
159
159
  const iter = writeIterationContext({
160
160
  solyDir: state.solyDir,
161
161
  projectRoot,
@@ -179,7 +179,7 @@ export function buildExecuteTransform(
179
179
  **Iteration context file written:** \`${iter.relPath}\` (${iter.tokens} tokens, ${iter.bytes} bytes)
180
180
  The worker reads this file first — it contains intent, STATE, ROADMAP (n/a for tasks), the feature README, prior task SUMMARYs, and the current task PLAN.
181
181
 
182
- **0-POINT CHECK.** Worker must re-read .soly/docs/ (intent) and .soly/features/${task.feature}/README.md before implementing.
182
+ **0-POINT CHECK.** Worker must re-read .agents/docs/ (intent) and .agents/features/${task.feature}/README.md before implementing.
183
183
 
184
184
  Launch a single subagent for this work. Do NOT do the work inline.
185
185
 
@@ -206,12 +206,12 @@ Soly dir: ${state.solyDir}
206
206
  Feature dir: ${featureDir}
207
207
  Task dir: ${task.dir}
208
208
 
209
- **0-POINT CHECK — read .soly/docs/ first.**
209
+ **0-POINT CHECK — read .agents/docs/ first.**
210
210
  These are the project's INTENT (business context, design vision). Re-read them before implementing. If you find a conflict between intent and PLAN.md, flag it instead of silently choosing one.
211
211
 
212
- **Follow the worker self-audit gate (see .soly/rules/process/worker-audit.md):**
212
+ **Follow the worker self-audit gate (see .agents/rules/process/worker-audit.md):**
213
213
  1. Run \`dotnet build\` (or relevant build) — 0 warnings
214
- 2. Cross-check diff against .soly/rules/coding/*
214
+ 2. Cross-check diff against .agents/rules/coding/*
215
215
  3. Invoke \`analyzer-coach\` skill for any rule gaps
216
216
  4. Loop until clean (max 3 iterations)
217
217
  5. Commit (production-code commit(s))
@@ -224,10 +224,10 @@ ${workflow}
224
224
 
225
225
  Hard rules:
226
226
  - Do not skip the close-out order: production commits -> SUMMARY commit -> status: done.
227
- - Do not modify any .soly/rules/ files.
227
+ - Do not modify any .agents/rules/ files.
228
228
  - Do not run subagents yourself.
229
229
  - Do not start a task whose \`depends-on:\` lists tasks that are not \`done\`.
230
- - PATH DISCIPLINE: all files YOU create must live under \`.soly/\` (iteration, handoff, etc.) or under the project's source dirs. Never write to the project root.
230
+ - PATH DISCIPLINE: all files YOU create must live under \`.agents/\` (iteration, handoff, etc.) or under the project's source dirs. Never write to the project root.
231
231
  - Return: changed files, commands run with exit codes, validation evidence, surprises, decisions needing parent approval.
232
232
  - Interactive-only rules are NOT in scope for you: ${interactiveRules.length > 0 ? interactiveRules.join(", ") : "(none)"}.
233
233
  \`
@@ -248,7 +248,7 @@ When the subagent completes, synthesize the result. Do not re-execute its work.
248
248
  handled: true,
249
249
  transformedText:
250
250
  target.kind === "all"
251
- ? `soly execute --all: no tasks found in .soly/features/*/tasks/.`
251
+ ? `soly execute --all: no tasks found in .agents/features/*/tasks/.`
252
252
  : `soly execute --feature ${target.feature}: no tasks found for that feature.`,
253
253
  };
254
254
  }
@@ -280,7 +280,7 @@ When the subagent completes, synthesize the result. Do not re-execute its work.
280
280
  return {
281
281
  handled: true,
282
282
  transformedText:
283
- `soly execute: phase ${target.phase} not found in .soly/phases/.\n` +
283
+ `soly execute: phase ${target.phase} not found in .agents/phases/.\n` +
284
284
  `Known phases: ${state.phases.map((p) => p.number).join(", ") || "(none)"}`,
285
285
  };
286
286
  }
@@ -358,8 +358,8 @@ The iteration context file lists all plans (their frontmatter) in section 6, gro
358
358
  **Iteration context file written:** \`${iter.relPath}\` (${iter.tokens} tokens, ${iter.bytes} bytes)
359
359
  The worker reads this file first — it contains intent, STATE, ROADMAP row for this phase, phase CONTEXT, phase RESEARCH, prior SUMMARYs, ${isPlanLevel ? "and the current PLAN" : "and all PLAN frontmatter summaries"}, and (for exec) the Critical Anti-Patterns from .continue-here.md.
360
360
 
361
- **0-POINT CHECK — worker must read .soly/docs/ first.**
362
- These are the project's INTENT docs. The worker is about to implement tasks; if the implementation diverges from intent, it will be wrong even if the tests pass. Have the worker re-read .soly/docs/ (and any intent docs linked from PLAN.md) before each plan.
361
+ **0-POINT CHECK — worker must read .agents/docs/ first.**
362
+ These are the project's INTENT docs. The worker is about to implement tasks; if the implementation diverges from intent, it will be wrong even if the tests pass. Have the worker re-read .agents/docs/ (and any intent docs linked from PLAN.md) before each plan.
363
363
 
364
364
  ${scopeBlock}
365
365
 
@@ -389,7 +389,7 @@ Project root: ${projectRoot}
389
389
  Soly dir: ${state.solyDir}
390
390
  Phase dir: ${phase.dir}
391
391
 
392
- **0-POINT CHECK — read .soly/docs/ first.**
392
+ **0-POINT CHECK — read .agents/docs/ first.**
393
393
  These are the project's INTENT (business context, design vision). Re-read them before implementing each plan. If you find a conflict between intent and PLAN.md, flag it instead of silently choosing one.
394
394
 
395
395
  Follow the workflow below VERBATIM — these are the user-approved soly instructions, not suggestions.
@@ -400,9 +400,9 @@ ${workflow}
400
400
 
401
401
  Hard rules:
402
402
  - Do not skip the close-out order: production commits -> SUMMARY commit -> STATE/ROADMAP update.
403
- - Do not modify any .soly/rules/ files.
403
+ - Do not modify any .agents/rules/ files.
404
404
  - Do not run subagents yourself.
405
- - PATH DISCIPLINE: all files YOU create must live under \`.soly/\` (e.g. .soly/iterations/, .soly/phases/<slug>/, .soly/HANDOFF.json) or under the project's source dirs. Never write PLAN/SUMMARY/CONTEXT/RESEARCH/iteration files to the project root.
405
+ - PATH DISCIPLINE: all files YOU create must live under \`.agents/\` (e.g. .agents/iterations/, .agents/phases/<slug>/, .agents/HANDOFF.json) or under the project's source dirs. Never write PLAN/SUMMARY/CONTEXT/RESEARCH/iteration files to the project root.
406
406
  - Return: changed files, commands run with exit codes, validation evidence, surprises, and any decisions needing parent approval.
407
407
  - Interactive-only rules are NOT in scope for you: ${interactiveRules.length > 0 ? interactiveRules.join(", ") : "(none)"}. They describe how the user-facing conversation should go, not how to execute work.
408
408
  \`
@@ -24,7 +24,6 @@ import { showStatus, showLog, showDiff } from "./quick.ts";
24
24
  import { showDoctor, showIterations, showDiffIterations, showPhaseDelete, showTodos } from "./inspect.ts";
25
25
  import { buildPlanTransform, buildDiscussTransform } from "./planning.ts";
26
26
  import { createVerifyLoop, type VerifyState } from "./verify.ts";
27
- import { buildMigrateTransform } from "./migrate.ts";
28
27
  import type { ContextManager } from "../context-manager.ts";
29
28
  import type { SolyState } from "../core.js";
30
29
  import type { SolyConfig } from "../config.js";
@@ -145,12 +144,6 @@ export function registerWorkflows(pi: ExtensionAPI, deps: WorkflowsDeps): void {
145
144
  return { action: "transform", text: result.transformedText };
146
145
  }
147
146
 
148
- if (cmd.verb === "migrate") {
149
- const result = buildMigrateTransform(state);
150
- if (!result.handled || !result.transformedText) return;
151
- return { action: "transform", text: result.transformedText };
152
- }
153
-
154
147
  if (cmd.verb === "verify") {
155
148
  const sub = (cmd.args[0] ?? "").toLowerCase();
156
149
  if (sub === "stop" || sub === "off") {
@@ -179,15 +172,12 @@ Lifecycle:
179
172
  Quick info (no LLM round-trip):
180
173
  status — position + progress + phases
181
174
  log [N] — last N decisions from STATE.md
182
- diff — git status + uncommitted .soly/ changes
175
+ diff — git status + uncommitted .agents/ changes
183
176
  doctor — health check (missing files, broken refs, stale iterations)
184
177
  iterations [N] — recent iteration bundles
185
178
  todos — pi-todo live list
186
179
  phase delete <N> — soft-delete a phase
187
180
 
188
- Maintenance:
189
- migrate — convert a legacy layout (NN-PLAN files / features/) to phases/<N>/tasks/
190
-
191
181
  State inspection lives on the slash form — \`/soly <sub>\`:
192
182
  position · state · plan · roadmap · progress · phases · tasks · task <id> ·
193
183
  features · milestone · context · research · config · reload
@@ -249,7 +239,7 @@ State inspection lives on the slash form — \`/soly <sub>\`:
249
239
  return {
250
240
  action: "transform",
251
241
  text: `soly phase — usage:
252
- soly phase delete <N> — soft-delete phase N (move to .soly/phases/.trash/)
242
+ soly phase delete <N> — soft-delete phase N (move to .agents/phases/.trash/)
253
243
  soly phase list — list all phases (same as /soly phases)
254
244
  soly phase <N> — alias for "soly plan <N>" (route through planner)`,
255
245
  };
@@ -265,8 +255,8 @@ State inspection lives on the slash form — \`/soly <sub>\`:
265
255
  pendingCompact = false;
266
256
  ctx.compact({
267
257
  customInstructions:
268
- "Session was paused via `soly compact`. Handoff files are in .soly/HANDOFF.json " +
269
- "and .soly/.continue-here.md. Preserve milestone/phase/plan position and key " +
258
+ "Session was paused via `soly compact`. Handoff files are in .agents/HANDOFF.json " +
259
+ "and .agents/.continue-here.md. Preserve milestone/phase/plan position and key " +
270
260
  "decisions in the summary. Drop implementation-detail noise.",
271
261
  onComplete: () => {
272
262
  ctx.ui.notify("soly: session compacted. Use `soly resume` to pick up.", "info");
@@ -6,6 +6,7 @@
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
+ import { solyDirFor } from "../core.js";
9
10
  import type { SolyState } from "../core.js";
10
11
  import type { SolyConfig } from "../config.js";
11
12
 
@@ -28,11 +29,11 @@ interface DoctorCheck {
28
29
  export function showDoctor(_cmd: unknown, state: SolyState, ui: InspectUI, config: SolyConfig, activeTools: string[] = []): void {
29
30
  const checks: DoctorCheck[] = [];
30
31
 
31
- // 1. .soly/ exists
32
+ // 1. .agents/ exists
32
33
  checks.push({
33
- name: ".soly/ directory",
34
+ name: ".agents/ directory",
34
35
  status: state.exists ? "pass" : "fail",
35
- detail: state.exists ? state.solyDir : "no .soly/ found in cwd",
36
+ detail: state.exists ? state.solyDir : "no .agents/ found in cwd",
36
37
  });
37
38
 
38
39
  // 2. STATE.md exists + has frontmatter
@@ -177,13 +178,13 @@ export function showDoctor(_cmd: unknown, state: SolyState, ui: InspectUI, confi
177
178
  }
178
179
  }
179
180
 
180
- // 9. .soly/rules/ exists if state says rules are loaded
181
+ // 9. .agents/rules/ exists if state says rules are loaded
181
182
  if (state.exists) {
182
183
  const rulesDir = path.join(state.solyDir, "rules");
183
184
  checks.push({
184
- name: ".soly/rules/ directory",
185
+ name: ".agents/rules/ directory",
185
186
  status: fs.existsSync(rulesDir) ? "pass" : "warn",
186
- detail: fs.existsSync(rulesDir) ? "present" : "no rules directory — soly will fall back to ~/.soly/rules/",
187
+ detail: fs.existsSync(rulesDir) ? "present" : "no rules directory — soly will fall back to ~/.agents/rules/",
187
188
  });
188
189
  }
189
190
 
@@ -220,7 +221,7 @@ export function showDoctor(_cmd: unknown, state: SolyState, ui: InspectUI, confi
220
221
  name: "project layout",
221
222
  status: legacy ? "info" : "pass",
222
223
  detail: legacy
223
- ? "legacy plans/features detected — run `soly migrate` to move to phases/<N>/tasks/"
224
+ ? "legacy plans/features detected — still supported alongside the unified phases/<N>/tasks/ model"
224
225
  : "unified model (phase = group of tasks)",
225
226
  });
226
227
  }
@@ -258,7 +259,7 @@ function pluralDays(n: number): string {
258
259
  }
259
260
 
260
261
  // ---------------------------------------------------------------------------
261
- // soly todos — read .soly/todos.json or .pi-todos.json and show as a notify.
262
+ // soly todos — read .agents/todos.json or .pi-todos.json and show as a notify.
262
263
  // Mirrors what pi-todo would render in the footer, so the user can see
263
264
  // todos even when pi-todo extension isn't loaded (e.g. just-installed).
264
265
  // ---------------------------------------------------------------------------
@@ -266,7 +267,7 @@ function pluralDays(n: number): string {
266
267
  /** Try to find a todo file in cwd. Returns the path or null. */
267
268
  function findTodosFile(cwd: string): string | null {
268
269
  const candidates = [
269
- path.join(cwd, ".soly", "todos.json"),
270
+ path.join(solyDirFor(cwd), "todos.json"),
270
271
  path.join(cwd, ".pi-todos.json"),
271
272
  ];
272
273
  for (const c of candidates) {
@@ -281,13 +282,13 @@ export function showTodos(
281
282
  ui: InspectUI,
282
283
  ): void {
283
284
  if (!state.exists) {
284
- ui.notify("soly todos: no .soly/ directory in cwd", "error");
285
+ ui.notify("soly todos: no .agents/ directory in cwd", "error");
285
286
  return;
286
287
  }
287
288
  const file = findTodosFile(state.solyDir);
288
289
  if (!file) {
289
290
  ui.notify(
290
- "soly todos: no todo file found. Install the `pi-todo` extension or write `.soly/todos.json` manually.",
291
+ "soly todos: no todo file found. Install the `pi-todo` extension or write `.agents/todos.json` manually.",
291
292
  "info",
292
293
  );
293
294
  return;
@@ -329,7 +330,7 @@ export function showIterations(
329
330
  limitDefault: number = 10,
330
331
  ): void {
331
332
  if (!state.exists) {
332
- ui.notify("soly iterations: no .soly/ directory in cwd", "error");
333
+ ui.notify("soly iterations: no .agents/ directory in cwd", "error");
333
334
  return;
334
335
  }
335
336
  const iterDir = path.join(state.solyDir, "iterations");
@@ -398,7 +399,7 @@ export function showDiffIterations(
398
399
  ui: InspectUI,
399
400
  ): void {
400
401
  if (!state.exists) {
401
- ui.notify("soly diff iterations: no .soly/ directory in cwd", "error");
402
+ ui.notify("soly diff iterations: no .agents/ directory in cwd", "error");
402
403
  return;
403
404
  }
404
405
  const iterDir = path.join(state.solyDir, "iterations");
@@ -456,7 +457,7 @@ export function showPhaseDelete(
456
457
  ui: InspectUI,
457
458
  ): void {
458
459
  if (!state.exists) {
459
- ui.notify("soly phase delete: no .soly/ directory in cwd", "error");
460
+ ui.notify("soly phase delete: no .agents/ directory in cwd", "error");
460
461
  return;
461
462
  }
462
463
  if (cmd.args.length < 1) {
@@ -492,7 +493,7 @@ export function showPhaseDelete(
492
493
  out.push(`✓ Phase ${phaseNum} (${phase.name}) moved to .trash/`);
493
494
  out.push(` ${phase.dir} → ${dest}`);
494
495
  out.push("");
495
- out.push("To restore: `mv` it back to .soly/phases/");
496
+ out.push("To restore: `mv` it back to .agents/phases/");
496
497
  out.push("To permanently delete: `rm -rf " + dest + "`");
497
498
  ui.notify(out.join("\n"), "info");
498
499
  }
@@ -18,7 +18,7 @@
18
18
  /** Verbs currently supported by the workflow handlers. */
19
19
  export type WorkflowVerb =
20
20
  | "execute" | "pause" | "compact" | "resume" | "status" | "log" | "diff"
21
- | "plan" | "discuss" | "help" | "doctor" | "iterations" | "phase" | "todos" | "verify" | "migrate";
21
+ | "plan" | "discuss" | "help" | "doctor" | "iterations" | "phase" | "todos" | "verify";
22
22
 
23
23
  export interface SolyCommand {
24
24
  verb: WorkflowVerb;
@@ -67,8 +67,7 @@ export function parseSolyCommand(text: string): SolyCommand | null {
67
67
  verb !== "iterations" &&
68
68
  verb !== "phase" &&
69
69
  verb !== "todos" &&
70
- verb !== "verify" &&
71
- verb !== "migrate"
70
+ verb !== "verify"
72
71
  ) {
73
72
  return null;
74
73
  }
@@ -7,8 +7,8 @@
7
7
  //
8
8
  // Like execute, we transform the input into a detailed LLM instruction that
9
9
  // walks the LLM through the soly pause-work workflow. The LLM produces both:
10
- // - .soly/HANDOFF.json (machine-readable state for resume)
11
- // - .soly/.continue-here.md (human-readable context)
10
+ // - .agents/HANDOFF.json (machine-readable state for resume)
11
+ // - .agents/.continue-here.md (human-readable context)
12
12
  //
13
13
  // For `compact`, we additionally call ctx.compact() AFTER the handoff files
14
14
  // are written — but we still let the LLM drive the handoff generation, since
@@ -94,7 +94,7 @@ export function buildPauseTransform(
94
94
  return {
95
95
  handled: true,
96
96
  transformedText:
97
- `soly: no .soly/ directory found in cwd (${state.solyDir || "<cwd>"}) — nothing to pause.\n` +
97
+ `soly: no .agents/ directory found in cwd (${state.solyDir || "<cwd>"}) — nothing to pause.\n` +
98
98
  `If you wanted to start a soly project, see the soly quickstart.`,
99
99
  triggerCompact: false,
100
100
  };
@@ -122,7 +122,7 @@ export function buildPauseTransform(
122
122
 
123
123
  ${actionLine}
124
124
 
125
- Current position (from .soly/STATE.md):
125
+ Current position (from .agents/STATE.md):
126
126
  ${state.position
127
127
  ? ` phase: ${state.position.phase}\n plan: ${state.position.plan}\n status: ${state.position.status}`
128
128
  : " (no position set — likely pre-planning or paused at a milestone boundary)"}
@@ -139,7 +139,7 @@ Follow the workflow below VERBATIM — these are the user-approved soly instruct
139
139
  ${workflow}
140
140
  === END WORKFLOW ===
141
141
 
142
- After you write both .soly/HANDOFF.json and .soly/.continue-here.md, tell the user:
142
+ After you write both .agents/HANDOFF.json and .agents/.continue-here.md, tell the user:
143
143
  - where the files were written (absolute paths)
144
144
  - the resume command: soly resume
145
145
  - ${isCompact ? "session will be compacted at end of this turn" : "session state preserved as-is"}
@@ -67,7 +67,7 @@ export function buildPlanTransform(cmd: SolyCommand, state: SolyState): Planning
67
67
  return {
68
68
  handled: true,
69
69
  transformedText:
70
- `soly plan: no .soly/ directory in cwd (${state.solyDir || "<cwd>"}) — cannot plan.\n` +
70
+ `soly plan: no .agents/ directory in cwd (${state.solyDir || "<cwd>"}) — cannot plan.\n` +
71
71
  `Initialize a soly project first.`,
72
72
  };
73
73
  }
@@ -123,8 +123,8 @@ export function buildPlanTransform(cmd: SolyCommand, state: SolyState): Planning
123
123
  **Iteration context file written:** \`${iter.relPath}\` (${iter.tokens} tokens, ${iter.bytes} bytes)
124
124
  The planner reads this file first — it contains intent, STATE, ROADMAP row for this phase, phase CONTEXT, phase RESEARCH, and prior SUMMARYs.
125
125
 
126
- **0-POINT CHECK — read .soly/docs/ first.**
127
- These documents hold the project's INTENT — business context, design vision, what the user wants this app to be. Plans that ignore intent produce code that "works" but doesn't fit. If the plan would diverge from anything in .soly/docs/, surface that as a discussion point before committing to it.
126
+ **0-POINT CHECK — read .agents/docs/ first.**
127
+ These documents hold the project's INTENT — business context, design vision, what the user wants this app to be. Plans that ignore intent produce code that "works" but doesn't fit. If the plan would diverge from anything in .agents/docs/, surface that as a discussion point before committing to it.
128
128
 
129
129
  Phase directory: ${phase.dir}
130
130
  Current state: planCount=${phase.planCount}, context=${phase.contextExists}, research=${phase.researchExists}
@@ -155,11 +155,11 @@ ${workflow}
155
155
 
156
156
  Hard rules:
157
157
  - Do not write production code. Planning only.
158
- - One task = one PLAN.md under .soly/phases/<NN>-<slug>/tasks/<task-id>/ (task id = <short-slug>-<4hex>).
158
+ - One task = one PLAN.md under .agents/phases/<NN>-<slug>/tasks/<task-id>/ (task id = <short-slug>-<4hex>).
159
159
  - Each task PLAN.md starts with frontmatter: id, kind, status: ready, depends-on: [task ids], optional feature. The cross-task dependency graph must be acyclic.
160
160
  - Each task PLAN needs requirements, must_haves.truths, must_haves.artifacts, must_haves.key_links.
161
- - PATH DISCIPLINE: all task PLAN.md / phase CONTEXT.md / RESEARCH.md go under \`.soly/phases/<NN>-<slug>/\`. Never write to the project root.
162
- - Update .soly/STATE.md Current Position at the end.
161
+ - PATH DISCIPLINE: all task PLAN.md / phase CONTEXT.md / RESEARCH.md go under \`.agents/phases/<NN>-<slug>/\`. Never write to the project root.
162
+ - Update .agents/STATE.md Current Position at the end.
163
163
  - Return: created task ids, the dependency order (which tasks are ready first), open questions.
164
164
  \`
165
165
  })
@@ -231,7 +231,7 @@ The planner reads this file first — it contains intent, STATE, the feature REA
231
231
  **Inline plan summary (so you have the must-haves even before reading the file):**
232
232
  ${inlineSummary}
233
233
 
234
- **0-POINT CHECK.** Re-read .soly/docs/ (intent) and .soly/features/${task.feature}/README.md (feature context) before refining the plan.
234
+ **0-POINT CHECK.** Re-read .agents/docs/ (intent) and .agents/features/${task.feature}/README.md (feature context) before refining the plan.
235
235
 
236
236
  This task already has PLAN.md. Your job is to flesh it out / improve it based on intent and feature context — not to start from scratch.
237
237
 
@@ -265,7 +265,7 @@ Hard rules:
265
265
  - Preserve the existing frontmatter (id, kind, feature, status, etc.) — only update if you find a bug.
266
266
  - If you change the plan body materially, commit it as \`chore(tasks): refine plan <task-id>\`.
267
267
  - If you only add small clarifications, no commit needed (or include in same commit).
268
- - PATH DISCIPLINE: PLAN.md lives at \`.soly/features/<feature>/tasks/<id>/PLAN.md\`. Never write to the project root.
268
+ - PATH DISCIPLINE: PLAN.md lives at \`.agents/features/<feature>/tasks/<id>/PLAN.md\`. Never write to the project root.
269
269
  - Return: what changed, open questions, dependencies discovered.
270
270
  \`
271
271
  })
@@ -289,8 +289,8 @@ When the subagent returns, summarize what was refined. Do not execute — planni
289
289
  return {
290
290
  handled: true,
291
291
  transformedText:
292
- `soly plan: could not create .soly/features/${target.feature}/tasks/ (${(e as Error).message}). ` +
293
- `Create it manually: \`mkdir -p .soly/features/${target.feature}/tasks/\``,
292
+ `soly plan: could not create .agents/features/${target.feature}/tasks/ (${(e as Error).message}). ` +
293
+ `Create it manually: \`mkdir -p .agents/features/${target.feature}/tasks/\``,
294
294
  };
295
295
  }
296
296
  try {
@@ -305,8 +305,8 @@ When the subagent returns, summarize what was refined. Do not execute — planni
305
305
  return {
306
306
  handled: true,
307
307
  transformedText:
308
- `soly plan: created .soly/features/${target.feature}/tasks/ but failed to write README.md (${(e as Error).message}). ` +
309
- `Continue manually: \`touch .soly/features/${target.feature}/README.md\``,
308
+ `soly plan: created .agents/features/${target.feature}/tasks/ but failed to write README.md (${(e as Error).message}). ` +
309
+ `Continue manually: \`touch .agents/features/${target.feature}/README.md\``,
310
310
  };
311
311
  }
312
312
  // Re-read state so the planner sees the new feature
@@ -331,13 +331,13 @@ When the subagent returns, summarize what was refined. Do not execute — planni
331
331
  **Slug:** ${target.slug}
332
332
  **Feature README:** ${featureDir}/README.md
333
333
 
334
- **0-POINT CHECK.** Re-read .soly/docs/ (intent) and .soly/features/${target.feature}/README.md (feature context) before planning.
334
+ **0-POINT CHECK.** Re-read .agents/docs/ (intent) and .agents/features/${target.feature}/README.md (feature context) before planning.
335
335
 
336
336
  **Step 1 — generate task ID.** The task ID is \`<slug>-<4hex>\` (e.g. \`${target.slug}-a3f9\`). Generate 4 lowercase hex chars (use \`crypto.randomBytes(2).toString('hex')\` in node, or any 4-char [0-9a-f]{4} string if you don't have a shell handy).
337
337
 
338
338
  **Step 2 — create the dir:**
339
339
  \`\`\`
340
- mkdir -p .soly/features/${target.feature}/tasks/<id>
340
+ mkdir -p .agents/features/${target.feature}/tasks/<id>
341
341
  \`\`\`
342
342
 
343
343
  **Step 3 — write PLAN.md** with the frontmatter below + the plan body.
@@ -379,7 +379,7 @@ ${workflow}
379
379
  Hard rules:
380
380
  - Do not write production code. Planning only.
381
381
  - Generate the task id as \`<slug>-<4hex>\` (e.g. \`${target.slug}-a3f9\`) — use 4 lowercase hex chars.
382
- - Create the dir \`.soly/features/${target.feature}/tasks/<id>/\` first.
382
+ - Create the dir \`.agents/features/${target.feature}/tasks/<id>/\` first.
383
383
  - Write PLAN.md with the frontmatter (id, kind, feature, status: ready, priority, parallelizable, depends-on).
384
384
  - Pick a \`kind:\` value matching the work (be|fe|infra|docs|integration).
385
385
  - Pick a reasonable \`priority:\` (default: medium).
@@ -457,7 +457,7 @@ export function buildDiscussTransform(
457
457
  return {
458
458
  handled: true,
459
459
  transformedText:
460
- `soly discuss: no .soly/ directory in cwd (${state.solyDir || "<cwd>"}) — cannot discuss.\n` +
460
+ `soly discuss: no .agents/ directory in cwd (${state.solyDir || "<cwd>"}) — cannot discuss.\n` +
461
461
  `Initialize a soly project first.`,
462
462
  };
463
463
  }
@@ -594,7 +594,7 @@ soly_ask_user({
594
594
  { category: "<cat>", choice: "<what was chosen>", rationale: "<why>" },
595
595
  ...
596
596
  ],
597
- canonical_refs: [".soly/docs/<file>", ".soly/ROADMAP.md", ...],
597
+ canonical_refs: [".agents/docs/<file>", ".agents/ROADMAP.md", ...],
598
598
  deferred_ideas: ["<scope creep for future phase>", ...],
599
599
  codebase_context: ["src/components/Card.tsx — has rounded/shadow variants, reuse", ...],
600
600
  })
@@ -608,7 +608,7 @@ soly_ask_user({
608
608
  - ${hasAskPro ? "`ask_pro` — multi-question tabbed picker (PREFERRED)" : "`soly_ask_user` — single-question picker (fallback)"}
609
609
  - \`soly_save_discuss_checkpoint\` — save partial progress (use after each answer)
610
610
  - \`soly_finish_discuss\` — finalize: writes CONTEXT.md, deletes checkpoint
611
- - \`soly_read\`, \`soly_snippet\`, \`soly_doc_search\` — read .soly/ artifacts as needed (intent docs are already in your system prompt)
611
+ - \`soly_read\`, \`soly_snippet\`, \`soly_doc_search\` — read .agents/ artifacts as needed (intent docs are already in your system prompt)
612
612
  - \`soly_log_decision\` — log to STATE.md Decisions table (use sparingly)
613
613
  - Standard pi tools: \`read\`, \`bash\`, \`grep\`, \`find\` for codebase context
614
614