@mjasnikovs/pi-task 0.16.4 → 0.16.5
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.
|
@@ -11,6 +11,7 @@ import { runSingleTask } from './orchestrator.js';
|
|
|
11
11
|
import { parseClarifyList, deriveTitle } from './parsers.js';
|
|
12
12
|
import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js';
|
|
13
13
|
import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT } from './auto-prompts.js';
|
|
14
|
+
import { getProjectSnapshot } from './file-inventory.js';
|
|
14
15
|
import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './question-dedup.js';
|
|
15
16
|
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
16
17
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
@@ -212,6 +213,13 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
212
213
|
if (planPhantoms.length > 0) {
|
|
213
214
|
logPlanDebug(cwd, `phantom specifiers rewritten in plan spec: ${planPhantoms.map(x => x.spec).join(', ')}`);
|
|
214
215
|
}
|
|
216
|
+
// Factual snapshot of the target directory (root files + top-level dirs).
|
|
217
|
+
// Clarify only "may" read the repo; a model that skips the read assumes a
|
|
218
|
+
// greenfield project and recommends bootstrapping config/manifest files that
|
|
219
|
+
// already exist (or invents a path like /workspace). Handing it the facts
|
|
220
|
+
// up front removes the guess. '' for a non-git/empty tree ⇒ prompt omits the
|
|
221
|
+
// block and the model correctly treats the target as greenfield.
|
|
222
|
+
const projectState = await getProjectSnapshot(cwd);
|
|
215
223
|
const answers = [];
|
|
216
224
|
// Plain text of every question already shown, for the duplicate backstop.
|
|
217
225
|
const askedQuestions = [];
|
|
@@ -224,7 +232,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
224
232
|
// Open-ended: keep asking until the model emits NONE or the user dismisses —
|
|
225
233
|
// but never past MAX_CLARIFY_QUESTIONS distinct questions.
|
|
226
234
|
while (askedQuestions.length < MAX_CLARIFY_QUESTIONS) {
|
|
227
|
-
const qRaw = await deps.runChild('auto-clarify', 'read', prependHint(dupHint, AUTO_CLARIFY_PROMPT(featureForModel, answers.join('\n'))));
|
|
235
|
+
const qRaw = await deps.runChild('auto-clarify', 'read', prependHint(dupHint, AUTO_CLARIFY_PROMPT(featureForModel, answers.join('\n'), projectState)));
|
|
228
236
|
const parsed = parseClarifyList(qRaw);
|
|
229
237
|
if (parsed.length === 0)
|
|
230
238
|
break; // NONE / nothing left to ask
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* when no clarification remains. priorQA carries the questions already answered so
|
|
10
10
|
* each next question adapts to them.
|
|
11
11
|
*/
|
|
12
|
-
export declare const AUTO_CLARIFY_PROMPT: (feature: string, priorQA: string) => string;
|
|
12
|
+
export declare const AUTO_CLARIFY_PROMPT: (feature: string, priorQA: string, projectState?: string) => string;
|
|
13
13
|
/**
|
|
14
14
|
* Decompose: output a markdown checkbox list of task titles (one line each).
|
|
15
15
|
*/
|
|
@@ -9,16 +9,19 @@
|
|
|
9
9
|
* when no clarification remains. priorQA carries the questions already answered so
|
|
10
10
|
* each next question adapts to them.
|
|
11
11
|
*/
|
|
12
|
-
export const AUTO_CLARIFY_PROMPT = (feature, priorQA) => `You are planning how to split a feature into separate implementation tasks, one clarifying question at a time.
|
|
12
|
+
export const AUTO_CLARIFY_PROMPT = (feature, priorQA, projectState = '') => `You are planning how to split a feature into separate implementation tasks, one clarifying question at a time.
|
|
13
13
|
|
|
14
14
|
FEATURE REQUEST:
|
|
15
15
|
${feature.trim()}
|
|
16
16
|
|
|
17
17
|
ANSWERS SO FAR:
|
|
18
18
|
${priorQA.trim() || '(none yet)'}
|
|
19
|
-
|
|
19
|
+
${projectState.trim() ? `\n${projectState.trim()}\n` : ''}
|
|
20
20
|
You may use the read tool to inspect the repo and any referenced docs so your
|
|
21
|
-
question and recommendation are grounded in what already exists.
|
|
21
|
+
question and recommendation are grounded in what already exists. When a PROJECT
|
|
22
|
+
STATE snapshot is given above, trust it: never assume an empty or greenfield
|
|
23
|
+
project, never invent directory paths, and never recommend creating files it
|
|
24
|
+
already lists — at most propose updating them.
|
|
22
25
|
|
|
23
26
|
Output the SINGLE most important clarifying question that REMAINS — the one whose
|
|
24
27
|
answer would most change HOW this feature is split into tasks (scope boundaries,
|
|
@@ -17,3 +17,19 @@ export declare function stripTasksDir(raw: string): string;
|
|
|
17
17
|
/** Cap output to maxLines real (non-blank) paths; tag truncation when cut. */
|
|
18
18
|
export declare function capInventory(raw: string, maxLines?: number): string;
|
|
19
19
|
export declare function getFileInventory(cwd: string, signal?: AbortSignal, maxLines?: number): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Derive a compact PROJECT STATE block (root-level files + top-level directories)
|
|
22
|
+
* from a raw `git ls-files` listing. Pure/testable; `getProjectSnapshot` wires it
|
|
23
|
+
* to the live repo. Returns '' when there is nothing tracked so the caller omits
|
|
24
|
+
* the block entirely (and the model is then correctly free to treat the target as
|
|
25
|
+
* an empty/greenfield project).
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatProjectSnapshot(raw: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Live PROJECT STATE snapshot for the planning phase. Clarify otherwise only
|
|
30
|
+
* "may" read the repo; a weak model that skips the read then assumes a greenfield
|
|
31
|
+
* project and recommends creating package.json/tsconfig from scratch — or invents
|
|
32
|
+
* a path like /workspace — even when those configs already exist on disk. Handing
|
|
33
|
+
* it the facts up front removes the guess. Empty (non-git / nothing tracked) ⇒ ''.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getProjectSnapshot(cwd: string, signal?: AbortSignal): Promise<string>;
|
|
@@ -59,3 +59,40 @@ export async function getFileInventory(cwd, signal, maxLines = DEFAULT_MAX_LINES
|
|
|
59
59
|
return '';
|
|
60
60
|
return capInventory(stripTasksDir(raw), maxLines);
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Derive a compact PROJECT STATE block (root-level files + top-level directories)
|
|
64
|
+
* from a raw `git ls-files` listing. Pure/testable; `getProjectSnapshot` wires it
|
|
65
|
+
* to the live repo. Returns '' when there is nothing tracked so the caller omits
|
|
66
|
+
* the block entirely (and the model is then correctly free to treat the target as
|
|
67
|
+
* an empty/greenfield project).
|
|
68
|
+
*/
|
|
69
|
+
export function formatProjectSnapshot(raw) {
|
|
70
|
+
const lines = stripTasksDir(raw)
|
|
71
|
+
.split('\n')
|
|
72
|
+
.map(l => l.trim())
|
|
73
|
+
.filter(l => l.length > 0);
|
|
74
|
+
const rootFiles = [...new Set(lines.filter(l => !l.includes('/')))].sort();
|
|
75
|
+
const dirs = [...new Set(lines.filter(l => l.includes('/')).map(l => l.split('/')[0]))].sort();
|
|
76
|
+
if (rootFiles.length === 0 && dirs.length === 0)
|
|
77
|
+
return '';
|
|
78
|
+
return ('EXISTING PROJECT STATE (a factual snapshot of the target directory — trust it over '
|
|
79
|
+
+ 'assumptions: do NOT assume an empty or greenfield project, do NOT invent directory '
|
|
80
|
+
+ 'paths, and do NOT propose creating files that already exist. Treat the config/manifest '
|
|
81
|
+
+ "files listed here as the project's established setup unless the request explicitly says "
|
|
82
|
+
+ 'to replace them):\n'
|
|
83
|
+
+ `Root files: ${rootFiles.join(', ') || '(none)'}\n`
|
|
84
|
+
+ `Top-level directories: ${dirs.join(', ') || '(none)'}`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Live PROJECT STATE snapshot for the planning phase. Clarify otherwise only
|
|
88
|
+
* "may" read the repo; a weak model that skips the read then assumes a greenfield
|
|
89
|
+
* project and recommends creating package.json/tsconfig from scratch — or invents
|
|
90
|
+
* a path like /workspace — even when those configs already exist on disk. Handing
|
|
91
|
+
* it the facts up front removes the guess. Empty (non-git / nothing tracked) ⇒ ''.
|
|
92
|
+
*/
|
|
93
|
+
export async function getProjectSnapshot(cwd, signal) {
|
|
94
|
+
const raw = await runGitLsFiles(cwd, signal);
|
|
95
|
+
if (!raw)
|
|
96
|
+
return '';
|
|
97
|
+
return formatProjectSnapshot(raw);
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|