@agent-plan/core 0.3.0 → 0.3.1
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/dist/guard-decision.d.ts +53 -43
- package/dist/guard-decision.d.ts.map +1 -1
- package/dist/guard-decision.js +134 -68
- package/dist/guard-state.d.ts +14 -0
- package/dist/guard-state.d.ts.map +1 -0
- package/dist/guard-state.js +35 -0
- package/dist/handoff-reply.d.ts +5 -0
- package/dist/handoff-reply.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/load-reply.d.ts +63 -0
- package/dist/load-reply.d.ts.map +1 -0
- package/dist/load-reply.js +64 -0
- package/dist/on-hold.d.ts +55 -0
- package/dist/on-hold.d.ts.map +1 -0
- package/dist/on-hold.js +123 -0
- package/dist/package-version.d.ts +13 -0
- package/dist/package-version.d.ts.map +1 -1
- package/dist/package-version.js +32 -0
- package/dist/project-context.d.ts +6 -4
- package/dist/project-context.d.ts.map +1 -1
- package/dist/project-context.js +2 -3
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +6 -0
- package/dist/recommend-reply.d.ts.map +1 -1
- package/dist/recommend-reply.js +10 -1
- package/dist/task-context.d.ts.map +1 -1
- package/dist/task-context.js +23 -13
- package/dist/task-selection.d.ts +4 -1
- package/dist/task-selection.d.ts.map +1 -1
- package/dist/task-selection.js +22 -2
- package/package.json +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { featureNumberOfPhase, formatFeatureRef, formatPhaseRef } from "./naming.js";
|
|
2
|
+
import { buildBoundedAcceptedDecisionContext } from "./task-context.js";
|
|
3
|
+
export const PLANNER_LOAD_RECAP_HEADING = "## Recap — show this to the user verbatim";
|
|
4
|
+
export const PLANNER_LOAD_AGENT_HEADING = "## Agent-only context — read and keep it; never quote it in the recap";
|
|
5
|
+
/** Split a load reply's text back into the recap the user sees and the
|
|
6
|
+
* agent-only context. Any client (or test) that must show only the recap
|
|
7
|
+
* uses this instead of guessing at the layout. */
|
|
8
|
+
export function splitPlannerLoadText(text) {
|
|
9
|
+
const agentAt = text.indexOf(PLANNER_LOAD_AGENT_HEADING);
|
|
10
|
+
const recapStart = text.startsWith(PLANNER_LOAD_RECAP_HEADING) ? PLANNER_LOAD_RECAP_HEADING.length : 0;
|
|
11
|
+
if (agentAt < 0)
|
|
12
|
+
return { recap: text.slice(recapStart).trim(), agentContext: "" };
|
|
13
|
+
return { recap: text.slice(recapStart, agentAt).trim(), agentContext: text.slice(agentAt + PLANNER_LOAD_AGENT_HEADING.length).trim() };
|
|
14
|
+
}
|
|
15
|
+
export function buildPlannerLoadReply(input) {
|
|
16
|
+
const guide = input.plannerGuide;
|
|
17
|
+
const guideBlock = guide.mode === "include"
|
|
18
|
+
? ["### Agent Plan operating guide", guide.customized ? guide.message : "", guide.content].filter(Boolean).join("\n\n")
|
|
19
|
+
: [
|
|
20
|
+
"### Agent Plan operating guide",
|
|
21
|
+
`Follow the Agent Plan operating guide. If it is not already in this conversation (the /planner command includes it), read ${guide.path}.`,
|
|
22
|
+
guide.customized ? guide.message : "",
|
|
23
|
+
].filter(Boolean).join("\n");
|
|
24
|
+
const blocks = [
|
|
25
|
+
PLANNER_LOAD_RECAP_HEADING,
|
|
26
|
+
input.recap,
|
|
27
|
+
PLANNER_LOAD_AGENT_HEADING,
|
|
28
|
+
"### Project context",
|
|
29
|
+
input.projectContext.text,
|
|
30
|
+
];
|
|
31
|
+
if (input.scopedDecisions && input.scopedDecisions.total > 0) {
|
|
32
|
+
blocks.push("### Accepted decisions for features, phases and tasks", input.scopedDecisions.content);
|
|
33
|
+
}
|
|
34
|
+
blocks.push(guideBlock);
|
|
35
|
+
return {
|
|
36
|
+
text: blocks.join("\n\n"),
|
|
37
|
+
structured: {
|
|
38
|
+
loaded: true,
|
|
39
|
+
projectContext: input.projectContext.structured,
|
|
40
|
+
plannerGuide: guide.mode === "include"
|
|
41
|
+
? { mode: "include", customized: guide.customized }
|
|
42
|
+
: { mode: "pointer", path: guide.path, customized: guide.customized },
|
|
43
|
+
scopedDecisions: input.scopedDecisions
|
|
44
|
+
? { total: input.scopedDecisions.total, truncated: input.scopedDecisions.truncated }
|
|
45
|
+
: { total: 0, truncated: false },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Accepted Decisions owned by features, phases and tasks, bounded for a
|
|
50
|
+
* load reply. Project decisions are left out: the project context already
|
|
51
|
+
* carries them. */
|
|
52
|
+
export function buildScopedDecisionContext(plan) {
|
|
53
|
+
const features = plan.features.features;
|
|
54
|
+
const phaseRef = (phase) => formatPhaseRef(phase.number, featureNumberOfPhase(phase, features));
|
|
55
|
+
const { content, total, truncated } = buildBoundedAcceptedDecisionContext([
|
|
56
|
+
...features.map((feature) => ({ scope: `feature ${formatFeatureRef(feature.number)}`, decisions: feature.acceptedDecisions })),
|
|
57
|
+
...plan.phases.map((phase) => ({ scope: `phase ${phaseRef(phase)}`, decisions: phase.acceptedDecisions })),
|
|
58
|
+
...plan.phases.flatMap((phase) => phase.tasks.map((task) => ({
|
|
59
|
+
scope: `task ${phaseRef(phase)}/T${String(task.number).padStart(3, "0")}`,
|
|
60
|
+
decisions: task.acceptedDecisions,
|
|
61
|
+
}))),
|
|
62
|
+
]);
|
|
63
|
+
return { content, total, truncated };
|
|
64
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Feature, Phase } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* On-hold work that may be ready to resume.
|
|
4
|
+
*
|
|
5
|
+
* A task parked as deferred, waiting or blocked keeps its reason in the
|
|
6
|
+
* status log ("Resume once P097 lands"). Nothing used to look at those
|
|
7
|
+
* reasons again, so work whose blocker had finished stayed parked until a
|
|
8
|
+
* person noticed. This module is the one place that lists on-hold tasks
|
|
9
|
+
* with their reasons and checks the ones whose reason names planner refs:
|
|
10
|
+
* the recommendation and the load recap both read it, so they cannot
|
|
11
|
+
* disagree.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ON_HOLD_STATUSES: Set<string>;
|
|
14
|
+
export interface OnHoldRef {
|
|
15
|
+
/** The ref as written in the reason, normalized (e.g. `P097(F003)`, `T468`). */
|
|
16
|
+
ref: string;
|
|
17
|
+
/** Whether the named phase or task exists and is finished. */
|
|
18
|
+
finished: boolean;
|
|
19
|
+
/** False when the ref names nothing in this plan. */
|
|
20
|
+
found: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface OnHoldTask {
|
|
23
|
+
taskId: string;
|
|
24
|
+
ref: string;
|
|
25
|
+
title: string;
|
|
26
|
+
status: string;
|
|
27
|
+
phaseId: string;
|
|
28
|
+
phaseRef: string;
|
|
29
|
+
/** Why it was put on hold: the motivation of the status change that parked it. */
|
|
30
|
+
reason: string;
|
|
31
|
+
heldSince: string;
|
|
32
|
+
/** Planner refs named in the reason, each checked against the plan. */
|
|
33
|
+
namedRefs: OnHoldRef[];
|
|
34
|
+
/** True when the reason names at least one ref and every named ref is finished. */
|
|
35
|
+
readyToResume: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Resolve every planner ref named in `text`, by number only (never by title). */
|
|
38
|
+
export declare function refsNamedIn(text: string, phases: Phase[], features: Feature[], selfTaskId?: string): OnHoldRef[];
|
|
39
|
+
/**
|
|
40
|
+
* Every on-hold task, ready-to-resume ones first. Pass `phaseIds` to limit
|
|
41
|
+
* the list to the phases a caller is about to act on.
|
|
42
|
+
*/
|
|
43
|
+
export declare function listOnHoldWork(features: Feature[], phases: Phase[], phaseIds?: Set<string>): OnHoldTask[];
|
|
44
|
+
/**
|
|
45
|
+
* The phases whose on-hold work matters now: phases where work has started
|
|
46
|
+
* (some task finished or in progress) but not every task is finished. A
|
|
47
|
+
* phase nobody has started yet keeps its on-hold tasks out of the way.
|
|
48
|
+
*/
|
|
49
|
+
export declare function startedOpenPhaseIds(phases: Phase[]): Set<string>;
|
|
50
|
+
/**
|
|
51
|
+
* One bounded text block for any reply. Ready-to-resume tasks first; each
|
|
52
|
+
* line carries the reason so the agent can re-check it against the plan.
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderOnHoldWork(items: OnHoldTask[], limit?: number): string;
|
|
55
|
+
//# sourceMappingURL=on-hold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-hold.d.ts","sourceRoot":"","sources":["../src/on-hold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAQ,MAAM,aAAa,CAAC;AAGxD;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,gBAAgB,aAA8C,CAAC;AAG5E,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,QAAQ,EAAE,OAAO,CAAC;IAClB,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,mFAAmF;IACnF,aAAa,EAAE,OAAO,CAAC;CACxB;AAgBD,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,SAAK,GAAG,SAAS,EAAE,CA8B5G;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,CAwBzG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAKhE;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,SAAI,GAAG,MAAM,CAiBvE"}
|
package/dist/on-hold.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { featureNumberOfPhase, formatPhaseRef } from "./naming.js";
|
|
2
|
+
/**
|
|
3
|
+
* On-hold work that may be ready to resume.
|
|
4
|
+
*
|
|
5
|
+
* A task parked as deferred, waiting or blocked keeps its reason in the
|
|
6
|
+
* status log ("Resume once P097 lands"). Nothing used to look at those
|
|
7
|
+
* reasons again, so work whose blocker had finished stayed parked until a
|
|
8
|
+
* person noticed. This module is the one place that lists on-hold tasks
|
|
9
|
+
* with their reasons and checks the ones whose reason names planner refs:
|
|
10
|
+
* the recommendation and the load recap both read it, so they cannot
|
|
11
|
+
* disagree.
|
|
12
|
+
*/
|
|
13
|
+
export const ON_HOLD_STATUSES = new Set(["deferred", "waiting", "blocked"]);
|
|
14
|
+
const FINISHED = new Set(["done", "canceled", "rejected"]);
|
|
15
|
+
// A phase ref with optional feature and task, in either spelling
|
|
16
|
+
// (`P097`, `P097(F003)`, `F003/P097`, `P094(F011)/T449`, `F011/P094/T449`),
|
|
17
|
+
// or a bare task ref (`T468`). Word-bounded so prose like "HTTP2" or
|
|
18
|
+
// "T-shirt" never matches.
|
|
19
|
+
const REF_IN_TEXT = /\b(?:F(\d{1,4})\/)?P(\d{1,4})(?:\(F(\d{1,4})\))?(?:\/T(\d{1,4}))?(?!\w)|\bT(\d{1,4})(?!\w)/g;
|
|
20
|
+
const pad = (n) => String(n).padStart(3, "0");
|
|
21
|
+
function holdEntry(task) {
|
|
22
|
+
const entry = [...(task.statusLog ?? [])].reverse().find((log) => log.toStatus === task.status);
|
|
23
|
+
const reason = (entry?.description ?? "").replace(/^Motivation:\s*/i, "").trim();
|
|
24
|
+
return { reason, at: entry?.date ?? task.updatedAt ?? "" };
|
|
25
|
+
}
|
|
26
|
+
/** Resolve every planner ref named in `text`, by number only (never by title). */
|
|
27
|
+
export function refsNamedIn(text, phases, features, selfTaskId = "") {
|
|
28
|
+
const phaseByNumber = new Map(phases.map((phase) => [phase.number, phase]));
|
|
29
|
+
const taskByNumber = new Map(phases.flatMap((phase) => phase.tasks.map((task) => [task.number, { phase, task }])));
|
|
30
|
+
const featureByNumber = new Map(features.map((feature) => [feature.number, feature]));
|
|
31
|
+
const seen = new Map();
|
|
32
|
+
for (const match of text.matchAll(REF_IN_TEXT)) {
|
|
33
|
+
const [, leadingFeatureNum, phaseNum, trailingFeatureNum, phaseTaskNum, bareTaskNum] = match;
|
|
34
|
+
const featureNum = trailingFeatureNum ?? leadingFeatureNum;
|
|
35
|
+
if (bareTaskNum || phaseTaskNum) {
|
|
36
|
+
const taskNum = Number(bareTaskNum ?? phaseTaskNum);
|
|
37
|
+
const hit = taskByNumber.get(taskNum);
|
|
38
|
+
const parentOk = !phaseNum || hit?.phase.number === Number(phaseNum);
|
|
39
|
+
const featureOk = !featureNum || (hit && hit.phase.featureId === featureByNumber.get(Number(featureNum))?.id);
|
|
40
|
+
const ref = phaseNum
|
|
41
|
+
? `P${pad(Number(phaseNum))}${featureNum ? `(F${pad(Number(featureNum))})` : ""}/T${pad(taskNum)}`
|
|
42
|
+
: `T${pad(taskNum)}`;
|
|
43
|
+
if (hit?.task.id === selfTaskId)
|
|
44
|
+
continue;
|
|
45
|
+
const found = Boolean(hit && parentOk && featureOk);
|
|
46
|
+
seen.set(ref, { ref, found, finished: found && FINISHED.has(hit.task.status) });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const phase = phaseByNumber.get(Number(phaseNum));
|
|
50
|
+
const featureOk = !featureNum || (phase && phase.featureId === featureByNumber.get(Number(featureNum))?.id);
|
|
51
|
+
const ref = `P${pad(Number(phaseNum))}${featureNum ? `(F${pad(Number(featureNum))})` : ""}`;
|
|
52
|
+
const found = Boolean(phase && featureOk);
|
|
53
|
+
const finished = found && phase.tasks.length > 0 && phase.tasks.every((task) => FINISHED.has(task.status));
|
|
54
|
+
seen.set(ref, { ref, found, finished });
|
|
55
|
+
}
|
|
56
|
+
return [...seen.values()];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Every on-hold task, ready-to-resume ones first. Pass `phaseIds` to limit
|
|
60
|
+
* the list to the phases a caller is about to act on.
|
|
61
|
+
*/
|
|
62
|
+
export function listOnHoldWork(features, phases, phaseIds) {
|
|
63
|
+
const items = [];
|
|
64
|
+
for (const phase of phases) {
|
|
65
|
+
if (phaseIds && !phaseIds.has(phase.id))
|
|
66
|
+
continue;
|
|
67
|
+
const phaseRef = formatPhaseRef(phase.number, featureNumberOfPhase(phase, features));
|
|
68
|
+
for (const task of phase.tasks) {
|
|
69
|
+
if (!ON_HOLD_STATUSES.has(task.status))
|
|
70
|
+
continue;
|
|
71
|
+
const { reason, at } = holdEntry(task);
|
|
72
|
+
const namedRefs = refsNamedIn(reason, phases, features, task.id);
|
|
73
|
+
items.push({
|
|
74
|
+
taskId: task.id,
|
|
75
|
+
ref: `${phaseRef}/T${pad(task.number)}`,
|
|
76
|
+
title: task.title,
|
|
77
|
+
status: task.status,
|
|
78
|
+
phaseId: phase.id,
|
|
79
|
+
phaseRef,
|
|
80
|
+
reason,
|
|
81
|
+
heldSince: at,
|
|
82
|
+
namedRefs,
|
|
83
|
+
readyToResume: namedRefs.length > 0 && namedRefs.every((named) => named.finished),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return items.sort((left, right) => Number(right.readyToResume) - Number(left.readyToResume));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The phases whose on-hold work matters now: phases where work has started
|
|
91
|
+
* (some task finished or in progress) but not every task is finished. A
|
|
92
|
+
* phase nobody has started yet keeps its on-hold tasks out of the way.
|
|
93
|
+
*/
|
|
94
|
+
export function startedOpenPhaseIds(phases) {
|
|
95
|
+
return new Set(phases
|
|
96
|
+
.filter((phase) => phase.tasks.some((task) => FINISHED.has(task.status) || task.status === "in-progress"))
|
|
97
|
+
.filter((phase) => phase.tasks.some((task) => !FINISHED.has(task.status)))
|
|
98
|
+
.map((phase) => phase.id));
|
|
99
|
+
}
|
|
100
|
+
const REASON_PREVIEW_CHARS = 160;
|
|
101
|
+
/**
|
|
102
|
+
* One bounded text block for any reply. Ready-to-resume tasks first; each
|
|
103
|
+
* line carries the reason so the agent can re-check it against the plan.
|
|
104
|
+
*/
|
|
105
|
+
export function renderOnHoldWork(items, limit = 8) {
|
|
106
|
+
if (items.length === 0)
|
|
107
|
+
return "";
|
|
108
|
+
const shown = items.slice(0, limit);
|
|
109
|
+
const lines = shown.map((item) => {
|
|
110
|
+
const reason = item.reason
|
|
111
|
+
? item.reason.length > REASON_PREVIEW_CHARS ? `${item.reason.slice(0, REASON_PREVIEW_CHARS).trimEnd()}…` : item.reason
|
|
112
|
+
: "(no reason recorded)";
|
|
113
|
+
const check = item.readyToResume
|
|
114
|
+
? ` — MAY BE READY: everything its reason names is finished (${item.namedRefs.map((named) => named.ref).join(", ")}); re-check and resume it if nothing else holds it`
|
|
115
|
+
: item.namedRefs.some((named) => !named.finished)
|
|
116
|
+
? ` — still waiting on ${item.namedRefs.filter((named) => !named.finished).map((named) => named.found ? named.ref : `${named.ref} (not found)`).join(", ")}`
|
|
117
|
+
: "";
|
|
118
|
+
return `- ${item.ref} — ${item.title} [${item.status}]${check}\n Reason: ${reason}`;
|
|
119
|
+
});
|
|
120
|
+
const more = items.length > shown.length ? `\n(${items.length - shown.length} more on hold; list them with the phase's task list.)` : "";
|
|
121
|
+
const ready = items.filter((item) => item.readyToResume).length;
|
|
122
|
+
return `On hold — check whether these can resume (${items.length} on hold${ready ? `, ${ready} may be ready` : ""}):\n${lines.join("\n")}${more}`;
|
|
123
|
+
}
|
|
@@ -8,4 +8,17 @@ export interface RuntimePackageVersion {
|
|
|
8
8
|
export declare function packageVersionFromModule(moduleUrlOrPath: string, expectedName: string): RuntimePackageVersion;
|
|
9
9
|
/** Resolve a dependency exactly as the caller loaded it, then read its manifest. */
|
|
10
10
|
export declare function resolvedPackageVersion(packageName: string, fromModuleUrl: string): RuntimePackageVersion;
|
|
11
|
+
/**
|
|
12
|
+
* A long-lived process (an MCP server a session started, a Pi extension)
|
|
13
|
+
* keeps running the code it loaded. After an upgrade it silently runs old
|
|
14
|
+
* code, so an already-fixed bug looks like it came back. This returns a
|
|
15
|
+
* checker that re-reads the loaded package's manifest — one small file, at
|
|
16
|
+
* most once per `intervalMs` — and returns a one-line notice while the
|
|
17
|
+
* version on disk differs from the one loaded. It never throws: an
|
|
18
|
+
* unreadable manifest means no notice.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createStaleProcessCheck(loaded: RuntimePackageVersion, { intervalMs, now }?: {
|
|
21
|
+
intervalMs?: number;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
}): () => string | undefined;
|
|
11
24
|
//# sourceMappingURL=package-version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../src/package-version.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAaD,wEAAwE;AACxE,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,GACnB,qBAAqB,CAqBvB;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,qBAAqB,CAGvB"}
|
|
1
|
+
{"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../src/package-version.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAaD,wEAAwE;AACxE,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,GACnB,qBAAqB,CAqBvB;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,qBAAqB,CAGvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,EAC7B,EAAE,UAAwB,EAAE,GAAsB,EAAE,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAAO,GACrG,MAAM,MAAM,GAAG,SAAS,CAmB1B"}
|
package/dist/package-version.js
CHANGED
|
@@ -33,3 +33,35 @@ export function resolvedPackageVersion(packageName, fromModuleUrl) {
|
|
|
33
33
|
const entryPath = createRequire(fromModuleUrl).resolve(packageName);
|
|
34
34
|
return packageVersionFromModule(entryPath, packageName);
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A long-lived process (an MCP server a session started, a Pi extension)
|
|
38
|
+
* keeps running the code it loaded. After an upgrade it silently runs old
|
|
39
|
+
* code, so an already-fixed bug looks like it came back. This returns a
|
|
40
|
+
* checker that re-reads the loaded package's manifest — one small file, at
|
|
41
|
+
* most once per `intervalMs` — and returns a one-line notice while the
|
|
42
|
+
* version on disk differs from the one loaded. It never throws: an
|
|
43
|
+
* unreadable manifest means no notice.
|
|
44
|
+
*/
|
|
45
|
+
export function createStaleProcessCheck(loaded, { intervalMs = 10 * 60_000, now = () => Date.now() } = {}) {
|
|
46
|
+
let checkedAt = Number.NEGATIVE_INFINITY;
|
|
47
|
+
let notice;
|
|
48
|
+
return () => {
|
|
49
|
+
if (!loaded.packageJsonPath)
|
|
50
|
+
return undefined;
|
|
51
|
+
const time = now();
|
|
52
|
+
if (time - checkedAt < intervalMs)
|
|
53
|
+
return notice;
|
|
54
|
+
checkedAt = time;
|
|
55
|
+
notice = undefined;
|
|
56
|
+
try {
|
|
57
|
+
const manifest = JSON.parse(readFileSync(loaded.packageJsonPath, "utf-8"));
|
|
58
|
+
if (typeof manifest.version === "string" && manifest.version.trim() && manifest.version !== loaded.version) {
|
|
59
|
+
notice = `⚠️ This session is running ${loaded.name} ${loaded.version}, but ${manifest.version} is now installed. Restart the session (in Claude Code: /mcp, then reconnect agent-plan) to use it; until then, fixes made after ${loaded.version} are not active here.`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
notice = undefined;
|
|
64
|
+
}
|
|
65
|
+
return notice;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type Project, type RequirementsDocument } from "./schema.js";
|
|
3
3
|
export declare const PROJECT_CONTEXT_DELIVERY_VERSION = 1;
|
|
4
|
-
export declare const DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS =
|
|
4
|
+
export declare const DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS = 32000;
|
|
5
5
|
export declare const ProjectContextReadAttestationSchema: z.ZodObject<{
|
|
6
6
|
sessionId: z.ZodString;
|
|
7
7
|
createdAt: z.ZodString;
|
|
@@ -375,9 +375,11 @@ export declare function renderProjectContext(snapshot: ProjectContextSnapshot):
|
|
|
375
375
|
* Every adapter (Pi, MCP, and any future harness) must deliver the same
|
|
376
376
|
* complete project-level context, so the reply is shaped once here instead
|
|
377
377
|
* of once per adapter — the same placement rule as handoff-reply.ts (AGENTS.md
|
|
378
|
-
* rule 4).
|
|
379
|
-
*
|
|
380
|
-
*
|
|
378
|
+
* rule 4). The snapshot is carried once, as rendered `text`; `structured`
|
|
379
|
+
* holds only the delivery status (complete or not, fingerprint, next
|
|
380
|
+
* actions). A host that shows the agent only structuredContent still gets
|
|
381
|
+
* the text: the MCP server folds every reply's text into its structured
|
|
382
|
+
* part (foldTextIntoStructured in plan-mcp).
|
|
381
383
|
*/
|
|
382
384
|
export interface ProjectContextReply {
|
|
383
385
|
text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-context.d.ts","sourceRoot":"","sources":["../src/project-context.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"project-context.d.ts","sourceRoot":"","sources":["../src/project-context.ts"],"names":[],"mappings":"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"}
|
package/dist/project-context.js
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { AcceptedDecisionSchema, DescriptionRefSchema, MacroTaskSchema, TimestampSchema, } from "./schema.js";
|
|
4
4
|
import { renderAcceptedDecisionsSection } from "./task-context.js";
|
|
5
5
|
export const PROJECT_CONTEXT_DELIVERY_VERSION = 1;
|
|
6
|
-
export const DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS =
|
|
6
|
+
export const DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS = 32_000;
|
|
7
7
|
export const ProjectContextReadAttestationSchema = z.object({
|
|
8
8
|
sessionId: z.string().min(1),
|
|
9
9
|
createdAt: TimestampSchema,
|
|
@@ -287,9 +287,8 @@ export function buildProjectContextLoadReply(delivery, maxChars) {
|
|
|
287
287
|
structured: {
|
|
288
288
|
loaded: true,
|
|
289
289
|
contextComplete: true,
|
|
290
|
-
project: delivery.snapshot.project,
|
|
291
|
-
requirements: delivery.snapshot.requirements,
|
|
292
290
|
fingerprint: delivery.evidence.fingerprint,
|
|
291
|
+
requirementCount: delivery.snapshot.requirements.length,
|
|
293
292
|
},
|
|
294
293
|
};
|
|
295
294
|
}
|
package/dist/recap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../src/recap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../src/recap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAOjD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,KAAK,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,GAAE,YAAiB,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgThH"}
|
package/dist/recap.js
CHANGED
|
@@ -2,6 +2,7 @@ import { formatPhaseRef, formatTwoDigitNumber } from "./naming.js";
|
|
|
2
2
|
import { buildPhaseWorkMap } from "./task-context.js";
|
|
3
3
|
import { recommendNextTask } from "./task-selection.js";
|
|
4
4
|
import { listOpenPhaseWork, openPhaseWorkLines } from "./stranded-phase.js";
|
|
5
|
+
import { listOnHoldWork, renderOnHoldWork, startedOpenPhaseIds } from "./on-hold.js";
|
|
5
6
|
const fref = (n) => `F${formatTwoDigitNumber(n)}`;
|
|
6
7
|
const tref = (n) => `T${formatTwoDigitNumber(n)}`;
|
|
7
8
|
/**
|
|
@@ -85,6 +86,11 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
85
86
|
if (openPhaseWorkText) {
|
|
86
87
|
lines.push("", italian ? "In-progress: fasi con lavoro rimanente (ordine di priorità):" : "In-progress phases with work left (priority order):", openPhaseWorkText);
|
|
87
88
|
}
|
|
89
|
+
// Parked work in phases already under way, with its reason and whether
|
|
90
|
+
// what it waits on has finished — the same list task_recommend shows.
|
|
91
|
+
const onHoldText = renderOnHoldWork(listOnHoldWork(feats, phases, startedOpenPhaseIds(phases)), 5);
|
|
92
|
+
if (onHoldText)
|
|
93
|
+
lines.push("", onHoldText);
|
|
88
94
|
if (activeTasks.length > 1) {
|
|
89
95
|
const conflicts = activeTasks.map(({ phase, task }) => {
|
|
90
96
|
const feature = feats.find((entry) => entry.id === phase.featureId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recommend-reply.d.ts","sourceRoot":"","sources":["../src/recommend-reply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,sBAAsB,EAAiB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"recommend-reply.d.ts","sourceRoot":"","sources":["../src/recommend-reply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,sBAAsB,EAAiB,MAAM,qBAAqB,CAAC;AAGjF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED;wDACwD;AACxD,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAwBD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,sBAAsB,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,cAAc,CA8D5G"}
|
package/dist/recommend-reply.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { featureNumberOfPhase, formatPhaseRef } from "./naming.js";
|
|
2
|
+
import { renderOnHoldWork } from "./on-hold.js";
|
|
2
3
|
function compositeTaskRef(candidate, features) {
|
|
3
4
|
const featureNumber = featureNumberOfPhase(candidate.phase, features);
|
|
4
5
|
return `${formatPhaseRef(candidate.phase.number, featureNumber)}/T${String(candidate.task.number).padStart(3, "0")}`;
|
|
@@ -32,7 +33,13 @@ function renderClaimsText(claims) {
|
|
|
32
33
|
*/
|
|
33
34
|
export function buildRecommendationReply(result, features) {
|
|
34
35
|
const { selection } = result;
|
|
35
|
-
const
|
|
36
|
+
const onHoldBlock = renderOnHoldWork(result.onHold ?? []);
|
|
37
|
+
const claimsText = `${renderClaimsText(result.claims)}${onHoldBlock ? `\n\n${onHoldBlock}` : ""}`;
|
|
38
|
+
// Counts and refs only: the reasons are in the text.
|
|
39
|
+
const onHoldSummary = {
|
|
40
|
+
total: (result.onHold ?? []).length,
|
|
41
|
+
readyToResume: (result.onHold ?? []).filter((item) => item.readyToResume).map((item) => item.ref),
|
|
42
|
+
};
|
|
36
43
|
if (!selection.candidate) {
|
|
37
44
|
const activeCandidates = selection.activeCandidates ?? [];
|
|
38
45
|
return {
|
|
@@ -46,6 +53,7 @@ export function buildRecommendationReply(result, features) {
|
|
|
46
53
|
nextPhase: result.nextPhase,
|
|
47
54
|
nextTask: result.nextTask,
|
|
48
55
|
claims: result.claims,
|
|
56
|
+
onHold: onHoldSummary,
|
|
49
57
|
selection: {
|
|
50
58
|
kind: selection.kind,
|
|
51
59
|
reason: selection.reason,
|
|
@@ -73,6 +81,7 @@ export function buildRecommendationReply(result, features) {
|
|
|
73
81
|
nextPhase: result.nextPhase,
|
|
74
82
|
nextTask: result.nextTask,
|
|
75
83
|
claims: result.claims,
|
|
84
|
+
onHold: onHoldSummary,
|
|
76
85
|
selection: {
|
|
77
86
|
kind: selection.kind,
|
|
78
87
|
reason: selection.reason,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-context.d.ts","sourceRoot":"","sources":["../src/task-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAIhG,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AACzD,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,wBAAwB,EAAE,OAAO,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,sHAAsH;IACtH,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAC;CAC3C;AAOD,8EAA8E;AAC9E,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,SAAS,GAAG,MAAM,CAgB/G;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,uBAAuB,EAAE,EACjC,QAAQ,SAAsC,GAC7C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAmB1E;AAQD,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,EACZ,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,EACtB,QAAQ,SAA2B,GAClC,YAAY,
|
|
1
|
+
{"version":3,"file":"task-context.d.ts","sourceRoot":"","sources":["../src/task-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAIhG,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AACzD,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,wBAAwB,EAAE,OAAO,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,sHAAsH;IACtH,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAC;CAC3C;AAOD,8EAA8E;AAC9E,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,SAAS,GAAG,MAAM,CAgB/G;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,uBAAuB,EAAE,EACjC,QAAQ,SAAsC,GAC7C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAmB1E;AAQD,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,EACZ,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,EACtB,QAAQ,SAA2B,GAClC,YAAY,CA6Dd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,kBAAkB,GAAE,WAAW,EAAO,EACtC,mBAAmB,GAAE,WAAW,EAAO,EACvC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,mBAAmB,CAAC,EAC7C,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,GAC3C,MAAM,CA0DR"}
|
package/dist/task-context.js
CHANGED
|
@@ -74,19 +74,29 @@ export function buildPhaseWorkMap(phase, featureNumber, currentTaskId, maxChars
|
|
|
74
74
|
remainingCapabilityOwner: !["done", "canceled", "rejected"].includes(task.status),
|
|
75
75
|
current: task.id === currentTaskId,
|
|
76
76
|
}));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
// Open work first, in full: that is what the agent must not duplicate and
|
|
78
|
+
// may pick up. Finished tasks follow as one line each — enough to see a
|
|
79
|
+
// capability already exists — so a long phase does not spend the reply on
|
|
80
|
+
// closed work, and the budget cuts finished lines before open ones.
|
|
81
|
+
const open = entries.filter((entry) => entry.remainingCapabilityOwner);
|
|
82
|
+
const closed = entries.filter((entry) => !entry.remainingCapabilityOwner);
|
|
83
|
+
const header = `Phase work map — canonical sibling capability ownership (${open.length} open, ${closed.length} finished; open first, priority order):`;
|
|
84
|
+
const rendered = [
|
|
85
|
+
...open.map((entry) => ({
|
|
86
|
+
entry,
|
|
87
|
+
block: [
|
|
88
|
+
`- ${entry.ref}${entry.current ? " (current)" : ""} — ${entry.title} [priority ${entry.priority}; ${entry.status}]`,
|
|
89
|
+
` Goal: ${entry.conciseGoal}`,
|
|
90
|
+
` Depends on: ${entry.dependencies.length > 0 ? entry.dependencies.join(", ") : "None."}`,
|
|
91
|
+
` Ownership: ${entry.ref} owns this remaining capability; do not duplicate it in another task.`,
|
|
92
|
+
].join("\n"),
|
|
93
|
+
})),
|
|
94
|
+
...closed.map((entry, index) => ({
|
|
95
|
+
entry,
|
|
96
|
+
block: `${index === 0 ? "Finished (already built; do not redo):\n" : ""}- ${entry.ref} — ${entry.title} [${entry.status}]`,
|
|
97
|
+
})),
|
|
98
|
+
];
|
|
99
|
+
const truncationNotice = (withheldCount) => `[Phase work map truncated for transport safety: ${withheldCount} ${withheldCount === 1 ? "entry" : "entries"} withheld, finished ones first. Read the canonical phase and task full views before proposing work; place deeper context under .planner/docs/.]`;
|
|
90
100
|
// Reserve space using the full task count as the withheld-count placeholder: since the
|
|
91
101
|
// eventual withheld count can never exceed it, this reservation is always a safe upper
|
|
92
102
|
// bound on the final notice length, so shrinking it afterward cannot overflow maxChars.
|
package/dist/task-selection.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Feature, Phase, Task, WorkDeviation } from "./schema.js";
|
|
2
|
+
import { type OnHoldTask } from "./on-hold.js";
|
|
2
3
|
export type RecommendationClaimKind = "recommendation" | "advisory" | "conflict" | "insufficient";
|
|
3
|
-
export type RecommendationClaimSource = "priority" | "active-task" | "resume-deviation" | "dependency-ready" | "handoff" | "archived-handoff";
|
|
4
|
+
export type RecommendationClaimSource = "priority" | "active-task" | "resume-deviation" | "dependency-ready" | "handoff" | "archived-handoff" | "on-hold";
|
|
4
5
|
export interface RecommendationClaim {
|
|
5
6
|
kind: RecommendationClaimKind;
|
|
6
7
|
source: RecommendationClaimSource;
|
|
@@ -46,6 +47,8 @@ export interface NextWorkRecommendation {
|
|
|
46
47
|
nextPhase: WorkSummary | null;
|
|
47
48
|
nextTask: WorkSummary | null;
|
|
48
49
|
claims: RecommendationClaim[];
|
|
50
|
+
/** On-hold tasks in started phases, ready-to-resume first (see on-hold.ts). */
|
|
51
|
+
onHold: OnHoldTask[];
|
|
49
52
|
}
|
|
50
53
|
export interface TaskRecommendation {
|
|
51
54
|
kind: TaskRecommendationKind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-selection.d.ts","sourceRoot":"","sources":["../src/task-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"task-selection.d.ts","sourceRoot":"","sources":["../src/task-selection.ts"],"names":[],"mappings":"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"}
|
package/dist/task-selection.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { listOnHoldWork, startedOpenPhaseIds } from "./on-hold.js";
|
|
1
2
|
const hardUnavailable = new Set(["blocked", "deferred", "canceled", "rejected"]);
|
|
2
3
|
const terminal = new Set(["done", "canceled", "rejected"]);
|
|
3
4
|
const priority = (entity) => entity.priority != null ? entity.priority : Number.MAX_SAFE_INTEGER;
|
|
@@ -15,8 +16,19 @@ const selectionClaimKind = (selectionKind) => {
|
|
|
15
16
|
return "insufficient";
|
|
16
17
|
return "advisory";
|
|
17
18
|
};
|
|
18
|
-
const buildRecommendationClaims = (selection, evidence = {}, dependencyReadyCandidates = []) => {
|
|
19
|
+
const buildRecommendationClaims = (selection, evidence = {}, dependencyReadyCandidates = [], onHold = []) => {
|
|
19
20
|
const claims = [];
|
|
21
|
+
// Parked work whose every named blocker is finished competes with fresh
|
|
22
|
+
// work; listed before it so the claim limit never drops it.
|
|
23
|
+
const readyOnHold = onHold.filter((item) => item.readyToResume).map((item) => ({
|
|
24
|
+
kind: "advisory",
|
|
25
|
+
source: "on-hold",
|
|
26
|
+
ref: item.ref,
|
|
27
|
+
title: item.title,
|
|
28
|
+
reason: `On hold (${item.status}); everything its reason names is finished (${item.namedRefs.map((named) => named.ref).join(", ")}), so it may be ready. Re-check it and resume it if nothing else holds it.`,
|
|
29
|
+
taskId: item.taskId,
|
|
30
|
+
phaseId: item.phaseId,
|
|
31
|
+
}));
|
|
20
32
|
if (selection.candidate) {
|
|
21
33
|
claims.push({
|
|
22
34
|
kind: selectionClaimKind(selection.kind),
|
|
@@ -29,6 +41,7 @@ const buildRecommendationClaims = (selection, evidence = {}, dependencyReadyCand
|
|
|
29
41
|
...(selection.candidate.feature?.id ? { featureId: selection.candidate.feature.id } : {}),
|
|
30
42
|
});
|
|
31
43
|
}
|
|
44
|
+
claims.push(...readyOnHold);
|
|
32
45
|
for (const candidate of dependencyReadyCandidates) {
|
|
33
46
|
claims.push({
|
|
34
47
|
kind: "advisory",
|
|
@@ -251,6 +264,12 @@ export function recommendNextWork(features, phases, deviations = [], currentPhas
|
|
|
251
264
|
&& !(feature && hardUnavailable.has(feature.status))
|
|
252
265
|
&& task.dependsOn.every((id) => taskById.get(id)?.status === "done"))
|
|
253
266
|
.sort((left, right) => compare(left.feature ?? { priority: 0, number: 0 }, right.feature ?? { priority: 0, number: 0 }) || compare(left.phase, right.phase) || compare(left.task, right.task));
|
|
267
|
+
// On-hold work in phases already under way, plus the phase the
|
|
268
|
+
// recommendation is about to send the agent into.
|
|
269
|
+
const onHoldPhaseIds = startedOpenPhaseIds(phases);
|
|
270
|
+
if (selection.candidate)
|
|
271
|
+
onHoldPhaseIds.add(selection.candidate.phase.id);
|
|
272
|
+
const onHold = listOnHoldWork(features, phases, onHoldPhaseIds);
|
|
254
273
|
const activeTask = selection.kind === "active" && selection.candidate
|
|
255
274
|
? summarizeTask(selection.candidate.task)
|
|
256
275
|
: null;
|
|
@@ -260,7 +279,8 @@ export function recommendNextWork(features, phases, deviations = [], currentPhas
|
|
|
260
279
|
nextFeature: selection.candidate?.feature ? summarizeFeature(selection.candidate.feature) : null,
|
|
261
280
|
nextPhase: selection.candidate ? summarizePhase(selection.candidate.phase) : null,
|
|
262
281
|
nextTask: selection.candidate ? summarizeTask(selection.candidate.task) : null,
|
|
263
|
-
claims: buildRecommendationClaims(selection, evidence, dependencyReadyCandidates),
|
|
282
|
+
claims: buildRecommendationClaims(selection, evidence, dependencyReadyCandidates, onHold),
|
|
283
|
+
onHold,
|
|
264
284
|
};
|
|
265
285
|
}
|
|
266
286
|
/**
|
package/package.json
CHANGED