@mjasnikovs/pi-task 0.40.50 → 0.41.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/README.md +7 -4
- package/dist/config/config.d.ts +61 -18
- package/dist/config/config.js +50 -6
- package/dist/config/register.js +18 -5
- package/dist/shared/content-tokens.d.ts +16 -0
- package/dist/shared/content-tokens.js +137 -0
- package/dist/task/accept-debt.d.ts +15 -1
- package/dist/task/accept-debt.js +4 -0
- package/dist/task/artifact-closure.js +7 -1
- package/dist/task/auto-io.d.ts +59 -7
- package/dist/task/auto-io.js +140 -47
- package/dist/task/auto-orchestrator.d.ts +22 -19
- package/dist/task/auto-orchestrator.js +126 -112
- package/dist/task/child-runner.d.ts +18 -0
- package/dist/task/child-runner.js +7 -8
- package/dist/task/command-run.d.ts +4 -0
- package/dist/task/command-run.js +4 -2
- package/dist/task/constraint-policy.d.ts +52 -0
- package/dist/task/constraint-policy.js +66 -0
- package/dist/task/context-silence.d.ts +5 -4
- package/dist/task/context-silence.js +13 -5
- package/dist/task/coverage-loop.d.ts +7 -4
- package/dist/task/coverage-loop.js +16 -138
- package/dist/task/debug-log.d.ts +25 -2
- package/dist/task/debug-log.js +64 -10
- package/dist/task/decompose-fidelity.d.ts +17 -4
- package/dist/task/decompose-fidelity.js +95 -55
- package/dist/task/decompose-granularity.d.ts +7 -7
- package/dist/task/decompose-granularity.js +8 -8
- package/dist/task/env-notes.d.ts +65 -19
- package/dist/task/env-notes.js +152 -50
- package/dist/task/external-context.js +25 -5
- package/dist/task/fix-context.d.ts +39 -0
- package/dist/task/fix-context.js +58 -0
- package/dist/task/gate-deps.d.ts +49 -0
- package/dist/task/gate-deps.js +199 -38
- package/dist/task/gate-evidence.d.ts +51 -0
- package/dist/task/gate-evidence.js +90 -0
- package/dist/task/gate-resolution.d.ts +119 -0
- package/dist/task/gate-resolution.js +120 -0
- package/dist/task/git-state-guard.js +3 -26
- package/dist/task/handoff.d.ts +35 -0
- package/dist/task/handoff.js +100 -0
- package/dist/task/health-baseline.d.ts +87 -0
- package/dist/task/health-baseline.js +158 -0
- package/dist/task/ledger.d.ts +10 -0
- package/dist/task/ledger.js +22 -11
- package/dist/task/lint-fix.d.ts +11 -0
- package/dist/task/lint-fix.js +14 -8
- package/dist/task/loop-detector.d.ts +52 -3
- package/dist/task/loop-detector.js +81 -5
- package/dist/task/mentions.d.ts +17 -0
- package/dist/task/mentions.js +86 -0
- package/dist/task/orchestrator.d.ts +53 -9
- package/dist/task/orchestrator.js +104 -28
- package/dist/task/orientation.d.ts +135 -29
- package/dist/task/orientation.js +259 -67
- package/dist/task/owned-freeze-reassign.d.ts +7 -3
- package/dist/task/owned-freeze-reassign.js +13 -5
- package/dist/task/parsers.d.ts +6 -1
- package/dist/task/parsers.js +21 -4
- package/dist/task/phases.d.ts +1 -1
- package/dist/task/phases.js +125 -153
- package/dist/task/plan-orchestrator.js +5 -6
- package/dist/task/plan-rounds.d.ts +1 -0
- package/dist/task/plan-rounds.js +8 -1
- package/dist/task/prohibition-probe.d.ts +6 -1
- package/dist/task/prohibition-probe.js +26 -6
- package/dist/task/prompts.js +13 -2
- package/dist/task/qa-transcript.d.ts +17 -0
- package/dist/task/qa-transcript.js +28 -0
- package/dist/task/question-source.d.ts +16 -2
- package/dist/task/question-source.js +21 -8
- package/dist/task/repo-health-check.d.ts +27 -0
- package/dist/task/repo-health-check.js +39 -5
- package/dist/task/requirements.d.ts +67 -13
- package/dist/task/requirements.js +129 -67
- package/dist/task/research-worker.d.ts +43 -2
- package/dist/task/research-worker.js +103 -8
- package/dist/task/run-bracket.d.ts +9 -4
- package/dist/task/run-bracket.js +9 -0
- package/dist/task/run-context.d.ts +152 -0
- package/dist/task/run-context.js +277 -0
- package/dist/task/spec-doc.d.ts +78 -0
- package/dist/task/spec-doc.js +205 -0
- package/dist/task/spec-model.d.ts +70 -0
- package/dist/task/spec-model.js +139 -0
- package/dist/task/stall-detector.d.ts +1 -1
- package/dist/task/stall-detector.js +9 -5
- package/dist/task/state-dir.d.ts +29 -0
- package/dist/task/state-dir.js +80 -0
- package/dist/task/suppression-probe.d.ts +67 -0
- package/dist/task/suppression-probe.js +78 -0
- package/dist/task/task-gates.d.ts +39 -57
- package/dist/task/task-gates.js +115 -195
- package/dist/task/task-io.d.ts +10 -0
- package/dist/task/task-io.js +69 -18
- package/dist/task/task-parsers.js +6 -6
- package/dist/task/task-types.d.ts +7 -0
- package/dist/task/timings.d.ts +11 -0
- package/dist/task/timings.js +21 -0
- package/dist/task/tree-hash.d.ts +8 -0
- package/dist/task/tree-hash.js +55 -0
- package/dist/task/verify-reconcile.d.ts +4 -3
- package/dist/task/verify-reconcile.js +7 -8
- package/dist/task/verify-resolution.d.ts +28 -5
- package/dist/task/verify-resolution.js +70 -13
- package/dist/task/verify-work.d.ts +98 -35
- package/dist/task/verify-work.js +227 -60
- package/dist/task/yolo.d.ts +0 -13
- package/dist/task/yolo.js +9 -21
- package/dist/workers/pi-worker-core.d.ts +20 -0
- package/dist/workers/pi-worker-core.js +16 -11
- package/dist/workers/pi-worker-docs.d.ts +6 -5
- package/dist/workers/pi-worker-docs.js +8 -7
- package/dist/workers/pi-worker-fetch.d.ts +3 -2
- package/dist/workers/pi-worker-fetch.js +5 -4
- package/dist/workers/pi-worker-search.js +5 -4
- package/dist/workers/research-cache.d.ts +23 -1
- package/dist/workers/research-cache.js +63 -3
- package/dist/workers/shared.d.ts +18 -0
- package/dist/workers/shared.js +38 -22
- package/package.json +1 -1
|
@@ -16,11 +16,17 @@ import { parseAutoAnswer, autoAnswerHasTag, deriveTitle } from './parsers.js';
|
|
|
16
16
|
import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js';
|
|
17
17
|
import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT, DECOMPOSE_COVERAGE_PROMPT } from './auto-prompts.js';
|
|
18
18
|
import { GRILL_AUTO_ANSWER_PROMPT, GRILL_AUTO_FORMAT_HINT } from './prompts.js';
|
|
19
|
-
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseCoverageVerdict, parseTaskList, checkOffTask, stampTaskInProgress, insertTaskAfter, findResumableAutoDetailed } from './auto-io.js';
|
|
19
|
+
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseCoverageVerdict, UNNAMED_COVERAGE_GAP, parseTaskList, planKeyAt, checkOffTask, stampTaskInProgress, beginTaskAttempt, recordTaskEnd, insertTaskAfter, findResumableAutoDetailed } from './auto-io.js';
|
|
20
20
|
import { decideResume, UNATTENDED_STATES } from './resume-gap.js';
|
|
21
|
+
import { ENTRY_ATTEMPT_BUDGET } from './gate-resolution.js';
|
|
22
|
+
import { recordDebt } from './accept-debt.js';
|
|
21
23
|
import { drainRepairQueue, mergeRepairCandidates, planHasRepairFor, parseRepairTitleFile, buildRepairTitle, buildRepairScopeFence, extractFailingCommand } from './root-cause-repair.js';
|
|
22
|
-
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath
|
|
23
|
-
|
|
24
|
+
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath } from './task-io.js';
|
|
25
|
+
// Re-exported as well as used: the @-mention helpers moved to their own module so
|
|
26
|
+
// the research phase can select a cited spec doc without importing this one, and
|
|
27
|
+
// the planning call sites still name them here.
|
|
28
|
+
import { expandFeatureMentions, readableMentions } from './mentions.js';
|
|
29
|
+
export { expandFeatureMentions, readableMentions };
|
|
24
30
|
import { findPhantomImports, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
25
31
|
import { isFatalChildCause, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
26
32
|
import { requestCancel, resetCancel, isCancelRequested, cancelCheckpoint } from './cancel-points.js';
|
|
@@ -36,16 +42,18 @@ import { gitUnmergedPaths, gitStashRef } from './auto-commit.js';
|
|
|
36
42
|
import { runFinalIntegrationGate, deriveOpenDebts } from './final-gate.js';
|
|
37
43
|
import { spawnCommand } from './command-run.js';
|
|
38
44
|
import { getConfig } from '../config/config.js';
|
|
39
|
-
import { debugLogLevel, shouldLogDebug } from './debug-log.js';
|
|
45
|
+
import { debugLogLevel, sanitizeDebugLine, shouldLogDebug } from './debug-log.js';
|
|
46
|
+
import { pruneRunLogs, runLogPath } from './state-dir.js';
|
|
40
47
|
import { isYoloMode, yoloPickAnswer } from './yolo.js';
|
|
41
48
|
import { QaTranscript, CLARIFY_QA_POLICY } from './qa-transcript.js';
|
|
42
49
|
import { makeQuestionSource } from './question-source.js';
|
|
43
50
|
import { CoverageLedger } from './plan-rounds.js';
|
|
44
51
|
import { CLARIFY_QUALITY_RULES, PLAN_FORMAT_HINT } from './plan-session.js';
|
|
45
52
|
import { configureResearchRun, resumeResearchRun } from '../workers/research-cache.js';
|
|
53
|
+
import { currentRunContext } from './run-context.js';
|
|
46
54
|
import { CONTRACT_EXTRACT_PROMPT, parseContractLines, keepGroundedContracts, appendContracts } from './contracts.js';
|
|
47
|
-
import { reconcileTitleSources } from './decompose-fidelity.js';
|
|
48
|
-
import { granularityFloor, granularitySplitHint, isPlanShapeQuestion, isTooCoarse, planShapeIsHostsToAnswer, PLAN_SHAPE_ANSWER } from './decompose-granularity.js';
|
|
55
|
+
import { reconcileTitleSources, renderFragments } from './decompose-fidelity.js';
|
|
56
|
+
import { granularityFloor, granularitySplitHint, isPlanShapeQuestion, isTooCoarse, planShapeIsHostsToAnswer, PLAN_SHAPE_ANSWER, PLAN_SHAPE_TOPIC } from './decompose-granularity.js';
|
|
49
57
|
import { mandatesTestsInSameChange, rewriteBatchTestPlan } from './batch-test-task.js';
|
|
50
58
|
import { REQUIREMENT_EXTRACT_PROMPT, COVERAGE_MAP_PROMPT, parseRequirementLines, keepGroundedRequirements, capRequirements, writeOwnedRequirements, enumerateObligationPassages, uncoveredPassages, extractionRetryHint, parseCoverageMap, accountCoverage, isCrossCuttingRequirement, appendCarriedRequirements, buildRequirementsLedger } from './requirements.js';
|
|
51
59
|
import { groundedCoverage } from './coverage-loop.js';
|
|
@@ -126,23 +134,6 @@ function suspectPlanHint(count) {
|
|
|
126
134
|
+ 'list for the ENTIRE feature, covering every part of the spec end to end. '
|
|
127
135
|
+ 'Output every task, one "- [ ] " line each, nothing else.]');
|
|
128
136
|
}
|
|
129
|
-
// Matches pi's @-file completion token (a path after @, until whitespace).
|
|
130
|
-
const MENTION_RE = /(?:^|\s)@([^\s]+)/g;
|
|
131
|
-
// Trailing punctuation a user naturally types AFTER an @-mention when it sits in
|
|
132
|
-
// prose — "Implement @design.md, reuse…" or "see @spec.md." — which the greedy
|
|
133
|
-
// [^\s]+ above would otherwise swallow into the path. Left unstripped, the
|
|
134
|
-
// resulting "design.md," resolves to no file, expansion is silently skipped, and
|
|
135
|
-
// the planner reasons over a one-line "Implement @design.md" with NO spec inline
|
|
136
|
-
// → it fabricates generic questions and tasks the spec never called for.
|
|
137
|
-
//
|
|
138
|
-
// Measured against a real file: the greedy token from "Implement @design.md,
|
|
139
|
-
// reuse the parser" is `design.md,`, which does not exist; stripped, `design.md`
|
|
140
|
-
// does. None of these chars are legitimate trailing characters of a doc path.
|
|
141
|
-
const MENTION_TRAILING_PUNCT = /[.,;:!?)\]}>"']+$/;
|
|
142
|
-
/** The cleaned path token of an @-mention: greedy match minus trailing prose punctuation. */
|
|
143
|
-
function mentionPath(token) {
|
|
144
|
-
return token.replace(MENTION_TRAILING_PUNCT, '');
|
|
145
|
-
}
|
|
146
137
|
/**
|
|
147
138
|
* Every plan-debug write not yet on disk, chained.
|
|
148
139
|
*
|
|
@@ -161,9 +152,9 @@ export function flushPlanDebug() {
|
|
|
161
152
|
* Fire-and-forget debug line for the PLAN phase (clarify/decompose). It is the
|
|
162
153
|
* only trail that phase has: planning runs before any task file exists, so there
|
|
163
154
|
* is no per-task `TASK_NNNN-debug.log` to write into yet. This goes to
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
155
|
+
* `plan-debug.log` in the run's state dir, beside every other log of the run, so
|
|
156
|
+
* one grep still finds them all. Never throws — the mkdir and the append are both
|
|
157
|
+
* best-effort.
|
|
167
158
|
*
|
|
168
159
|
* Every call site records a plan DECISION (how many titles a round produced,
|
|
169
160
|
* whether a retry was adopted, which clarify answer was auto-resolved), so all of
|
|
@@ -173,11 +164,11 @@ export function flushPlanDebug() {
|
|
|
173
164
|
function logPlanDebug(cwd, msg) {
|
|
174
165
|
if (!shouldLogDebug('event', debugLogLevel()))
|
|
175
166
|
return;
|
|
176
|
-
const line = `${new Date().toISOString()} ${msg}\n`;
|
|
177
|
-
const
|
|
167
|
+
const line = `${new Date().toISOString()} ${sanitizeDebugLine(msg)}\n`;
|
|
168
|
+
const file = runLogPath(cwd, 'plan-debug.log');
|
|
178
169
|
planDebugChain = planDebugChain
|
|
179
|
-
.then(() => fsp.mkdir(
|
|
180
|
-
.then(() => fsp.appendFile(
|
|
170
|
+
.then(() => fsp.mkdir(path.dirname(file), { recursive: true }))
|
|
171
|
+
.then(() => fsp.appendFile(file, line))
|
|
181
172
|
.catch(() => { });
|
|
182
173
|
}
|
|
183
174
|
/**
|
|
@@ -240,61 +231,6 @@ async function triageClarifyQuestion(deps, cwd, featureForModel, existingFilesBl
|
|
|
240
231
|
return null;
|
|
241
232
|
}
|
|
242
233
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Expand any @file references in the feature text by appending each referenced
|
|
245
|
-
* file's contents, so the planning children (clarify, decompose) always see the
|
|
246
|
-
* real spec inline instead of relying on the model to open the file itself.
|
|
247
|
-
* Without this, clarify on a one-line "Implement @spec.md" tends to bail with
|
|
248
|
-
* NONE because, to the model, the request looks small and unambiguous.
|
|
249
|
-
* Unreadable mentions (typos, non-file @tokens) are left untouched; the feature
|
|
250
|
-
* is returned verbatim when nothing readable is referenced.
|
|
251
|
-
*/
|
|
252
|
-
export async function expandFeatureMentions(cwd, feature) {
|
|
253
|
-
const seen = new Set();
|
|
254
|
-
const blocks = [];
|
|
255
|
-
for (const m of feature.matchAll(MENTION_RE)) {
|
|
256
|
-
const rel = mentionPath(m[1]);
|
|
257
|
-
if (rel === '' || seen.has(rel))
|
|
258
|
-
continue;
|
|
259
|
-
seen.add(rel);
|
|
260
|
-
try {
|
|
261
|
-
// Normalize CRLF/CR so an @-mentioned design doc saved on Windows
|
|
262
|
-
// inlines with LF endings the downstream phase parsers expect.
|
|
263
|
-
const body = await readTextFile(path.resolve(cwd, rel));
|
|
264
|
-
if (body.trim().length > 0) {
|
|
265
|
-
blocks.push(`--- contents of ${rel} ---\n${body.trim()}`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
// not a readable file — leave the @token in place, skip expansion
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return blocks.length === 0 ? feature : `${feature.trim()}\n\n${blocks.join('\n\n')}`;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* The @file references in the feature that point at a readable file on disk —
|
|
276
|
-
* the bare path tokens, deduped, in first-seen order. Unreadable mentions
|
|
277
|
-
* (typos, non-file @tokens) are dropped so we never advertise a missing file as
|
|
278
|
-
* an authoritative spec.
|
|
279
|
-
*/
|
|
280
|
-
export async function readableMentions(cwd, feature) {
|
|
281
|
-
const out = [];
|
|
282
|
-
const seen = new Set();
|
|
283
|
-
for (const m of feature.matchAll(MENTION_RE)) {
|
|
284
|
-
const rel = mentionPath(m[1]);
|
|
285
|
-
if (rel === '' || seen.has(rel))
|
|
286
|
-
continue;
|
|
287
|
-
seen.add(rel);
|
|
288
|
-
try {
|
|
289
|
-
await fsp.access(path.resolve(cwd, rel));
|
|
290
|
-
out.push(rel);
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
// not a readable file — don't thread it into task titles
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return out;
|
|
297
|
-
}
|
|
298
234
|
/** A trailing "[decisions: …]" clause decompose may attach to a task line. */
|
|
299
235
|
const DECISIONS_RE = /\s*\[decisions:\s*(.+?)\]\s*$/i;
|
|
300
236
|
/**
|
|
@@ -342,29 +278,37 @@ export function attachSpecRefs(titles, refs) {
|
|
|
342
278
|
*
|
|
343
279
|
* The plan listing strips the threaded "| decisions … | spec …" tail from each
|
|
344
280
|
* title and keeps the human-readable head, so the model reads clean step names,
|
|
345
|
-
* and it marks the current one
|
|
281
|
+
* and it marks the current one `[this]` — both confirmed by building a fence
|
|
346
282
|
* over threaded titles. The authoritative spec ref still rides on THIS step's own
|
|
347
283
|
* title via attachSpecRefs.
|
|
284
|
+
*
|
|
285
|
+
* Every sibling used to read as unbuilt, which is a lie about the first half of
|
|
286
|
+
* any plan past step one: a step told its predecessors are "done in later runs"
|
|
287
|
+
* re-scaffolds what is already in the tree. The entries carry their own done
|
|
288
|
+
* state, so the listing states it.
|
|
348
289
|
*/
|
|
349
|
-
export function buildScopeFence(
|
|
350
|
-
const n =
|
|
351
|
-
const listing =
|
|
352
|
-
.map((
|
|
353
|
-
const head =
|
|
354
|
-
const
|
|
355
|
-
|
|
290
|
+
export function buildScopeFence(entries, currentIndex) {
|
|
291
|
+
const n = entries.length;
|
|
292
|
+
const listing = entries
|
|
293
|
+
.map((e, i) => {
|
|
294
|
+
const head = e.title.split(' | ')[0].trim();
|
|
295
|
+
const marker = i === currentIndex ? 'this'
|
|
296
|
+
: e.done ? 'done'
|
|
297
|
+
: 'later';
|
|
298
|
+
return `[${i + 1}] [${marker}] ${head}`;
|
|
356
299
|
})
|
|
357
300
|
.join('\n');
|
|
358
301
|
return (`PLAN CONTEXT — this task is STEP ${currentIndex + 1} of ${n} in an already-decomposed plan. `
|
|
359
|
-
+ `Each step below is implemented by its OWN separate run
|
|
360
|
-
+ `
|
|
302
|
+
+ `Each step below is implemented by its OWN separate run: a [done] step is ALREADY BUILT and `
|
|
303
|
+
+ `in the tree (build on it — never rebuild, duplicate or re-scaffold it), a [later] step is `
|
|
304
|
+
+ `not built yet and is NOT your job. Implement ONLY the slice named in "Task" below.\n\n`
|
|
361
305
|
+ `The design/spec document the task references describes the WHOLE system across all ${n} `
|
|
362
306
|
+ `steps. Read it to get exact names, types, and signatures for YOUR step and to understand `
|
|
363
307
|
+ `how your step fits — but DO NOT design, scaffold, schema, route, page, query, component, or `
|
|
364
308
|
+ `test anything that belongs to another step listed below. Your GOAL / CONSTRAINTS / `
|
|
365
309
|
+ `KNOWN-UNKNOWNS must cover only THIS step's slice. Do not pull in tables, endpoints, pages, `
|
|
366
310
|
+ `components, or flows owned by a later step.\n\n`
|
|
367
|
-
+ `The full plan (these run separately — do NOT implement
|
|
311
|
+
+ `The full plan (these run separately — do NOT implement the [later] ones here):\n${listing}`);
|
|
368
312
|
}
|
|
369
313
|
/**
|
|
370
314
|
* The scope fence for step `currentIndex`, plus the REPAIR fence when that step is
|
|
@@ -377,12 +321,13 @@ export function buildScopeFence(titles, currentIndex) {
|
|
|
377
321
|
* `bun run test` yields it while a plain-prose defect yields undefined and only the
|
|
378
322
|
* file pin applies.
|
|
379
323
|
*/
|
|
380
|
-
function buildStepFence(
|
|
381
|
-
const base = buildScopeFence(
|
|
382
|
-
const
|
|
324
|
+
export function buildStepFence(entries, currentIndex) {
|
|
325
|
+
const base = buildScopeFence(entries, currentIndex);
|
|
326
|
+
const title = entries[currentIndex]?.title ?? '';
|
|
327
|
+
const repairFile = parseRepairTitleFile(title);
|
|
383
328
|
if (!repairFile)
|
|
384
329
|
return base;
|
|
385
|
-
return `${base}\n\n${buildRepairScopeFence(repairFile, extractFailingCommand(
|
|
330
|
+
return `${base}\n\n${buildRepairScopeFence(repairFile, extractFailingCommand(title))}`;
|
|
386
331
|
}
|
|
387
332
|
/**
|
|
388
333
|
* Drain the gate's root-cause repair queue into the running plan: one scoped
|
|
@@ -566,6 +511,7 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
566
511
|
generate: hint => deps.runChild('auto-clarify', 'read', prependHint(hint, AUTO_CLARIFY_PROMPT(featureForModel, transcript.forGenerator()))),
|
|
567
512
|
formatHint: PLAN_FORMAT_HINT,
|
|
568
513
|
rules: CLARIFY_QUALITY_RULES,
|
|
514
|
+
topics: [{ id: PLAN_SHAPE_TOPIC, match: isPlanShapeQuestion }],
|
|
569
515
|
cap: MAX_CLARIFY_QUESTIONS,
|
|
570
516
|
log: msg => logPlanDebug(cwd, `clarify: ${msg}`)
|
|
571
517
|
});
|
|
@@ -589,6 +535,10 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
589
535
|
if (planShapeIsHostsToAnswer(ownableRequirements) && isPlanShapeQuestion(plainQ)) {
|
|
590
536
|
logPlanDebug(cwd, `plan-shape question answered host-side (not the triage): ${plainQ.replace(/\s+/g, ' ').slice(0, 120)}`);
|
|
591
537
|
transcript.add('host-set', plainQ, PLAN_SHAPE_ANSWER);
|
|
538
|
+
// The transcript entry alone never stopped the re-draw: the generator
|
|
539
|
+
// is stateless and reworded the same fork (often into the plural),
|
|
540
|
+
// which then reached the user or the triage as if it were open.
|
|
541
|
+
source.settle(PLAN_SHAPE_TOPIC);
|
|
592
542
|
continue;
|
|
593
543
|
}
|
|
594
544
|
// Answer-side triage (grill parity): if the inlined spec already settles
|
|
@@ -658,7 +608,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
658
608
|
logPlanDebug(cwd, `decompose fidelity: ${plan.sourced}/${plan.titles.length} titles cited a grounded source; `
|
|
659
609
|
+ `${plan.restored.length} restoration(s)`
|
|
660
610
|
+ plan.restored
|
|
661
|
-
.map(r => ` [task ${r.index + 1}: ${r.fragments
|
|
611
|
+
.map(r => ` [task ${r.index + 1}: ${renderFragments(r.fragments)}]`)
|
|
662
612
|
.join(''));
|
|
663
613
|
}
|
|
664
614
|
// Batch-test ban (item 6): drop or scope a whole-project "write all the
|
|
@@ -777,7 +727,12 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
777
727
|
rethrowIfCancelled(err);
|
|
778
728
|
verdict = null;
|
|
779
729
|
}
|
|
780
|
-
|
|
730
|
+
// An `unparseable` verdict IS a verdict of INCOMPLETE — it just names no
|
|
731
|
+
// area. Reading it as "nothing missing" shipped a plan the judge had ruled
|
|
732
|
+
// incomplete; the host names the gap instead so the round still happens.
|
|
733
|
+
const verdictMissing = verdict?.kind === 'incomplete' ? verdict.missing
|
|
734
|
+
: verdict?.kind === 'unparseable' ? [UNNAMED_COVERAGE_GAP]
|
|
735
|
+
: [];
|
|
781
736
|
let acc = null;
|
|
782
737
|
// The monotonic guard's owned-set is grounded DETERMINISTICALLY in
|
|
783
738
|
// requirement↔title token overlap — NOT the coverage-map model's TASK
|
|
@@ -880,6 +835,10 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
880
835
|
.slice(0, 200)}]`
|
|
881
836
|
: ''));
|
|
882
837
|
}
|
|
838
|
+
if (outcome.decision.terminal === true) {
|
|
839
|
+
logPlanDebug(cwd, 'decompose-coverage: TERMINAL — another round asks the same question');
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
883
842
|
}
|
|
884
843
|
const best = rounds.best();
|
|
885
844
|
const round = rounds.round();
|
|
@@ -1026,16 +985,23 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
1026
985
|
ground: emitted => keepGroundedScripts(emitted, featureForModel),
|
|
1027
986
|
append: appendDeclaredScripts
|
|
1028
987
|
});
|
|
1029
|
-
// Persist the TASK-MAPPED requirements
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
//
|
|
988
|
+
// Persist the TASK-MAPPED requirements under the plan key of the entry each
|
|
989
|
+
// was mapped to — the same key `buildAutoBody` is about to write onto the
|
|
990
|
+
// checkbox line, so the two sides of the join are minted from one index. With
|
|
991
|
+
// only cross-cutting entries travelling, the mapped ones shape the title list
|
|
992
|
+
// and then vanish, and a task can narrow a requirement out of its own spec
|
|
993
|
+
// with nothing to stop it.
|
|
1033
994
|
// Inert until the owned-requirements injection is wired into the phase
|
|
1034
995
|
// prompts; recorded regardless so the plan's mapping is auditable per run.
|
|
1035
996
|
if (best.accounting && best.accounting.mapped.length > 0) {
|
|
1036
997
|
await writeOwnedRequirements(cwd, best.accounting.mapped
|
|
1037
998
|
.filter(m => m.task >= 1 && m.task <= titles.length)
|
|
1038
|
-
.map(m => ({
|
|
999
|
+
.map(m => ({
|
|
1000
|
+
quote: m.req.quote,
|
|
1001
|
+
anchor: m.req.anchor,
|
|
1002
|
+
key: planKeyAt(m.task - 1),
|
|
1003
|
+
title: titles[m.task - 1]
|
|
1004
|
+
})));
|
|
1039
1005
|
}
|
|
1040
1006
|
// persist
|
|
1041
1007
|
const id = await allocateAutoId(cwd);
|
|
@@ -1276,9 +1242,28 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1276
1242
|
return;
|
|
1277
1243
|
}
|
|
1278
1244
|
await updateTaskFrontMatter(cwd, id, { state: 'completed' });
|
|
1245
|
+
// Retention runs HERE and nowhere else: a completed run is the one
|
|
1246
|
+
// moment the state dir is quiet and this process still knows which
|
|
1247
|
+
// repo it was serving. A failed or cancelled run keeps its logs —
|
|
1248
|
+
// those are the ones somebody is about to read.
|
|
1249
|
+
await pruneRunLogs(cwd);
|
|
1279
1250
|
announceDone(active, stage.message, stage.level);
|
|
1280
1251
|
return;
|
|
1281
1252
|
}
|
|
1253
|
+
// ABANDON an entry that has spent its attempt budget. A task that
|
|
1254
|
+
// crashes before it can be checked off is re-entered by every resume,
|
|
1255
|
+
// for as long as anyone keeps resuming — the plan never advances and
|
|
1256
|
+
// the entry never converges. Give up on THIS entry, write down what was
|
|
1257
|
+
// left unfinished, and let the rest of the plan run.
|
|
1258
|
+
if ((next.attempts ?? 0) >= ENTRY_ATTEMPT_BUDGET) {
|
|
1259
|
+
await recordDebt(cwd, next.producedId ?? id, `"${next.title}" was abandoned after ${next.attempts} attempts`
|
|
1260
|
+
+ `${next.lastEnd ? ` (last ended: ${next.lastEnd})` : ''}`
|
|
1261
|
+
+ ' — no verified artifact; the plan moved on without it', 'abandoned');
|
|
1262
|
+
await checkOffTask(cwd, id, next.index, next.producedId ?? '', next.title);
|
|
1263
|
+
notifyBoth(active, `${id}: abandoned "${next.title}" after ${next.attempts} attempts — `
|
|
1264
|
+
+ 'recorded as debt; continuing with the rest of the plan.', 'warning');
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1282
1267
|
// REFUSE to start on a conflicted tree: an unmerged index dooms every
|
|
1283
1268
|
// commit ahead and a `git add -A` would silently mis-resolve it.
|
|
1284
1269
|
const unmerged = deps.unmergedPaths ? await deps.unmergedPaths(cwd) : [];
|
|
@@ -1323,6 +1308,18 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1323
1308
|
if (checkpoint.committed) {
|
|
1324
1309
|
notifyRun(active, `${id}: checkpointed uncommitted work before "${next.title}".`, 'info');
|
|
1325
1310
|
}
|
|
1311
|
+
// REPO-HEALTH BASELINE, taken here because the checkpoint above just made
|
|
1312
|
+
// the tree clean: what the project's own statics say now is what this task
|
|
1313
|
+
// INHERITED, and the verify gate attributes a red check against it instead
|
|
1314
|
+
// of failing the task for a sibling's defect (health-baseline.ts). The
|
|
1315
|
+
// inner task file does not exist yet, so the capture is handed to the
|
|
1316
|
+
// runner, which writes the section once its id is allocated.
|
|
1317
|
+
const capture = deps.captureHealthBaseline;
|
|
1318
|
+
const healthBaseline = capture ?
|
|
1319
|
+
{
|
|
1320
|
+
healthBaseline: (taskCtx) => capture(taskCtx, cwd, next.title)
|
|
1321
|
+
}
|
|
1322
|
+
: {};
|
|
1326
1323
|
// Stash ref before the task: compared after the gates so a stash pushed
|
|
1327
1324
|
// during the task (impl model or any child) and left behind is called
|
|
1328
1325
|
// out instead of silently waiting to detonate in a later task.
|
|
@@ -1336,14 +1333,21 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1336
1333
|
announceDone(active, `${id} cancelled before "${next.title}" — resume with /task-auto-resume.`, 'warning');
|
|
1337
1334
|
return;
|
|
1338
1335
|
}
|
|
1336
|
+
// Counted BEFORE the run, not after it: a run that takes pi down
|
|
1337
|
+
// with it would never reach an after-the-fact increment, and an
|
|
1338
|
+
// attempt nobody counted is exactly the one the budget exists to
|
|
1339
|
+
// bound.
|
|
1340
|
+
await beginTaskAttempt(cwd, id, next.index);
|
|
1339
1341
|
const res = await deps.runTask(active, cwd, next.title, {
|
|
1340
1342
|
resumeId,
|
|
1343
|
+
...healthBaseline,
|
|
1341
1344
|
// Fence this step against re-expanding the whole referenced spec:
|
|
1342
1345
|
// name the sibling steps so refine bounds this step's slice. Only
|
|
1343
1346
|
// matters when refine runs fresh (a resumed task past refine ignores
|
|
1344
1347
|
// it), but always supplied so a resume that restarts at refine is
|
|
1345
1348
|
// fenced too.
|
|
1346
|
-
planContext: buildStepFence(entries
|
|
1349
|
+
planContext: buildStepFence(entries, next.index),
|
|
1350
|
+
...(next.key !== undefined && { planKey: next.key }),
|
|
1347
1351
|
onStart: resumeId ? undefined : (innerId => stampTaskInProgress(cwd, id, next.index, innerId, next.title))
|
|
1348
1352
|
});
|
|
1349
1353
|
active = res.ctx ?? active;
|
|
@@ -1356,6 +1360,10 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1356
1360
|
// only the wording is this command's.
|
|
1357
1361
|
if (!runSucceeded(res.end)) {
|
|
1358
1362
|
const policy = RUN_END_POLICY[res.end.kind];
|
|
1363
|
+
// The ending goes on the ENTRY, where the next resume reads it:
|
|
1364
|
+
// the inner task file's own state cannot distinguish the entry
|
|
1365
|
+
// that keeps faulting from the one a user stopped once.
|
|
1366
|
+
await recordTaskEnd(cwd, id, next.index, res.end.kind);
|
|
1359
1367
|
// Demote the INNER task file: it reads `completed` from
|
|
1360
1368
|
// spec-handoff, and leaving it that way is how a failed run's task
|
|
1361
1369
|
// file claims success after the run failed.
|
|
@@ -1396,7 +1404,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1396
1404
|
title: next.title,
|
|
1397
1405
|
tag: id,
|
|
1398
1406
|
// Fence an AUTOFIX re-run against re-expanding the whole spec.
|
|
1399
|
-
planContext: buildStepFence(entries
|
|
1407
|
+
planContext: buildStepFence(entries, next.index),
|
|
1400
1408
|
// res.ok === true means runner.run() completed, so res.taskId is the
|
|
1401
1409
|
// allocated TASK_NNNN id (never empty here). The parent task-list
|
|
1402
1410
|
// check-off runs after verify passes/accepts and before the commit,
|
|
@@ -1481,10 +1489,12 @@ async function handleTaskAuto(args, ctx) {
|
|
|
1481
1489
|
// and the ordinary command path cannot reach us.
|
|
1482
1490
|
try {
|
|
1483
1491
|
await withRun(ctx, { onCancel: terminalCancel }, async () => {
|
|
1484
|
-
// Stamp
|
|
1492
|
+
// Stamp the per-run research-cache id BEFORE planning so enrichment and
|
|
1485
1493
|
// every task's research phase share one run's cache; disabled ⇒ clears any token a
|
|
1486
|
-
// prior run left, so nothing is cached.
|
|
1487
|
-
|
|
1494
|
+
// prior run left, so nothing is cached. It is the RUN's own id (the bracket
|
|
1495
|
+
// opened the run context one statement ago), so the cache file and the
|
|
1496
|
+
// env-notes ledger name this run the same way.
|
|
1497
|
+
configureResearchRun(getConfig().researchCache, currentRunContext(cwd).runId);
|
|
1488
1498
|
const abort = new AbortController();
|
|
1489
1499
|
const deps = defaultDeps(ctx, cwd, abort.signal, deriveTitle(raw));
|
|
1490
1500
|
let id;
|
|
@@ -1557,6 +1567,10 @@ async function handleTaskAutoResume(args, ctx) {
|
|
|
1557
1567
|
// working cache, and a whole-file freshness gate can never hold on a
|
|
1558
1568
|
// greenfield run that installs packages as it goes — so invalidation is
|
|
1559
1569
|
// per entry. See resumeResearchRun.
|
|
1570
|
+
//
|
|
1571
|
+
// The RUN context keeps its own fresh id, deliberately: an external doc
|
|
1572
|
+
// digest does not go stale over a pause, and an environment fact measured
|
|
1573
|
+
// against the tree before it does.
|
|
1560
1574
|
const research = await resumeResearchRun(cwd, getConfig().researchCache);
|
|
1561
1575
|
if (research.reused) {
|
|
1562
1576
|
logPlanDebug(cwd, `research cache: resume reused ${research.entries} entr(ies), `
|
|
@@ -16,6 +16,7 @@ import type { docsRaw, docsFocused } from '../workers/docs-core.js';
|
|
|
16
16
|
import type { fetchRaw, fetchFocused } from '../workers/fetch-core.js';
|
|
17
17
|
import type { npmVersionLookup } from '../workers/npm-version.js';
|
|
18
18
|
import type { SearchCoreInput, SearchCoreResult } from '../workers/search-core.js';
|
|
19
|
+
import type { RunContext } from './run-context.js';
|
|
19
20
|
export declare const USER_CANCELLED = "__user_cancelled__";
|
|
20
21
|
/**
|
|
21
22
|
* Why a phase child did not answer.
|
|
@@ -65,6 +66,13 @@ export interface PhaseDeps {
|
|
|
65
66
|
cwd: string;
|
|
66
67
|
taskId: string;
|
|
67
68
|
signal: AbortSignal;
|
|
69
|
+
/**
|
|
70
|
+
* What is true of the RUN rather than of this task: the inventory, the
|
|
71
|
+
* orientation core, the manifest deps, the ecosystems, the verified tooling.
|
|
72
|
+
* Absent → the phase builds a throwaway context for itself, which is the old
|
|
73
|
+
* per-task behaviour (see run-context.ts).
|
|
74
|
+
*/
|
|
75
|
+
runContext?: RunContext;
|
|
68
76
|
onChildOutput?: (line: string) => void;
|
|
69
77
|
onContextUsage?: (snapshot: ContextSnapshot) => void;
|
|
70
78
|
/**
|
|
@@ -215,6 +223,16 @@ export declare function runPhaseChild(deps: PhaseDeps, name: string, tools: stri
|
|
|
215
223
|
*/
|
|
216
224
|
export declare function formatDegradeHint(hit: LoopHit): string;
|
|
217
225
|
export declare function prependHint(hint: string | null, prompt: string): string;
|
|
226
|
+
/**
|
|
227
|
+
* Append one line per loop kill to the task file's `loop events` section.
|
|
228
|
+
*
|
|
229
|
+
* Best-effort by contract: it runs for every phase child AND every research
|
|
230
|
+
* worker, and not every caller
|
|
231
|
+
* owns a task file on disk (a scripted harness, a bare unit deps bag). A trail
|
|
232
|
+
* that cannot be written must cost the phase nothing — the loop kill itself is
|
|
233
|
+
* already reported through the debug log and the thrown error.
|
|
234
|
+
*/
|
|
235
|
+
export declare function appendLoopEvents(cwd: string, taskId: string, phase: string, r: RunWorkerResult): Promise<void>;
|
|
218
236
|
/**
|
|
219
237
|
* Run a child up to twice; the second attempt gets `emphasized=true` to escalate
|
|
220
238
|
* the prompt. On success, return the validator's value; on two failures, throw
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
12
12
|
import { classifyWorkerFailure } from '../workers/worker-failure.js';
|
|
13
13
|
import { isFatalKill } from '../workers/worker-kill.js';
|
|
14
|
-
import { MAX_LOOP_RESTARTS } from './loop-detector.js';
|
|
14
|
+
import { describeLoopHit, MAX_LOOP_RESTARTS } from './loop-detector.js';
|
|
15
15
|
import { MAX_LEAK_RETRIES } from '../shared/leaked-tool-call.js';
|
|
16
|
-
import {
|
|
16
|
+
import { mergeTaskSection } from './task-io.js';
|
|
17
17
|
import { streamStallCause } from '../shared/stream-watchdog.js';
|
|
18
18
|
import { getConfig } from '../config/config.js';
|
|
19
19
|
import { groupChildArgs, groupWindow } from '../config/group-args.js';
|
|
@@ -238,15 +238,15 @@ export function prependHint(hint, prompt) {
|
|
|
238
238
|
/**
|
|
239
239
|
* Append one line per loop kill to the task file's `loop events` section.
|
|
240
240
|
*
|
|
241
|
-
* Best-effort by contract: it runs for
|
|
241
|
+
* Best-effort by contract: it runs for every phase child AND every research
|
|
242
|
+
* worker, and not every caller
|
|
242
243
|
* owns a task file on disk (a scripted harness, a bare unit deps bag). A trail
|
|
243
244
|
* that cannot be written must cost the phase nothing — the loop kill itself is
|
|
244
245
|
* already reported through the debug log and the thrown error.
|
|
245
246
|
*/
|
|
246
|
-
async function appendLoopEvents(cwd, taskId, phase, r) {
|
|
247
|
+
export async function appendLoopEvents(cwd, taskId, phase, r) {
|
|
247
248
|
const line = (hit, strike, outcome) => `- ${new Date().toISOString()} ${phase} strike ${strike}/${MAX_LOOP_RESTARTS + 1} `
|
|
248
|
-
+ `${
|
|
249
|
-
+ `${hit.windowSize} calls → ${outcome}`;
|
|
249
|
+
+ `${describeLoopHit(hit)} → ${outcome}`;
|
|
250
250
|
const lines = r.restarts.flatMap(x => x.loopHit ?
|
|
251
251
|
[
|
|
252
252
|
line(x.loopHit, x.attempt, x.rescue ? 'degraded — no-tools final attempt' : 'restarted with hint')
|
|
@@ -257,8 +257,7 @@ async function appendLoopEvents(cwd, taskId, phase, r) {
|
|
|
257
257
|
if (lines.length === 0)
|
|
258
258
|
return;
|
|
259
259
|
try {
|
|
260
|
-
|
|
261
|
-
await setTaskSection(cwd, taskId, 'loop events', [existing, ...lines].filter(Boolean).join('\n'));
|
|
260
|
+
await mergeTaskSection(cwd, taskId, 'loop events', existing => [existing ?? '', ...lines].filter(Boolean).join('\n'));
|
|
262
261
|
}
|
|
263
262
|
catch {
|
|
264
263
|
/* best-effort: a trail is never worth failing a phase for */
|
|
@@ -172,6 +172,10 @@ export type VerifyRerunOutcome = {
|
|
|
172
172
|
outcome: 'gap';
|
|
173
173
|
detail: string;
|
|
174
174
|
};
|
|
175
|
+
/** The command word of a shell line, past any leading `VAR=value` assignments.
|
|
176
|
+
* Exported for gate-evidence, which spawns the same shape of line and must
|
|
177
|
+
* resolve the same runner against the same stripped PATH. */
|
|
178
|
+
export declare function leadingBin(line: string): string | null;
|
|
175
179
|
/**
|
|
176
180
|
* Re-run one VERIFY-block command line under the gate's existing
|
|
177
181
|
* env-gap contract, so a debt whose reason NAMES that command can be closed by the
|
package/dist/task/command-run.js
CHANGED
|
@@ -308,8 +308,10 @@ export function classifyCommandRun(run, gapPatterns = [], opts = {}) {
|
|
|
308
308
|
tail: outputTail(run.stdout, run.stderr)
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
|
-
/** The command word of a shell line, past any leading `VAR=value` assignments.
|
|
312
|
-
|
|
311
|
+
/** The command word of a shell line, past any leading `VAR=value` assignments.
|
|
312
|
+
* Exported for gate-evidence, which spawns the same shape of line and must
|
|
313
|
+
* resolve the same runner against the same stripped PATH. */
|
|
314
|
+
export function leadingBin(line) {
|
|
313
315
|
for (const tok of line.trim().split(/\s+/)) {
|
|
314
316
|
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(tok))
|
|
315
317
|
continue;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* constraint-policy — how much a constraint's PROVENANCE is worth, as one table
|
|
3
|
+
* both gate children render from.
|
|
4
|
+
*
|
|
5
|
+
* The verify child was told a spec constraint is unwaivable, full stop. The
|
|
6
|
+
* resolution judge was told to judge by function and treat over-literal wording
|
|
7
|
+
* as a false alarm. Neither was wrong on its own; together they made the gate's
|
|
8
|
+
* answer depend on which child happened to speak. 0043 is what that costs: a
|
|
9
|
+
* constraint a grill AUTO-ANSWER invented ("do NOT touch `test/ct/template/
|
|
10
|
+
* index.html`") was held as unwaivable as one the user typed, and the task failed
|
|
11
|
+
* on it.
|
|
12
|
+
*
|
|
13
|
+
* A constraint a human stated, or one the refined task carried in, BINDS. A
|
|
14
|
+
* constraint some automation produced — an auto-answer, a YOLO pick, an untagged
|
|
15
|
+
* line the composer wrote itself — is ADVISORY: it sharpens the work, and it
|
|
16
|
+
* fails a gate only when an ACCEPTANCE bullet also fails. Acceptance is the bar;
|
|
17
|
+
* an advisory constraint is a hint about how to clear it.
|
|
18
|
+
*
|
|
19
|
+
* One table, one renderer, two call sites (verify rule 4b, the judge's rules).
|
|
20
|
+
*/
|
|
21
|
+
import type { Constraint, ConstraintProvenance } from './spec-model.js';
|
|
22
|
+
export type ConstraintWeight = 'binding' | 'advisory';
|
|
23
|
+
/**
|
|
24
|
+
* What each provenance is worth. A new `ConstraintProvenance` member is a compile
|
|
25
|
+
* error until it declares its weight — which is the point, since the cheapest way
|
|
26
|
+
* back to 0043 is a new automated kind quietly defaulting to `binding`.
|
|
27
|
+
*
|
|
28
|
+
* `accepted` binds because a human read the recommendation and took it; `typed`
|
|
29
|
+
* and `host-set` for the same reason at either end (a person's own words, and a
|
|
30
|
+
* decision the host makes deterministically rather than leaving to a model).
|
|
31
|
+
* Everything else was produced by automation reasoning about the task, and
|
|
32
|
+
* automation may not mint an unwaivable rule.
|
|
33
|
+
*/
|
|
34
|
+
export declare const CONSTRAINT_WEIGHTS: Record<ConstraintProvenance, ConstraintWeight>;
|
|
35
|
+
export declare function constraintWeight(provenance: ConstraintProvenance): ConstraintWeight;
|
|
36
|
+
/** The weight marker a child sees on a constraint line, and probes match on. */
|
|
37
|
+
export declare function weightTag(provenance: ConstraintProvenance): string;
|
|
38
|
+
/** Does this collection of findings name at least one BINDING constraint? */
|
|
39
|
+
export declare function anyBinding(findings: readonly string[]): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The spec's CONSTRAINTS, annotated with each line's weight, for a child that
|
|
42
|
+
* must judge them. Returns null when the spec states none — the caller then emits
|
|
43
|
+
* no block rather than an empty heading.
|
|
44
|
+
*/
|
|
45
|
+
export declare function annotateConstraints(constraints: readonly Constraint[]): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* The policy, as prompt lines. ONE renderer: the verify pass and the resolution
|
|
48
|
+
* judge cite the same paragraph, so the two children cannot hold opposite
|
|
49
|
+
* policies again. `heading` differs because the two prompts number their rules
|
|
50
|
+
* differently — that is the only thing a call site may vary.
|
|
51
|
+
*/
|
|
52
|
+
export declare function renderConstraintPolicy(heading: string): string[];
|