@mjasnikovs/pi-task 0.29.3 → 0.31.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.
@@ -143,7 +143,7 @@ export function startWidget(ctx, getState) {
143
143
  }
144
144
  export function buildAutoLoaderLines(s, theme) {
145
145
  const elapsed = formatDuration(Date.now() - s.startedAt);
146
- const head = `/task-auto · ${s.title}`;
146
+ const head = `${s.command ?? '/task-auto'} · ${s.title}`;
147
147
  let detail = s.kind === 'enforce' ? `enforcing guidelines · ${elapsed}`
148
148
  : s.kind === 'verify' ? `verifying work · ${elapsed}`
149
149
  : s.kind === 'recommend' ? `assessing the failure · ${elapsed}`
@@ -171,7 +171,7 @@ export function buildAutoLoaderData(s) {
171
171
  : s.kind === 'final-fix' ? 'fixing the final gate'
172
172
  : s.step;
173
173
  const d = {
174
- title: `/task-auto · ${s.title}`,
174
+ title: `${s.command ?? '/task-auto'} · ${s.title}`,
175
175
  phase,
176
176
  elapsed: formatDuration(Date.now() - s.startedAt)
177
177
  };
@@ -54,6 +54,48 @@ export declare function parseNameStatusChanges(nameStatus: string): TreeChangeSu
54
54
  * destroyed a sibling task's verified deliverable.
55
55
  */
56
56
  export declare function findForbiddenDeletions(changes: TreeChangeSummary): string[];
57
+ /** Why an ignored path is (or is not) something the gate may rule on. */
58
+ export type IgnoredClass = 'build-output' | 'dep-dir' | 'task-dir' | 'vcs-meta' | 'actionable';
59
+ /**
60
+ * Classify one repo-relative ignored path. `outdirs` are build output directories
61
+ * parsed from the project's own build commands. Only `actionable` may produce a
62
+ * finding — everything else is reproducible from the repository by running the
63
+ * project's own tooling, which is exactly what makes it not a gate concern.
64
+ */
65
+ export declare function classifyIgnoredPath(rel: string, outdirs: string[]): IgnoredClass;
66
+ /** The ignored paths a gate may rule on: everything not exempt by mechanism. */
67
+ export declare function findActionableIgnoredWrites(paths: string[], outdirs: string[]): string[];
68
+ /**
69
+ * A fingerprint per ignored path (`mtimeMs:size`, or a marker for a directory),
70
+ * taken before and after a write-capable child so a write is ATTRIBUTED to that
71
+ * child rather than to the tree's pre-existing state. Ignored files are untracked,
72
+ * so git cannot tell "changed" from "was always there" — only the snapshot can.
73
+ */
74
+ export type IgnoredSnapshot = Record<string, string>;
75
+ /**
76
+ * Paths whose fingerprint appeared or changed across the child's window. Pure, so
77
+ * the attribution rule is unit-testable without a repo.
78
+ */
79
+ export declare function diffIgnoredSnapshots(before: IgnoredSnapshot, after: IgnoredSnapshot): string[];
80
+ /**
81
+ * The gate-trail line. PATH NAMES ONLY: the contents of an ignored file are never
82
+ * read into a log, a debt reason or a child prompt (`.env` is the canonical case —
83
+ * this channel exists because of a file full of credentials).
84
+ */
85
+ export declare function ignoredWriteTrailLine(paths: string[]): string;
86
+ /**
87
+ * The durable debt reason for the same event. Path names only, same rule.
88
+ *
89
+ * The wording tracks what was actually PROVEN. `dependent === true` is the probe's
90
+ * answer that the gate does not pass without these files; `undefined` is an
91
+ * unanswered probe (the attempt never converged, or the probe could not run), which
92
+ * is still worth carrying because the file is on disk and can green a LATER attempt.
93
+ * A debt that overstates its evidence is the same defect this whole channel exists
94
+ * to fix, one level up.
95
+ */
96
+ export declare function ignoredWriteDebtReason(paths: string[], dependent?: boolean): string;
97
+ /** The UNOBSERVED note that replaces such a PASS. Path names only, same rule. */
98
+ export declare function ignoredWriteUnobservedNote(paths: string[]): string;
57
99
  /**
58
100
  * One-line summary for the gate debug log — the diff capture every write-capable
59
101
  * child gets so "what did this pass change" is answerable from artifacts (the
@@ -132,6 +132,114 @@ export function findForbiddenDeletions(changes) {
132
132
  const addedNames = new Set(changes.added.map(basename));
133
133
  return changes.deleted.filter(p => !addedNames.has(basename(p)));
134
134
  }
135
+ /**
136
+ * Directory names that are build output or a dependency tree by convention. The
137
+ * parsed outdirs (see classifyIgnoredPath's `outdirs`) come from the project's
138
+ * own tooling and are the primary mechanism; this list is the fallback for
139
+ * projects whose build is not declared in a package.json (CMake, cargo, gradle).
140
+ */
141
+ const BUILD_DIR_NAMES = new Set([
142
+ 'node_modules',
143
+ 'dist',
144
+ 'build',
145
+ 'target',
146
+ 'out',
147
+ 'coverage',
148
+ '.next',
149
+ '.nuxt',
150
+ '.svelte-kit',
151
+ '.turbo',
152
+ '.cache',
153
+ '.parcel-cache',
154
+ '.vite',
155
+ '.gradle',
156
+ '__pycache__',
157
+ '.pytest_cache',
158
+ '.venv',
159
+ 'venv'
160
+ ]);
161
+ /**
162
+ * Classify one repo-relative ignored path. `outdirs` are build output directories
163
+ * parsed from the project's own build commands. Only `actionable` may produce a
164
+ * finding — everything else is reproducible from the repository by running the
165
+ * project's own tooling, which is exactly what makes it not a gate concern.
166
+ */
167
+ export function classifyIgnoredPath(rel, outdirs) {
168
+ const segs = rel
169
+ .replace(/^\.\//, '')
170
+ .split('/')
171
+ .filter(s => s.length > 0);
172
+ if (segs.length === 0)
173
+ return 'actionable';
174
+ if (segs.includes('.pi-tasks'))
175
+ return 'task-dir';
176
+ if (segs.includes('.git'))
177
+ return 'vcs-meta';
178
+ if (segs.includes('node_modules'))
179
+ return 'dep-dir';
180
+ for (const o of outdirs) {
181
+ const oSegs = o
182
+ .replace(/^\.\//, '')
183
+ .split('/')
184
+ .filter(s => s.length > 0);
185
+ if (oSegs.length > 0 && oSegs.every((s, i) => segs[i] === s))
186
+ return 'build-output';
187
+ }
188
+ if (segs.some(s => BUILD_DIR_NAMES.has(s)))
189
+ return 'build-output';
190
+ return 'actionable';
191
+ }
192
+ /** The ignored paths a gate may rule on: everything not exempt by mechanism. */
193
+ export function findActionableIgnoredWrites(paths, outdirs) {
194
+ return paths.filter(p => classifyIgnoredPath(p, outdirs) === 'actionable');
195
+ }
196
+ /**
197
+ * Paths whose fingerprint appeared or changed across the child's window. Pure, so
198
+ * the attribution rule is unit-testable without a repo.
199
+ */
200
+ export function diffIgnoredSnapshots(before, after) {
201
+ const out = [];
202
+ for (const [p, fp] of Object.entries(after)) {
203
+ if (before[p] !== fp)
204
+ out.push(p);
205
+ }
206
+ return out.sort();
207
+ }
208
+ /**
209
+ * The gate-trail line. PATH NAMES ONLY: the contents of an ignored file are never
210
+ * read into a log, a debt reason or a child prompt (`.env` is the canonical case —
211
+ * this channel exists because of a file full of credentials).
212
+ */
213
+ export function ignoredWriteTrailLine(paths) {
214
+ return (`final-gate: fix pass modified IGNORED path(s) — ${paths.join(', ')}; `
215
+ + 'these are NOT committed and NOT reproducible from the repository');
216
+ }
217
+ /**
218
+ * The durable debt reason for the same event. Path names only, same rule.
219
+ *
220
+ * The wording tracks what was actually PROVEN. `dependent === true` is the probe's
221
+ * answer that the gate does not pass without these files; `undefined` is an
222
+ * unanswered probe (the attempt never converged, or the probe could not run), which
223
+ * is still worth carrying because the file is on disk and can green a LATER attempt.
224
+ * A debt that overstates its evidence is the same defect this whole channel exists
225
+ * to fix, one level up.
226
+ */
227
+ export function ignoredWriteDebtReason(paths, dependent) {
228
+ const head = dependent === true ?
229
+ `final-gate PASS depended on gitignored file(s) the run wrote and cannot ship: ${paths.join(', ')}. `
230
+ + 'A fresh clone does NOT have them, so the checks that passed here cannot be reproduced. '
231
+ : `the final-gate fix pass wrote gitignored file(s) that are NOT in the commit: ${paths.join(', ')}. `
232
+ + 'Whether the gate needs them was not established, so a fresh clone may not reproduce this run. ';
233
+ return (head
234
+ + 'The durable fix is a TRACKED counterpart (e.g. .env.example) or a check that '
235
+ + 'does not need the file — never committing the ignored file itself.');
236
+ }
237
+ /** The UNOBSERVED note that replaces such a PASS. Path names only, same rule. */
238
+ export function ignoredWriteUnobservedNote(paths) {
239
+ return (`UNOBSERVED — NOT a pass: the gate's checks passed only with gitignored file(s) `
240
+ + `this run wrote (${paths.join(', ')}), which are not in the commit; re-running `
241
+ + 'them without those files FAILS, so no reproducible evidence was produced.');
242
+ }
135
243
  /**
136
244
  * One-line summary for the gate debug log — the diff capture every write-capable
137
245
  * child gets so "what did this pass change" is answerable from artifacts (the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.29.3",
3
+ "version": "0.31.0",
4
4
  "description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",