pi-soly 1.16.0 → 1.16.2
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/commands.ts +2 -2
- package/config.ts +26 -0
- package/index.ts +1 -0
- package/nudge.ts +22 -11
- package/package.json +1 -1
- package/workflows/done.ts +5 -4
- package/workflows/execute.ts +5 -2
- package/workflows/index.ts +10 -2
- package/workflows/new.ts +13 -10
- package/workflows/parser.ts +38 -11
- package/workflows/planning.ts +9 -4
package/commands.ts
CHANGED
|
@@ -589,12 +589,12 @@ What must the LLM do?
|
|
|
589
589
|
}
|
|
590
590
|
switch (verb) {
|
|
591
591
|
case "new": {
|
|
592
|
-
const r = buildNewTransform(cmd, state, ui, ctx.cwd);
|
|
592
|
+
const r = buildNewTransform(cmd, state, ui, ctx.cwd, getConfig().plan.defaultBranchPrefix);
|
|
593
593
|
if (r.handled && r.transformedText) ui.notify(r.transformedText, "info");
|
|
594
594
|
return;
|
|
595
595
|
}
|
|
596
596
|
case "done": {
|
|
597
|
-
const r = buildDoneTransform(cmd, state, ui, ctx.cwd);
|
|
597
|
+
const r = buildDoneTransform(cmd, state, ui, ctx.cwd, { defaultBranchPrefix: getConfig().plan.defaultBranchPrefix });
|
|
598
598
|
if (r.handled && r.transformedText) ui.notify(r.transformedText, "info");
|
|
599
599
|
return;
|
|
600
600
|
}
|
package/config.ts
CHANGED
|
@@ -121,6 +121,18 @@ export interface SolyConfig {
|
|
|
121
121
|
* 0 = keep forever. */
|
|
122
122
|
retentionDays: number;
|
|
123
123
|
};
|
|
124
|
+
plan: {
|
|
125
|
+
/** Optional branch prefix prepended when the user runs `soly new <slug>`
|
|
126
|
+
* without an explicit `<prefix>/<slug>` form. Common values:
|
|
127
|
+
* "feature" → branches like `feature/statistic-preparation`
|
|
128
|
+
* "fix" → branches like `fix/login-redirect`
|
|
129
|
+
* "" (default)→ no prefix; branch = slug verbatim.
|
|
130
|
+
* Per-project only (set in `.agents/soly.json`). User-supplied
|
|
131
|
+
* `<prefix>/<slug>` always wins over this default.
|
|
132
|
+
* Plan dir is always `.agents/plans/<prefix>-<slug>` (flattened)
|
|
133
|
+
* so the on-disk layout stays one-deep. */
|
|
134
|
+
defaultBranchPrefix: string;
|
|
135
|
+
};
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
export const DEFAULT_CONFIG: SolyConfig = {
|
|
@@ -184,6 +196,9 @@ export const DEFAULT_CONFIG: SolyConfig = {
|
|
|
184
196
|
theme: "",
|
|
185
197
|
retentionDays: 7,
|
|
186
198
|
},
|
|
199
|
+
plan: {
|
|
200
|
+
defaultBranchPrefix: "", // "" = no prefix; branch = slug verbatim
|
|
201
|
+
},
|
|
187
202
|
};
|
|
188
203
|
|
|
189
204
|
/** Raw parsed shape from a config.json — could be partial, could have wrong types. */
|
|
@@ -256,6 +271,17 @@ function deepMerge(base: SolyConfig, over: RawConfig): SolyConfig {
|
|
|
256
271
|
if (over.editor && typeof over.editor.command === "string") {
|
|
257
272
|
merged.editor.command = over.editor.command;
|
|
258
273
|
}
|
|
274
|
+
if (over.plan && typeof over.plan.defaultBranchPrefix === "string") {
|
|
275
|
+
// Sanitize: lowercase, then allow only kebab-case chars (no
|
|
276
|
+
// slashes/dots/spaces). Empty string is allowed and means
|
|
277
|
+
// "no prefix". We do NOT warn on slash/uppercase — the user
|
|
278
|
+
// probably meant a different prefix shape; silently normalizing
|
|
279
|
+
// keeps configs from being rejected for cosmetic reasons.
|
|
280
|
+
const lowercased = over.plan.defaultBranchPrefix.toLowerCase();
|
|
281
|
+
const sanitized = lowercased.replace(/[^a-z0-9-]/g, "");
|
|
282
|
+
const collapsed = sanitized.replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
283
|
+
merged.plan.defaultBranchPrefix = collapsed;
|
|
284
|
+
}
|
|
259
285
|
if (over.chrome) {
|
|
260
286
|
if (typeof over.chrome.enabled === "boolean") merged.chrome.enabled = over.chrome.enabled;
|
|
261
287
|
if (typeof over.chrome.ascii === "boolean") merged.chrome.ascii = over.chrome.ascii;
|
package/index.ts
CHANGED
|
@@ -694,6 +694,7 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
694
694
|
buildNudgeSection(heuristics, {
|
|
695
695
|
hasProject: state.exists,
|
|
696
696
|
confirmBeforeCode: getActiveConfig().agent.confirmBeforeCode,
|
|
697
|
+
defaultBranchPrefix: getActiveConfig().plan.defaultBranchPrefix,
|
|
697
698
|
}),
|
|
698
699
|
);
|
|
699
700
|
|
package/nudge.ts
CHANGED
|
@@ -117,7 +117,11 @@ const ASK_DIRECTIVE = `**Confirm before coding.** Don't jump straight into writi
|
|
|
117
117
|
|
|
118
118
|
export function buildNudgeSection(
|
|
119
119
|
heuristics: TaskHeuristics,
|
|
120
|
-
opts: {
|
|
120
|
+
opts: {
|
|
121
|
+
hasProject?: boolean;
|
|
122
|
+
confirmBeforeCode?: boolean | ConfirmLevel;
|
|
123
|
+
defaultBranchPrefix?: string;
|
|
124
|
+
} = {},
|
|
121
125
|
): string {
|
|
122
126
|
// Always-on rules (cheap to add, high signal):
|
|
123
127
|
// - Don't dive in on non-trivial tasks without a brief check
|
|
@@ -145,20 +149,27 @@ export function buildNudgeSection(
|
|
|
145
149
|
// model toward the workflow lifecycle instead of ad-hoc edits. This block
|
|
146
150
|
// also tells the model it may *itself* scaffold a new plan after asking the
|
|
147
151
|
// user — the user explicitly opted into this in 1.16.0.
|
|
152
|
+
const prefix = opts.defaultBranchPrefix ?? "";
|
|
153
|
+
const branchLine = prefix
|
|
154
|
+
? `Branches look like \`${prefix}/<slug>\` (the project default is **\`"${prefix}"\`**). Plan dir: \`.agents/plans/${prefix}-<slug>/\`.`
|
|
155
|
+
: `Branches look like \`<slug>\` (no project default prefix is set). Plan dir: \`.agents/plans/<slug>/\`.`;
|
|
148
156
|
const workflowPoint =
|
|
149
157
|
opts.hasProject && heuristics.nonTrivial
|
|
150
|
-
? `\n\n4. **Route project work through the soly plan workflow.** Each plan is a git branch
|
|
158
|
+
? `\n\n4. **Route project work through the soly plan workflow.** Each plan is a git branch with \`.agents/plans/<slug>/PLAN.md\` on it. Two parallel plans don't collide.
|
|
159
|
+
|
|
160
|
+
**Branch naming convention for THIS project:** ${branchLine}
|
|
161
|
+
\`soly new <slug>\` applies the project prefix automatically. \`soly new <prefix>/<slug>\` overrides it (use this when the work isn't a "feature" — e.g. \`soly new fix/login-redirect-bug\`).
|
|
151
162
|
|
|
152
163
|
Lifecycle (text commands you type — pi routes them through the workflow handlers):
|
|
153
|
-
- \`soly new <slug>\`
|
|
154
|
-
- \`soly discuss <slug>\`
|
|
155
|
-
- \`soly plan <slug>\`
|
|
156
|
-
- \`soly execute <slug>\`
|
|
157
|
-
- \`soly done <slug>\`
|
|
158
|
-
- \`soly verify\`
|
|
159
|
-
- \`soly status\`
|
|
160
|
-
|
|
161
|
-
**You may scaffold a new plan yourself** when the user asks for a new piece of work and the scope is clear (or the user just confirmed). Propose the slug first via ask_pro
|
|
164
|
+
- \`soly new <slug-or-prefix>/<slug>\` — scaffold: branch + plan dir + stub PLAN.md + commit
|
|
165
|
+
- \`soly discuss <slug>\` — interactive discussion of the plan (uses ask_pro)
|
|
166
|
+
- \`soly plan <slug>\` — flesh out PLAN.md via ask_pro
|
|
167
|
+
- \`soly execute <slug>\` — execute the plan in a subagent
|
|
168
|
+
- \`soly done <slug>\` — commit + push + open draft PR via gh
|
|
169
|
+
- \`soly verify\` — self-review loop until clean
|
|
170
|
+
- \`soly status\` — current position + progress (no LLM round-trip)
|
|
171
|
+
|
|
172
|
+
**You may scaffold a new plan yourself** when the user asks for a new piece of work and the scope is clear (or the user just confirmed). Propose the slug AND the full branch name first via ask_pro — offer the default \`${prefix ? prefix + "/" : ""}<slug>\` plus the alternatives \`fix/<slug>\`, \`chore/<slug>\`, or just \`<slug>\` (no prefix). Pick whatever fits the work, then type \`soly new …\` in your next output. Don't ask for trivial one-liners or when the user already said "go". Skip only for a genuine one-off that doesn't deserve a plan branch.`
|
|
162
173
|
: "";
|
|
163
174
|
|
|
164
175
|
// Confirm-before-coding gate: for non-trivial implementation, pull the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-soly",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.2",
|
|
4
4
|
"description": "Project management + workflow framework for pi-coding-agent. Plans, state, MANDATORY rules, self-review, multi-question picker, native footer + welcome chrome — one npm install, zero config. The LLM drives execution and delegates to a worker subagent when available.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/workflows/done.ts
CHANGED
|
@@ -84,7 +84,7 @@ export function buildDoneTransform(
|
|
|
84
84
|
state: SolyState,
|
|
85
85
|
ui: Notifier,
|
|
86
86
|
projectRoot: string,
|
|
87
|
-
opts: { ghPath?: string } = {},
|
|
87
|
+
opts: { ghPath?: string; defaultBranchPrefix?: string } = {},
|
|
88
88
|
): DoneResult {
|
|
89
89
|
if (!state.exists) {
|
|
90
90
|
return reply(`soly done: no .agents/ directory in cwd — run /soly init first.`);
|
|
@@ -93,10 +93,11 @@ export function buildDoneTransform(
|
|
|
93
93
|
const raw = cmd.args.join(" ").trim();
|
|
94
94
|
const parsed = parsePlanName(raw);
|
|
95
95
|
if ("error" in parsed) {
|
|
96
|
-
return reply(`soly done: ${parsed.error}\n\nUsage: soly done <slug>`);
|
|
96
|
+
return reply(`soly done: ${parsed.error}\n\nUsage: soly done <slug> OR soly done <prefix>/<slug>`);
|
|
97
97
|
}
|
|
98
|
-
const { name } = parsed;
|
|
99
|
-
const
|
|
98
|
+
const { name, prefix } = parsed;
|
|
99
|
+
const effectivePrefix = prefix ?? opts.defaultBranchPrefix ?? "";
|
|
100
|
+
const branchName = effectivePrefix ? `${effectivePrefix}/${name}` : name;
|
|
100
101
|
|
|
101
102
|
// 1. Preconditions
|
|
102
103
|
const currentBranch = git(["branch", "--show-current"], { cwd: projectRoot });
|
package/workflows/execute.ts
CHANGED
|
@@ -274,9 +274,12 @@ When the subagent completes, synthesize the result. Do not re-execute its work.
|
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
// === PLAN MODE (new dual-mode: `<type>/<name>` plans live under .agents/plans/<name>/) ===
|
|
277
|
+
// === PLAN MODE (new dual-mode: `<type>/<name>` plans live under .agents/plans/<prefix>-<name>/) ===
|
|
278
278
|
if (target.kind === "plan") {
|
|
279
|
-
|
|
279
|
+
// Plan dir is always flattened: `<prefix>-<name>` if a prefix is
|
|
280
|
+
// present, else just `<name>`.
|
|
281
|
+
const dirSlug = target.prefix ? `${target.prefix}-${target.name}` : target.name;
|
|
282
|
+
const planDirAbs = `${state.solyDir}/plans/${dirSlug}`;
|
|
280
283
|
const planFile = `${planDirAbs}/PLAN.md`;
|
|
281
284
|
let planBody: string;
|
|
282
285
|
try {
|
package/workflows/index.ts
CHANGED
|
@@ -148,7 +148,13 @@ export function registerWorkflows(pi: ExtensionAPI, deps: WorkflowsDeps): void {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (cmd.verb === "new") {
|
|
151
|
-
const result = buildNewTransform(
|
|
151
|
+
const result = buildNewTransform(
|
|
152
|
+
cmd,
|
|
153
|
+
state,
|
|
154
|
+
ctx.ui,
|
|
155
|
+
ctx.cwd,
|
|
156
|
+
getConfig().plan.defaultBranchPrefix,
|
|
157
|
+
);
|
|
152
158
|
if (!result.handled || !result.transformedText) return;
|
|
153
159
|
// Direct execution (the workflow already called ui.notify). The
|
|
154
160
|
// transformed text goes to the model so it can also tell the user
|
|
@@ -157,7 +163,9 @@ export function registerWorkflows(pi: ExtensionAPI, deps: WorkflowsDeps): void {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
if (cmd.verb === "done") {
|
|
160
|
-
const result = buildDoneTransform(cmd, state, ctx.ui, ctx.cwd
|
|
166
|
+
const result = buildDoneTransform(cmd, state, ctx.ui, ctx.cwd, {
|
|
167
|
+
defaultBranchPrefix: getConfig().plan.defaultBranchPrefix,
|
|
168
|
+
});
|
|
161
169
|
if (!result.handled || !result.transformedText) return;
|
|
162
170
|
// Direct execution — workflow already called ui.notify. The
|
|
163
171
|
// transformed text tells the LLM (and the user in chat) what happened.
|
package/workflows/new.ts
CHANGED
|
@@ -80,6 +80,7 @@ export function buildNewTransform(
|
|
|
80
80
|
state: SolyState,
|
|
81
81
|
ui: Notifier,
|
|
82
82
|
projectRoot: string,
|
|
83
|
+
defaultBranchPrefix = "",
|
|
83
84
|
): NewResult {
|
|
84
85
|
if (!state.exists) {
|
|
85
86
|
return reply(`soly new: no .agents/ directory in cwd — run /soly init first.`);
|
|
@@ -88,16 +89,18 @@ export function buildNewTransform(
|
|
|
88
89
|
const parsed = parsePlanName(cmd.args.join(" "));
|
|
89
90
|
if ("error" in parsed) return reply(`soly new: ${parsed.error}`);
|
|
90
91
|
|
|
91
|
-
const { name } = parsed;
|
|
92
|
-
// Branch
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
92
|
+
const { name, prefix } = parsed;
|
|
93
|
+
// Branch can be one of three shapes:
|
|
94
|
+
// - "feature/statistic-preparation" (user typed <prefix>/<slug>)
|
|
95
|
+
// - "feature/statistic-preparation" (config has defaultBranchPrefix "feature", user typed "statistic-preparation")
|
|
96
|
+
// - "statistic-preparation" (no prefix anywhere)
|
|
97
|
+
// The plan dir is always flattened to keep the on-disk layout one-deep:
|
|
98
|
+
// `.agents/plans/<prefix>-<slug>` or `.agents/plans/<slug>`.
|
|
99
|
+
const effectivePrefix = prefix ?? defaultBranchPrefix;
|
|
100
|
+
const branchName = effectivePrefix ? `${effectivePrefix}/${name}` : name;
|
|
101
|
+
const dirSlug = effectivePrefix ? `${effectivePrefix}-${name}` : name;
|
|
102
|
+
const planDirRel = `.agents/plans/${dirSlug}`;
|
|
103
|
+
const planDirAbs = `${state.solyDir}/plans/${dirSlug}`;
|
|
101
104
|
const planFile = `${planDirAbs}/PLAN.md`;
|
|
102
105
|
|
|
103
106
|
// 1. Preconditions
|
package/workflows/parser.ts
CHANGED
|
@@ -37,17 +37,44 @@ export type WorkflowVerb =
|
|
|
37
37
|
* soly new login-redirect-bug
|
|
38
38
|
* soly new stats-rollup
|
|
39
39
|
*/
|
|
40
|
-
export function parsePlanName(
|
|
40
|
+
export function parsePlanName(
|
|
41
|
+
raw: string,
|
|
42
|
+
): { name: string; prefix: string | null } | { error: string } {
|
|
41
43
|
const trimmed = raw.trim();
|
|
42
44
|
if (!trimmed) return { error: "missing plan name" };
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
+
|
|
46
|
+
// Optional `<prefix>/<slug>` form: prefix is one kebab-case word
|
|
47
|
+
// (no internal slashes), slug is the same kebab-case shape. Examples:
|
|
48
|
+
// feature/statistic-preparation
|
|
49
|
+
// fix/login-redirect-bug
|
|
50
|
+
// chore/upgrade-deps
|
|
51
|
+
// Plan dirs flatten the slash: `.agents/plans/<prefix>-<slug>`.
|
|
52
|
+
const slashIdx = trimmed.indexOf("/");
|
|
53
|
+
if (slashIdx > 0) {
|
|
54
|
+
const prefix = trimmed.slice(0, slashIdx);
|
|
55
|
+
const slug = trimmed.slice(slashIdx + 1);
|
|
56
|
+
const prefixOk = /^[a-z][a-z0-9-]*[a-z0-9]$/.test(prefix);
|
|
57
|
+
const slugOk = /^[a-z][a-z0-9-]*[a-z0-9]$/.test(slug);
|
|
58
|
+
if (!prefixOk || !slugOk) {
|
|
59
|
+
return {
|
|
60
|
+
error:
|
|
61
|
+
`bad plan name "${trimmed}".\n` +
|
|
62
|
+
`\nExpected: <slug> OR <prefix>/<slug>\n` +
|
|
63
|
+
` prefix: one kebab-case word (e.g. "feature", "fix", "chore")\n` +
|
|
64
|
+
` slug: kebab-case (lowercase letters, digits, hyphens)\n` +
|
|
65
|
+
`\nExample: soly new feature/statistic-preparation`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { name: slug, prefix };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Plain slug form. Must contain at least one letter — pure-digit strings
|
|
72
|
+
// are reserved for phase numbers (`soly plan 11` → phase 11), not slugs.
|
|
45
73
|
if (!/[a-z]/.test(trimmed)) {
|
|
46
74
|
return {
|
|
47
75
|
error:
|
|
48
76
|
`bad plan name "${trimmed}".\n` +
|
|
49
|
-
`\nExpected: <slug>
|
|
50
|
-
` length: 2-64 chars\n` +
|
|
77
|
+
`\nExpected: <slug> OR <prefix>/<slug>\n` +
|
|
51
78
|
` chars: lowercase letters, digits, and hyphens\n` +
|
|
52
79
|
` must start and end with a letter or digit\n` +
|
|
53
80
|
`\nExample: soly new statistic-preparation`,
|
|
@@ -58,7 +85,7 @@ export function parsePlanName(raw: string): { name: string } | { error: string }
|
|
|
58
85
|
return {
|
|
59
86
|
error:
|
|
60
87
|
`bad plan name "${trimmed}".\n` +
|
|
61
|
-
`\nExpected: <slug>
|
|
88
|
+
`\nExpected: <slug> OR <prefix>/<slug>\n` +
|
|
62
89
|
` length: 2-64 chars\n` +
|
|
63
90
|
` chars: lowercase letters, digits, and hyphens\n` +
|
|
64
91
|
` must start and end with a letter or digit\n` +
|
|
@@ -68,7 +95,7 @@ export function parsePlanName(raw: string): { name: string } | { error: string }
|
|
|
68
95
|
if (trimmed.length > 64) {
|
|
69
96
|
return { error: `name "${trimmed}" is too long (max 64 chars)` };
|
|
70
97
|
}
|
|
71
|
-
return { name: trimmed };
|
|
98
|
+
return { name: trimmed, prefix: null };
|
|
72
99
|
}
|
|
73
100
|
|
|
74
101
|
export interface SolyCommand {
|
|
@@ -214,7 +241,7 @@ function parseNewTaskFlag(
|
|
|
214
241
|
*/
|
|
215
242
|
export type ExecuteTarget =
|
|
216
243
|
| { kind: "phase"; phase: number; plan: number | null; raw: string }
|
|
217
|
-
| { kind: "plan"; name: string; raw: string }
|
|
244
|
+
| { kind: "plan"; name: string; prefix: string | null; raw: string }
|
|
218
245
|
| { kind: "task"; taskId: string; raw: string }
|
|
219
246
|
| { kind: "all"; raw: string }
|
|
220
247
|
| { kind: "feature"; feature: string; raw: string };
|
|
@@ -260,7 +287,7 @@ export function describeExecuteTarget(args: string[]): ExecuteTarget | null {
|
|
|
260
287
|
|
|
261
288
|
const plan = parsePlanName(target);
|
|
262
289
|
if (!("error" in plan)) {
|
|
263
|
-
return { kind: "plan", name: plan.name, raw };
|
|
290
|
+
return { kind: "plan", name: plan.name, prefix: plan.prefix, raw };
|
|
264
291
|
}
|
|
265
292
|
|
|
266
293
|
const phase = parsePhaseShape(target);
|
|
@@ -286,7 +313,7 @@ export function describeExecuteTarget(args: string[]): ExecuteTarget | null {
|
|
|
286
313
|
*/
|
|
287
314
|
export type PlanTarget =
|
|
288
315
|
| { kind: "phase"; phase: number; raw: string }
|
|
289
|
-
| { kind: "plan"; name: string; raw: string }
|
|
316
|
+
| { kind: "plan"; name: string; prefix: string | null; raw: string }
|
|
290
317
|
| { kind: "task"; taskId: string; raw: string }
|
|
291
318
|
| { kind: "new-task"; slug: string; feature: string; raw: string }
|
|
292
319
|
| { kind: "feature"; feature: string; raw: string };
|
|
@@ -334,7 +361,7 @@ export function describePlanTarget(args: string[]): PlanTarget | null {
|
|
|
334
361
|
|
|
335
362
|
const plan = parsePlanName(target);
|
|
336
363
|
if (!("error" in plan)) {
|
|
337
|
-
return { kind: "plan", name: plan.name, raw };
|
|
364
|
+
return { kind: "plan", name: plan.name, prefix: plan.prefix, raw };
|
|
338
365
|
}
|
|
339
366
|
|
|
340
367
|
// Plan target only matches plain N (no .MM — plan is per-phase, executed
|
package/workflows/planning.ts
CHANGED
|
@@ -91,9 +91,13 @@ export function buildPlanTransform(cmd: SolyCommand, state: SolyState): Planning
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// === PLAN MODE (new dual-mode: `<type>/<name>` plans live under .agents/plans/<name>/) ===
|
|
94
|
+
// === PLAN MODE (new dual-mode: `<type>/<name>` plans live under .agents/plans/<prefix>-<name>/) ===
|
|
95
95
|
if (target.kind === "plan") {
|
|
96
|
-
|
|
96
|
+
// Plan dir is always flattened: `<prefix>-<name>` if a prefix is
|
|
97
|
+
// present (user typed `<prefix>/<slug>` or the project default
|
|
98
|
+
// applied), else just `<name>`.
|
|
99
|
+
const dirSlug = target.prefix ? `${target.prefix}-${target.name}` : target.name;
|
|
100
|
+
const planDirAbs = `${state.solyDir}/plans/${dirSlug}`;
|
|
97
101
|
const planFile = `${planDirAbs}/PLAN.md`;
|
|
98
102
|
let planBody: string;
|
|
99
103
|
try {
|
|
@@ -514,10 +518,11 @@ export function buildDiscussTransform(
|
|
|
514
518
|
};
|
|
515
519
|
}
|
|
516
520
|
|
|
517
|
-
// Plan mode: `<
|
|
521
|
+
// Plan mode: `<slug>` or `<prefix>/<slug>`
|
|
518
522
|
const planParsed = parsePlanName(arg);
|
|
519
523
|
if (!("error" in planParsed)) {
|
|
520
|
-
const
|
|
524
|
+
const dirSlug = planParsed.prefix ? `${planParsed.prefix}-${planParsed.name}` : planParsed.name;
|
|
525
|
+
const planDirAbs = `${state.solyDir}/plans/${dirSlug}`;
|
|
521
526
|
const planFile = `${planDirAbs}/PLAN.md`;
|
|
522
527
|
let planBody: string;
|
|
523
528
|
try {
|