@mjasnikovs/pi-task 0.2.1 → 0.2.3
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/README.md +29 -0
- package/dist/index.js +2 -0
- package/dist/shared/child-process.js +25 -4
- package/dist/task/auto-commit.d.ts +20 -0
- package/dist/task/auto-commit.js +56 -0
- package/dist/task/auto-io.d.ts +17 -0
- package/dist/task/auto-io.js +124 -0
- package/dist/task/auto-orchestrator.d.ts +28 -0
- package/dist/task/auto-orchestrator.js +318 -0
- package/dist/task/auto-prompts.d.ts +15 -0
- package/dist/task/auto-prompts.js +66 -0
- package/dist/task/inline-markdown.d.ts +18 -0
- package/dist/task/inline-markdown.js +28 -0
- package/dist/task/orchestrator.d.ts +28 -0
- package/dist/task/orchestrator.js +42 -9
- package/dist/task/parsers.d.ts +16 -0
- package/dist/task/parsers.js +70 -0
- package/dist/task/phases.d.ts +2 -1
- package/dist/task/phases.js +126 -100
- package/dist/task/prompts.d.ts +24 -1
- package/dist/task/prompts.js +40 -5
- package/dist/task/widget.d.ts +19 -0
- package/dist/task/widget.js +73 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,9 @@ pi install npm:@mjasnikovs/pi-task
|
|
|
61
61
|
| `/task-list` | Open the task list in an editor dialog. |
|
|
62
62
|
| `/task-resume [id]` | Resume the most recent (or named) unfinished task. |
|
|
63
63
|
| `/task-cancel` | Cancel the running task (soft-terminal — still resumable). |
|
|
64
|
+
| `/task-auto <feature>` | Plan a feature into a task list and run each title through `/task` in order (resumable). |
|
|
65
|
+
| `/task-auto-resume` | Resume the active `/task-auto` run at the next unfinished task. |
|
|
66
|
+
| `/task-auto-cancel` | Stop the `/task-auto` loop after the current task (still resumable). |
|
|
64
67
|
|
|
65
68
|
## The pipeline
|
|
66
69
|
|
|
@@ -74,6 +77,32 @@ pi install npm:@mjasnikovs/pi-task
|
|
|
74
77
|
|
|
75
78
|
The finished spec is delivered to your main `pi` conversation via `sendUserMessage`, so you keep working in the same chat — no context handoff, no copy-paste.
|
|
76
79
|
|
|
80
|
+
## Orchestrating multiple tasks — `/task-auto`
|
|
81
|
+
|
|
82
|
+
A real feature is usually several tasks, not one. `/task-auto` is a thin planner on top of the single-task pipeline:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
/task-auto add multi-tenant billing
|
|
86
|
+
│
|
|
87
|
+
▼
|
|
88
|
+
┌──────────┐ ┌──────────┐ ┌─────────────┐
|
|
89
|
+
│ clarify │──▶│ decompose│──▶│ TASK_AUTO_… │ resumable list of task titles
|
|
90
|
+
│ gray │ │ → titles │ │ .md (titles) │
|
|
91
|
+
│ areas │ └──────────┘ └──────┬───────┘
|
|
92
|
+
└──────────┘ │
|
|
93
|
+
┌────────────▼─────────────┐
|
|
94
|
+
│ for each unchecked title │
|
|
95
|
+
│ → full /task pipeline │ (spec + implement)
|
|
96
|
+
│ → wait until it finishes │
|
|
97
|
+
│ → check the box, next │
|
|
98
|
+
└────────────────────────────┘
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- **It only produces titles.** All the depth — refine, research, grill, compose, critique — is `/task`'s job, run fresh per title. `/task-auto` never researches or specs anything itself.
|
|
102
|
+
- **Clarify first.** It asks the few clarifying questions whose answers change how the feature splits, then decomposes the answers into an ordered list of task titles written to `.pi-tasks/TASK_AUTO_NNNN.md`.
|
|
103
|
+
- **Sequential, blocking.** Each title runs through `/task` to a spec, the spec is implemented, and the loop waits for that to finish before starting the next title. No overlap.
|
|
104
|
+
- **Crash- and cancel-safe.** Progress is the markdown checkboxes in the AUTO file. `/task-auto-resume` (no id) automatically picks up the active run at the first unchecked title. If a title's `/task` run fails, the loop stops and leaves the run resumable.
|
|
105
|
+
|
|
77
106
|
## Bundled tools
|
|
78
107
|
|
|
79
108
|
`pi-task` also registers four MCP-style worker tools (formerly `@mjasnikovs/pi-worker`). All are parallel-execution-capable, so the parent session can issue several calls in one turn.
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { registerTask } from './task/orchestrator.js';
|
|
2
|
+
import { registerTaskAuto } from './task/auto-orchestrator.js';
|
|
2
3
|
import { registerWorkers } from './workers/index.js';
|
|
3
4
|
export default function (pi) {
|
|
4
5
|
registerTask(pi);
|
|
6
|
+
registerTaskAuto(pi);
|
|
5
7
|
registerWorkers(pi);
|
|
6
8
|
}
|
|
@@ -27,6 +27,14 @@ export function runChild(spawn, invocation, cwd, signal, opts) {
|
|
|
27
27
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
28
28
|
});
|
|
29
29
|
let firstByteFired = false;
|
|
30
|
+
// json-events lines can split across data chunks; this holds the trailing
|
|
31
|
+
// partial line between chunks so events spanning a boundary still parse.
|
|
32
|
+
// In json-events mode we deliberately do NOT accumulate the full raw
|
|
33
|
+
// stream: a long-running child emits hundreds of MB of events and
|
|
34
|
+
// `stdout += chunk` would overflow V8's max string length (≈512MB),
|
|
35
|
+
// crashing the process. We only need the parsed text, so we drop the raw
|
|
36
|
+
// bytes once drained.
|
|
37
|
+
let lineBuffer = '';
|
|
30
38
|
proc.stdout?.on('data', (d) => {
|
|
31
39
|
if (!firstByteFired) {
|
|
32
40
|
firstByteFired = true;
|
|
@@ -35,15 +43,22 @@ export function runChild(spawn, invocation, cwd, signal, opts) {
|
|
|
35
43
|
if (discardStdout)
|
|
36
44
|
return;
|
|
37
45
|
const chunk = d.toString();
|
|
38
|
-
stdout += chunk;
|
|
39
46
|
if (isJsonEvents) {
|
|
40
|
-
drainJsonEvents(chunk, opts);
|
|
47
|
+
lineBuffer = drainJsonEvents(lineBuffer + chunk, opts);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
stdout += chunk;
|
|
41
51
|
}
|
|
42
52
|
});
|
|
43
53
|
proc.stderr?.on('data', (d) => {
|
|
44
54
|
stderr += d.toString();
|
|
45
55
|
});
|
|
46
56
|
proc.on('close', (code) => {
|
|
57
|
+
// Flush a final event that wasn't newline-terminated.
|
|
58
|
+
if (isJsonEvents && lineBuffer.trim().length > 0) {
|
|
59
|
+
drainJsonEvents(lineBuffer + '\n', opts);
|
|
60
|
+
lineBuffer = '';
|
|
61
|
+
}
|
|
47
62
|
const text = isJsonEvents ? (finalText || textDeltaAccum).trim() : undefined;
|
|
48
63
|
resolve({ stdout, stderr, exitCode: code ?? 0, aborted, text });
|
|
49
64
|
});
|
|
@@ -65,8 +80,13 @@ export function runChild(spawn, invocation, cwd, signal, opts) {
|
|
|
65
80
|
signal.addEventListener('abort', kill, { once: true });
|
|
66
81
|
}
|
|
67
82
|
// ─── JSON event-stream processing ──────────────────────────────────
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Parse every complete (newline-terminated) JSON line in `buf` and
|
|
85
|
+
* return the trailing partial line, which the caller carries into the
|
|
86
|
+
* next chunk. This avoids both losing events that span a chunk boundary
|
|
87
|
+
* and buffering the entire stream.
|
|
88
|
+
*/
|
|
89
|
+
function drainJsonEvents(buf, jsonOpts) {
|
|
70
90
|
let nl;
|
|
71
91
|
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
72
92
|
const line = buf.slice(0, nl).trim();
|
|
@@ -83,6 +103,7 @@ export function runChild(spawn, invocation, cwd, signal, opts) {
|
|
|
83
103
|
// Non-JSON line (startup banner, etc.) — ignore.
|
|
84
104
|
}
|
|
85
105
|
}
|
|
106
|
+
return buf;
|
|
86
107
|
}
|
|
87
108
|
function handleEvent(evt, jsonOpts) {
|
|
88
109
|
const t = typeof evt.type === 'string' ? evt.type : '';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task git commit for /task-auto.
|
|
3
|
+
*
|
|
4
|
+
* After each decomposed task passes, runAutoLoop snapshots the working tree into
|
|
5
|
+
* a single commit so the run produces one commit per task. This is best-effort:
|
|
6
|
+
* outside a git repo, with nothing staged, or on any git error we report the
|
|
7
|
+
* reason and let the loop continue (the task already succeeded).
|
|
8
|
+
*/
|
|
9
|
+
import { type SpawnFn } from '../shared/child-process.js';
|
|
10
|
+
export interface CommitResult {
|
|
11
|
+
committed: boolean;
|
|
12
|
+
/** Short, human-readable reason when committed === false. */
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Stage everything (`git add -A`) and commit it with `message`. Honors
|
|
17
|
+
* .gitignore via git itself. Never throws — failures surface as
|
|
18
|
+
* `{committed: false, reason}` so the caller can warn and keep going.
|
|
19
|
+
*/
|
|
20
|
+
export declare function gitCommitAll(cwd: string, message: string, signal?: AbortSignal, spawnFn?: SpawnFn): Promise<CommitResult>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task git commit for /task-auto.
|
|
3
|
+
*
|
|
4
|
+
* After each decomposed task passes, runAutoLoop snapshots the working tree into
|
|
5
|
+
* a single commit so the run produces one commit per task. This is best-effort:
|
|
6
|
+
* outside a git repo, with nothing staged, or on any git error we report the
|
|
7
|
+
* reason and let the loop continue (the task already succeeded).
|
|
8
|
+
*/
|
|
9
|
+
import { runChildDefault } from '../shared/child-process.js';
|
|
10
|
+
function firstLine(s) {
|
|
11
|
+
const line = s.split('\n').find(l => l.trim().length > 0);
|
|
12
|
+
return (line ?? s).trim();
|
|
13
|
+
}
|
|
14
|
+
async function git(cwd, args, signal, spawnFn) {
|
|
15
|
+
const r = await runChildDefault({ command: 'git', args }, cwd, signal, { mode: 'text' }, spawnFn);
|
|
16
|
+
return { stdout: r.stdout, stderr: r.stderr, exitCode: r.exitCode, aborted: r.aborted };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Stage everything (`git add -A`) and commit it with `message`. Honors
|
|
20
|
+
* .gitignore via git itself. Never throws — failures surface as
|
|
21
|
+
* `{committed: false, reason}` so the caller can warn and keep going.
|
|
22
|
+
*/
|
|
23
|
+
export async function gitCommitAll(cwd, message, signal, spawnFn) {
|
|
24
|
+
// 1. Is this a git work tree at all?
|
|
25
|
+
const inside = await git(cwd, ['rev-parse', '--is-inside-work-tree'], signal, spawnFn);
|
|
26
|
+
if (inside.aborted)
|
|
27
|
+
return { committed: false, reason: 'cancelled' };
|
|
28
|
+
if (inside.exitCode !== 0 || inside.stdout.trim() !== 'true') {
|
|
29
|
+
return { committed: false, reason: 'not a git repository' };
|
|
30
|
+
}
|
|
31
|
+
// 2. Stage all working-tree changes (new, modified, deleted).
|
|
32
|
+
const add = await git(cwd, ['add', '-A'], signal, spawnFn);
|
|
33
|
+
if (add.aborted)
|
|
34
|
+
return { committed: false, reason: 'cancelled' };
|
|
35
|
+
if (add.exitCode !== 0) {
|
|
36
|
+
return { committed: false, reason: `git add failed: ${firstLine(add.stderr)}` };
|
|
37
|
+
}
|
|
38
|
+
// 3. Anything staged? `git diff --cached --quiet` exits 0 when the index
|
|
39
|
+
// matches HEAD (nothing to commit), 1 when there are staged changes.
|
|
40
|
+
const diff = await git(cwd, ['diff', '--cached', '--quiet'], signal, spawnFn);
|
|
41
|
+
if (diff.aborted)
|
|
42
|
+
return { committed: false, reason: 'cancelled' };
|
|
43
|
+
if (diff.exitCode === 0)
|
|
44
|
+
return { committed: false, reason: 'nothing to commit' };
|
|
45
|
+
// 4. Commit. A failure here is usually missing user.name/user.email config.
|
|
46
|
+
const commit = await git(cwd, ['commit', '-m', message], signal, spawnFn);
|
|
47
|
+
if (commit.aborted)
|
|
48
|
+
return { committed: false, reason: 'cancelled' };
|
|
49
|
+
if (commit.exitCode !== 0) {
|
|
50
|
+
return {
|
|
51
|
+
committed: false,
|
|
52
|
+
reason: `git commit failed: ${firstLine(commit.stderr || commit.stdout)}`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { committed: true };
|
|
56
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TaskEntry {
|
|
2
|
+
index: number;
|
|
3
|
+
title: string;
|
|
4
|
+
done: boolean;
|
|
5
|
+
producedId?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function allocateAutoId(cwd: string): Promise<string>;
|
|
8
|
+
/** Parse a decompose-phase model output into a clean list of titles. */
|
|
9
|
+
export declare function parseDecomposeList(raw: string): string[];
|
|
10
|
+
/** Parse the "## tasks" checkbox list. */
|
|
11
|
+
export declare function parseTaskList(body: string): TaskEntry[];
|
|
12
|
+
/** Build the initial AUTO-file body. */
|
|
13
|
+
export declare function buildAutoBody(feature: string, clarifications: string, titles: string[]): string;
|
|
14
|
+
/** Check off the Nth checkbox line, stamping the produced TASK_NNNN id. */
|
|
15
|
+
export declare function checkOffTask(cwd: string, id: string, index: number, producedId: string, title: string): Promise<void>;
|
|
16
|
+
/** Find the most-recently-updated resumable TASK_AUTO_* file, or null. */
|
|
17
|
+
export declare function findResumableAuto(cwd: string): Promise<string | null>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-file I/O & parsing for /task-auto.
|
|
3
|
+
*
|
|
4
|
+
* Thin layer over task-io/task-parsers: a TASK_AUTO_NNNN.md is a normal task
|
|
5
|
+
* file (same front matter) whose body holds feature prompt, clarifications, and
|
|
6
|
+
* a markdown checkbox list of task titles. The checkboxes are the resume cursor.
|
|
7
|
+
*/
|
|
8
|
+
import * as fsp from 'node:fs/promises';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { tasksDir, ensureTasksDir, readTaskFile, setTaskSection } from './task-io.js';
|
|
11
|
+
import { extractSection, parseFrontMatter } from './task-parsers.js';
|
|
12
|
+
import { RESUMABLE_STATES } from './task-types.js';
|
|
13
|
+
const AUTO_FILE_RE = /^(TASK_AUTO_\d{4,})\.md$/;
|
|
14
|
+
const MAX_TASKS = 30;
|
|
15
|
+
export async function allocateAutoId(cwd) {
|
|
16
|
+
await ensureTasksDir(cwd);
|
|
17
|
+
const entries = await fsp.readdir(tasksDir(cwd));
|
|
18
|
+
let max = 0;
|
|
19
|
+
for (const e of entries) {
|
|
20
|
+
const m = AUTO_FILE_RE.exec(e);
|
|
21
|
+
if (m) {
|
|
22
|
+
const n = parseInt(m[1].slice('TASK_AUTO_'.length), 10);
|
|
23
|
+
if (n > max)
|
|
24
|
+
max = n;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return `TASK_AUTO_${String(max + 1).padStart(4, '0')}`;
|
|
28
|
+
}
|
|
29
|
+
/** Parse a decompose-phase model output into a clean list of titles. */
|
|
30
|
+
export function parseDecomposeList(raw) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const line of raw.split('\n')) {
|
|
33
|
+
const m = /^\s*(?:-\s*\[\s*[xX ]?\s*\]\s*|-\s+|\d+[.)]\s+)(.+?)\s*$/.exec(line);
|
|
34
|
+
if (m && m[1].trim().length > 0)
|
|
35
|
+
out.push(m[1].trim());
|
|
36
|
+
if (out.length >= MAX_TASKS)
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
const CHECKBOX_RE = /^- \[([ xX])\]\s+(.+?)\s*$/;
|
|
42
|
+
const PRODUCED_ID_RE = /^(TASK_\d{4,})\s{2,}(.+)$/;
|
|
43
|
+
/** Parse the "## tasks" checkbox list. */
|
|
44
|
+
export function parseTaskList(body) {
|
|
45
|
+
const section = extractSection(body, 'tasks');
|
|
46
|
+
if (section === null)
|
|
47
|
+
return [];
|
|
48
|
+
const entries = [];
|
|
49
|
+
let index = 0;
|
|
50
|
+
for (const line of section.split('\n')) {
|
|
51
|
+
const m = CHECKBOX_RE.exec(line.trim());
|
|
52
|
+
if (!m)
|
|
53
|
+
continue;
|
|
54
|
+
const done = m[1].toLowerCase() === 'x';
|
|
55
|
+
const rest = m[2].trim();
|
|
56
|
+
if (done) {
|
|
57
|
+
const idm = PRODUCED_ID_RE.exec(rest);
|
|
58
|
+
if (idm) {
|
|
59
|
+
entries.push({ index, title: idm[2].trim(), done: true, producedId: idm[1] });
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
entries.push({ index, title: rest, done: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
entries.push({ index, title: rest, done: false });
|
|
67
|
+
}
|
|
68
|
+
index++;
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
}
|
|
72
|
+
/** Build the initial AUTO-file body. */
|
|
73
|
+
export function buildAutoBody(feature, clarifications, titles) {
|
|
74
|
+
const tasks = titles.map(t => `- [ ] ${t}`).join('\n');
|
|
75
|
+
return (`\n## feature prompt\n\n${feature.trim() || '(none)'}\n\n`
|
|
76
|
+
+ `## clarifications\n\n${clarifications.trim() || '(none)'}\n\n`
|
|
77
|
+
+ `## tasks\n\n${tasks}\n`);
|
|
78
|
+
}
|
|
79
|
+
/** Check off the Nth checkbox line, stamping the produced TASK_NNNN id. */
|
|
80
|
+
export async function checkOffTask(cwd, id, index, producedId, title) {
|
|
81
|
+
const { body } = await readTaskFile(cwd, id);
|
|
82
|
+
const section = extractSection(body, 'tasks') ?? '';
|
|
83
|
+
const lines = section.split('\n');
|
|
84
|
+
let seen = -1;
|
|
85
|
+
for (let i = 0; i < lines.length; i++) {
|
|
86
|
+
if (!CHECKBOX_RE.test(lines[i].trim()))
|
|
87
|
+
continue;
|
|
88
|
+
seen++;
|
|
89
|
+
if (seen === index) {
|
|
90
|
+
lines[i] = producedId ? `- [x] ${producedId} ${title}` : `- [x] ${title}`;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (seen < index) {
|
|
95
|
+
throw new Error(`checkOffTask: index ${index} out of range in ${id} (only ${seen + 1} checkboxes found)`);
|
|
96
|
+
}
|
|
97
|
+
await setTaskSection(cwd, id, 'tasks', lines.join('\n'));
|
|
98
|
+
}
|
|
99
|
+
/** Find the most-recently-updated resumable TASK_AUTO_* file, or null. */
|
|
100
|
+
export async function findResumableAuto(cwd) {
|
|
101
|
+
await ensureTasksDir(cwd);
|
|
102
|
+
const entries = await fsp.readdir(tasksDir(cwd));
|
|
103
|
+
const candidates = [];
|
|
104
|
+
for (const f of entries) {
|
|
105
|
+
const m = AUTO_FILE_RE.exec(f);
|
|
106
|
+
if (!m)
|
|
107
|
+
continue;
|
|
108
|
+
try {
|
|
109
|
+
const raw = await fsp.readFile(path.join(tasksDir(cwd), f), 'utf8');
|
|
110
|
+
const fm = parseFrontMatter(raw);
|
|
111
|
+
if (!fm)
|
|
112
|
+
continue;
|
|
113
|
+
if (!RESUMABLE_STATES.includes(fm.state))
|
|
114
|
+
continue;
|
|
115
|
+
const st = await fsp.stat(path.join(tasksDir(cwd), f));
|
|
116
|
+
candidates.push({ id: m[1], mtime: st.mtimeMs });
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
/* skip unreadable */
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
candidates.sort((a, b) => b.mtime - a.mtime);
|
|
123
|
+
return candidates.length > 0 ? candidates[0].id : null;
|
|
124
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { RunSingleTaskResult } from './orchestrator.js';
|
|
3
|
+
import { type CommitResult } from './auto-commit.js';
|
|
4
|
+
/**
|
|
5
|
+
* Injectable seams so the planner and loop are testable without spawning pi.
|
|
6
|
+
* `runChild` is used by planAuto; `runTask` is used by runAutoLoop.
|
|
7
|
+
*/
|
|
8
|
+
export interface AutoDeps {
|
|
9
|
+
runChild: (name: string, tools: string, prompt: string) => Promise<string>;
|
|
10
|
+
runTask: (ctx: ExtensionCommandContext, cwd: string, title: string) => Promise<RunSingleTaskResult>;
|
|
11
|
+
/** Snapshot the working tree into one commit after a task passes. */
|
|
12
|
+
commit: (cwd: string, message: string) => Promise<CommitResult>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Expand any @file references in the feature text by appending each referenced
|
|
16
|
+
* file's contents, so the planning children (clarify, decompose) always see the
|
|
17
|
+
* real spec inline instead of relying on the model to open the file itself.
|
|
18
|
+
* Without this, clarify on a one-line "Implement @spec.md" tends to bail with
|
|
19
|
+
* NONE because, to the model, the request looks small and unambiguous.
|
|
20
|
+
* Unreadable mentions (typos, non-file @tokens) are left untouched; the feature
|
|
21
|
+
* is returned verbatim when nothing readable is referenced.
|
|
22
|
+
*/
|
|
23
|
+
export declare function expandFeatureMentions(cwd: string, feature: string): Promise<string>;
|
|
24
|
+
/** Plan phase: clarify → decompose → write AUTO file. Returns the new id, or null. */
|
|
25
|
+
export declare function planAuto(ctx: ExtensionCommandContext, cwd: string, feature: string, deps: AutoDeps): Promise<string | null>;
|
|
26
|
+
export declare function requestAutoCancel(): void;
|
|
27
|
+
export declare function runAutoLoop(ctx: ExtensionCommandContext, cwd: string, id: string, deps: AutoDeps): Promise<void>;
|
|
28
|
+
export declare function registerTaskAuto(pi: ExtensionAPI): void;
|