@agent-plan/core 0.2.25 → 0.2.27
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/description-freshness.d.ts +37 -0
- package/dist/description-freshness.d.ts.map +1 -0
- package/dist/description-freshness.js +84 -0
- package/dist/display-status.d.ts +3 -3
- package/dist/display-status.d.ts.map +1 -1
- package/dist/display-status.js +5 -4
- package/dist/handoff-context.d.ts +222 -1
- package/dist/handoff-context.d.ts.map +1 -1
- package/dist/handoff-context.js +461 -11
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/naming.d.ts +3 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +7 -0
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -1
- package/dist/package-version.js +1 -1
- package/dist/payload-fallback.d.ts +38 -0
- package/dist/payload-fallback.d.ts.map +1 -0
- package/dist/payload-fallback.js +79 -0
- package/dist/plan-store.d.ts +192 -36
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +1048 -126
- package/dist/planner-rules.d.ts.map +1 -1
- package/dist/planner-rules.js +9 -3
- package/dist/planner-skill.d.ts +24 -0
- package/dist/planner-skill.d.ts.map +1 -0
- package/dist/planner-skill.js +113 -0
- package/dist/project-context-migration.d.ts +47 -0
- package/dist/project-context-migration.d.ts.map +1 -0
- package/dist/project-context-migration.js +168 -0
- package/dist/read-tracking.d.ts +47 -13
- package/dist/read-tracking.d.ts.map +1 -1
- package/dist/read-tracking.js +88 -33
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +34 -9
- package/dist/refs.d.ts +6 -1
- package/dist/refs.d.ts.map +1 -1
- package/dist/refs.js +25 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +24 -2
- package/dist/requirement-macro-tasks.d.ts +18 -0
- package/dist/requirement-macro-tasks.d.ts.map +1 -0
- package/dist/requirement-macro-tasks.js +55 -0
- package/dist/runtime-diagnostics.d.ts +34 -0
- package/dist/runtime-diagnostics.d.ts.map +1 -0
- package/dist/runtime-diagnostics.js +39 -0
- package/dist/schema.d.ts +1575 -290
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +89 -4
- package/dist/task-context.d.ts +41 -2
- package/dist/task-context.d.ts.map +1 -1
- package/dist/task-context.js +102 -4
- package/dist/task-selection.d.ts +44 -1
- package/dist/task-selection.d.ts.map +1 -1
- package/dist/task-selection.js +158 -7
- package/dist/task-start-outcome.d.ts +1 -1
- package/dist/task-start-outcome.d.ts.map +1 -1
- package/dist/task-start-outcome.js +1 -0
- package/dist/write-coordination.d.ts +27 -0
- package/dist/write-coordination.d.ts.map +1 -0
- package/dist/write-coordination.js +223 -0
- package/package.json +3 -1
- package/planner-skill.md +226 -0
- package/skills/grill-me/SKILL.md +10 -0
package/dist/read-tracking.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session-scoped
|
|
2
|
+
* Session-scoped context-read enforcement for agent lifecycle operations.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Task/phase/feature reads and linked requirements remain explicit, but fresh
|
|
5
|
+
* in-session reads may be performed in any order. Persisted sessionInfo
|
|
6
|
+
* attestations may satisfy later checks while the entity's current revision
|
|
7
|
+
* remains at or before the attested timestamp.
|
|
8
8
|
*/
|
|
9
9
|
const DEFAULT_SESSION_ID = "__default__";
|
|
10
10
|
const newState = () => ({
|
|
@@ -51,6 +51,40 @@ export function markFeatureRead(featureId) {
|
|
|
51
51
|
export function markFeatureReadForSessionId(sessionId, featureId) {
|
|
52
52
|
markFeatureReadForSession(sessionId, featureId);
|
|
53
53
|
}
|
|
54
|
+
function deliveredAcceptedDecisionField(content, value) {
|
|
55
|
+
const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
56
|
+
return lines.every((line) => content.includes(line));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record a canonical feature/phase/task full read only after its visible tool
|
|
60
|
+
* content contains every field of every Accepted Decision. This prevents an
|
|
61
|
+
* adapter from certifying a title-only or otherwise incomplete decision read.
|
|
62
|
+
*/
|
|
63
|
+
export function markCanonicalFullReadForSessionId(sessionId, kind, entity, deliveredContent) {
|
|
64
|
+
const acceptedDecisions = entity.acceptedDecisions ?? [];
|
|
65
|
+
const omitted = [
|
|
66
|
+
...(!deliveredContent.includes(`Accepted Decisions (${acceptedDecisions.length}):`) ? ["context-marker"] : []),
|
|
67
|
+
...acceptedDecisions.flatMap((acceptedDecision) => [
|
|
68
|
+
["id", acceptedDecision.id],
|
|
69
|
+
["title", acceptedDecision.title],
|
|
70
|
+
["decision", acceptedDecision.decision],
|
|
71
|
+
["rationale", acceptedDecision.rationale],
|
|
72
|
+
["implementationNotes", acceptedDecision.implementationNotes],
|
|
73
|
+
["acceptedAt", acceptedDecision.acceptedAt],
|
|
74
|
+
]
|
|
75
|
+
.filter(([, value]) => !deliveredAcceptedDecisionField(deliveredContent, value))
|
|
76
|
+
.map(([field]) => `${acceptedDecision.id}.${field}`)),
|
|
77
|
+
];
|
|
78
|
+
if (omitted.length > 0) {
|
|
79
|
+
throw new Error(`Cannot attest ${kind} ${entity.id} as fully read: omitted Accepted Decision fields: ${omitted.join(", ")}.`);
|
|
80
|
+
}
|
|
81
|
+
if (kind === "feature")
|
|
82
|
+
markFeatureReadForSession(sessionId, entity.id);
|
|
83
|
+
else if (kind === "phase")
|
|
84
|
+
markPhaseReadForSession(sessionId, entity.id);
|
|
85
|
+
else
|
|
86
|
+
markTaskReadForSession(sessionId, entity.id);
|
|
87
|
+
}
|
|
54
88
|
/** Record a full phase read in the default compatibility session. */
|
|
55
89
|
export function markPhaseRead(phaseId, _featureId) {
|
|
56
90
|
markPhaseReadForSession(DEFAULT_SESSION_ID, phaseId);
|
|
@@ -77,19 +111,14 @@ export function markRequirementReadForSessionId(sessionId, requirementId) {
|
|
|
77
111
|
}
|
|
78
112
|
function orderedEligibility(sessionId, taskId, phaseId, featureId) {
|
|
79
113
|
const state = stateFor(sessionId);
|
|
80
|
-
|
|
81
|
-
if (taskSequence === undefined) {
|
|
114
|
+
if (!state.tasks.has(taskId)) {
|
|
82
115
|
return { eligible: false, reason: "Read this exact task with full=true first." };
|
|
83
116
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return { eligible: false, reason: "After reading the task, read its parent phase with full=true." };
|
|
117
|
+
if (!state.phases.has(phaseId)) {
|
|
118
|
+
return { eligible: false, reason: "Read this task's parent phase with full=true." };
|
|
87
119
|
}
|
|
88
|
-
if (!featureId)
|
|
89
|
-
return { eligible:
|
|
90
|
-
const featureSequence = state.features.get(featureId);
|
|
91
|
-
if (featureSequence === undefined || featureSequence <= phaseSequence) {
|
|
92
|
-
return { eligible: false, reason: "After reading the phase, read its parent feature with full=true." };
|
|
120
|
+
if (featureId && !state.features.has(featureId)) {
|
|
121
|
+
return { eligible: false, reason: "Read this phase's parent feature with full=true." };
|
|
93
122
|
}
|
|
94
123
|
return { eligible: true, reason: "" };
|
|
95
124
|
}
|
|
@@ -118,13 +147,14 @@ function persistedEligibility(input) {
|
|
|
118
147
|
}
|
|
119
148
|
function requiredReadReason(requiredReads) {
|
|
120
149
|
const labels = requiredReads.map((read) => `${read.kind} ${read.id} (${read.state})`);
|
|
121
|
-
return `Read required context only for: ${labels.join(", ")}.
|
|
150
|
+
return `Read required context only for: ${labels.join(", ")}. Perform only these reads, in any order within the current session, then retry.`;
|
|
122
151
|
}
|
|
123
152
|
/**
|
|
124
|
-
* Combine valid persisted attestations with fresh in-memory reads.
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
153
|
+
* Combine valid persisted attestations with fresh in-memory reads. An entity read
|
|
154
|
+
* in the current session satisfies eligibility regardless of sequence order,
|
|
155
|
+
* so agents can read task, phase, and feature in any order; only persisted
|
|
156
|
+
* attestations are checked for revision freshness (stale) and missing entities
|
|
157
|
+
* remain reported as missing.
|
|
128
158
|
*/
|
|
129
159
|
export function contextReadEligibilityForSession(input) {
|
|
130
160
|
const state = stateFor(input.sessionId);
|
|
@@ -137,26 +167,23 @@ export function contextReadEligibilityForSession(input) {
|
|
|
137
167
|
}
|
|
138
168
|
const phaseStored = storedReadState(input.phase, input.sessionId, "phase");
|
|
139
169
|
const phaseSequence = state.phases.get(input.phaseId);
|
|
140
|
-
const
|
|
141
|
-
&& (taskStored === "valid" || (taskSequence !== undefined && phaseSequence > taskSequence));
|
|
142
|
-
const phaseReady = phaseStored === "valid" || phaseReadInOrder;
|
|
170
|
+
const phaseReady = phaseStored === "valid" || phaseSequence !== undefined;
|
|
143
171
|
if (!phaseReady) {
|
|
144
172
|
requiredReads.push({
|
|
145
173
|
kind: "phase",
|
|
146
174
|
id: input.phaseId,
|
|
147
|
-
state: phaseStored === "stale" ? "stale" :
|
|
175
|
+
state: phaseStored === "stale" ? "stale" : "missing",
|
|
148
176
|
});
|
|
149
177
|
}
|
|
150
178
|
if (input.featureId) {
|
|
151
179
|
const featureStored = storedReadState(input.feature, input.sessionId, "feature");
|
|
152
180
|
const featureSequence = state.features.get(input.featureId);
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
if (featureStored !== "valid" && !featureReadInOrder) {
|
|
181
|
+
const featureReady = featureStored === "valid" || featureSequence !== undefined;
|
|
182
|
+
if (!featureReady) {
|
|
156
183
|
requiredReads.push({
|
|
157
184
|
kind: "feature",
|
|
158
185
|
id: input.featureId,
|
|
159
|
-
state: featureStored === "stale" ? "stale" :
|
|
186
|
+
state: featureStored === "stale" ? "stale" : "missing",
|
|
160
187
|
});
|
|
161
188
|
}
|
|
162
189
|
}
|
|
@@ -164,6 +191,19 @@ export function contextReadEligibilityForSession(input) {
|
|
|
164
191
|
return { eligible: true, reason: "" };
|
|
165
192
|
return { eligible: false, reason: requiredReadReason(requiredReads), requiredReads };
|
|
166
193
|
}
|
|
194
|
+
export function projectGuidelinesReadStateForSession(project, sessionId) {
|
|
195
|
+
const guidelines = project.projectGuidelines;
|
|
196
|
+
const content = guidelines?.content?.trim() ?? "";
|
|
197
|
+
if (!content)
|
|
198
|
+
return "not-required";
|
|
199
|
+
const entry = guidelines?.sessionInfo?.find((candidate) => candidate.sessionId === sessionId);
|
|
200
|
+
if (!entry)
|
|
201
|
+
return "missing";
|
|
202
|
+
const updatedAt = guidelines?.updatedAt?.trim() ?? "";
|
|
203
|
+
if (updatedAt && updatedAt > entry.createdAt)
|
|
204
|
+
return "stale";
|
|
205
|
+
return "valid";
|
|
206
|
+
}
|
|
167
207
|
/** Return true only when the persisted attestation covers the current revisions. */
|
|
168
208
|
export function hasValidSessionAttestation(input) {
|
|
169
209
|
return persistedEligibility(input);
|
|
@@ -178,12 +218,27 @@ export function hasReadParents(featureId, phaseId) {
|
|
|
178
218
|
return state.phases.has(phaseId) && (!featureId || state.features.has(featureId));
|
|
179
219
|
}
|
|
180
220
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
221
|
+
* Report the exact linked requirements whose delivered read evidence is missing
|
|
222
|
+
* or stale for this session. A broad requirement inventory must not call the
|
|
223
|
+
* mark function; only a target-scoped response that actually includes the
|
|
224
|
+
* linked requirement records may create fresh in-memory evidence.
|
|
183
225
|
*/
|
|
184
|
-
export function
|
|
226
|
+
export function requirementReadEligibilityForSession(sessionId, requirementIds, requirements = []) {
|
|
185
227
|
const state = stateFor(sessionId);
|
|
186
|
-
|
|
228
|
+
const requiredReads = [];
|
|
229
|
+
for (const id of [...new Set(requirementIds)]) {
|
|
230
|
+
const stored = storedReadState(requirements.find((requirement) => requirement.id === id), sessionId, "requirement");
|
|
231
|
+
if (state.requirements.has(id) || stored === "valid")
|
|
232
|
+
continue;
|
|
233
|
+
requiredReads.push({ kind: "requirement", id, state: stored === "stale" ? "stale" : "missing" });
|
|
234
|
+
}
|
|
235
|
+
if (requiredReads.length === 0)
|
|
236
|
+
return { eligible: true, reason: "" };
|
|
237
|
+
return { eligible: false, reason: requiredReadReason(requiredReads), requiredReads };
|
|
238
|
+
}
|
|
239
|
+
/** Whether every linked requirement has fresh delivered read evidence. */
|
|
240
|
+
export function hasReadRequirementsForSession(sessionId, requirementIds, requirements = []) {
|
|
241
|
+
return requirementReadEligibilityForSession(sessionId, requirementIds, requirements).eligible;
|
|
187
242
|
}
|
|
188
243
|
/** Legacy requirement check for the default compatibility session. */
|
|
189
244
|
export function hasReadRequirements(requirementIds) {
|
|
@@ -208,7 +263,7 @@ export function parentReadAdvisory(featureId, phaseId) {
|
|
|
208
263
|
const state = stateFor(DEFAULT_SESSION_ID);
|
|
209
264
|
if (state.phases.has(phaseId) && (!featureId || state.features.has(featureId)))
|
|
210
265
|
return "";
|
|
211
|
-
return "\n\n⚠️ READ REQUIRED before proceeding: read the parent phase and feature with full=true.";
|
|
266
|
+
return "\n\n⚠️ READ REQUIRED before proceeding: read only the missing parent phase and feature with full=true.";
|
|
212
267
|
}
|
|
213
268
|
/** Advisory text for the separate linked-requirements gate. */
|
|
214
269
|
export function requirementReadAdvisory(requirementIds) {
|
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;AAIjD;;;;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,CA4QhH"}
|
package/dist/recap.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatPhaseRef, formatTwoDigitNumber } from "./naming.js";
|
|
2
|
+
import { buildPhaseWorkMap } from "./task-context.js";
|
|
2
3
|
const fref = (n) => `F${formatTwoDigitNumber(n)}`;
|
|
3
4
|
const tref = (n) => `T${formatTwoDigitNumber(n)}`;
|
|
4
5
|
/**
|
|
@@ -32,7 +33,8 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
32
33
|
const activeP = phases.filter((x) => x.status === "in-progress" || x.status === "discovery").length;
|
|
33
34
|
const totalT = allTasks.length;
|
|
34
35
|
const doneT = allTasks.filter(({ task }) => task.status === "done").length;
|
|
35
|
-
const
|
|
36
|
+
const activeTasks = allTasks.filter(({ task }) => task.status === "in-progress");
|
|
37
|
+
const activeT = activeTasks.length;
|
|
36
38
|
const checkpointedTasks = allTasks.filter(({ task }) => !["done", "canceled", "rejected"].includes(task.status) && task.pauseSnapshot);
|
|
37
39
|
const checkpointedT = checkpointedTasks.length;
|
|
38
40
|
const pendingDeviation = [...plan.project.workDeviations]
|
|
@@ -50,8 +52,9 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
50
52
|
// Plan is fully complete: there is work and all of it is done, nothing active.
|
|
51
53
|
// (totalT > 0 guards the empty/unstarted case from looking "complete".)
|
|
52
54
|
const planComplete = totalT > 0 && doneT === totalT && doneP === totalP && doneF === totalF;
|
|
53
|
-
// Current focus
|
|
54
|
-
|
|
55
|
+
// Current focus is authoritative only when exactly one task is in progress.
|
|
56
|
+
// Multiple active tasks are an explicit conflict, never a reason to pick the first silently.
|
|
57
|
+
const focusTask = activeTasks.length === 1 ? activeTasks[0] : undefined;
|
|
55
58
|
const focusPhase = focusTask?.phase;
|
|
56
59
|
const focusFeature = focusPhase ? feats.find((x) => x.id === focusPhase.featureId) : undefined;
|
|
57
60
|
const italian = (plan.project.chatLanguage || "").toLowerCase().includes("ital");
|
|
@@ -67,10 +70,20 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
67
70
|
lines.push(italian ? "## Ripresa planner" : "## Planner recap");
|
|
68
71
|
const name = plan.project.name || "(unnamed project)";
|
|
69
72
|
lines.push(`${italian ? "Progetto" : "Project"}: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
|
|
73
|
+
lines.push("", "## Project Guidelines", plan.project.projectGuidelines.content.trim() || "No project guidelines set.");
|
|
70
74
|
lines.push(italian
|
|
71
75
|
? `Avanzamento: feature ${doneF}/${totalF} completate (${activeF} attive) · fasi ${doneP}/${totalP} completate (${activeP} attive) · task ${doneT}/${totalT} completati (${activeT} attivi, ${checkpointedT} con checkpoint)`
|
|
72
76
|
: `Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active, ${checkpointedT} with checkpoints)`);
|
|
73
|
-
if (
|
|
77
|
+
if (activeTasks.length > 1) {
|
|
78
|
+
const conflicts = activeTasks.map(({ phase, task }) => {
|
|
79
|
+
const feature = feats.find((entry) => entry.id === phase.featureId);
|
|
80
|
+
return `${formatPhaseRef(phase.number, feature?.number)}/${tref(task.number)} — ${task.title}`;
|
|
81
|
+
});
|
|
82
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "CONFLITTO TASK ATTIVI" : "ACTIVE TASK CONFLICT"} — ${activeTasks.length} ${italian ? "task risultano in-progress" : "tasks are in progress"}: ${conflicts.join("; ")}`, "", italian
|
|
83
|
+
? `Esegui ${cmd("task_recommend", "planner-task-recommend")} e riconcilia esplicitamente il conflitto prima di dichiarare un focus.`
|
|
84
|
+
: `Run ${cmd("task_recommend", "planner-task-recommend")} and explicitly reconcile the conflict before claiming a current focus.`);
|
|
85
|
+
}
|
|
86
|
+
else if (focusTask && focusPhase) {
|
|
74
87
|
const fr = focusFeature ? fref(focusFeature.number) : "?";
|
|
75
88
|
const pr = formatPhaseRef(focusPhase.number, focusFeature?.number);
|
|
76
89
|
const tr = tref(focusTask.task.number);
|
|
@@ -89,7 +102,7 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
89
102
|
}
|
|
90
103
|
else if (handoffs.length > 0) {
|
|
91
104
|
const top = handoffs[0];
|
|
92
|
-
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "nessun task attivo" : "no active task"} · ${italian ? "handoff pendente più recente" : "most recent pending handoff"}: ${top.compositeRef} — "${top.firstLine}"`);
|
|
105
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "nessun task attivo (verificato dagli stati persistiti di tutti i task)" : "no active task (verified from all persisted task statuses)"} · ${italian ? "handoff pendente più recente" : "most recent pending handoff"}: ${top.compositeRef} — "${top.firstLine}" (${top.resumeReady ? "resume-ready" : italian ? "verifica read-back richiesta" : "read-back verification required"})`);
|
|
93
106
|
}
|
|
94
107
|
else if (planComplete) {
|
|
95
108
|
lines.push(italian
|
|
@@ -97,7 +110,14 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
97
110
|
: "Current focus: plan complete — all features/phases/tasks are done.");
|
|
98
111
|
}
|
|
99
112
|
else {
|
|
100
|
-
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "nessun task attivo — rivedi il piano e scegli il prossimo task concreto" : "no active task — review the plan and pick the next concrete task"}`);
|
|
113
|
+
lines.push(`${italian ? "Focus corrente" : "Current focus"}: ${italian ? "nessun task attivo (verificato dagli stati persistiti di tutti i task) — rivedi il piano e scegli il prossimo task concreto" : "no active task (verified from all persisted task statuses) — review the plan and pick the next concrete task"}`);
|
|
114
|
+
}
|
|
115
|
+
const orientation = focusTask ?? pendingResume ?? latestStandaloneCheckpoint;
|
|
116
|
+
if (orientation) {
|
|
117
|
+
const feature = feats.find((entry) => entry.id === orientation.phase.featureId);
|
|
118
|
+
lines.push("", buildPhaseWorkMap(orientation.phase, feature?.number, orientation.task.id, 4_000).content, italian
|
|
119
|
+
? "Prima di proporre nuovo lavoro, rileggi la fase canonica e il task fratello pertinente: non duplicare capability già assegnate."
|
|
120
|
+
: "Before proposing new work, reread the canonical phase and the relevant sibling task: do not duplicate an already-owned capability.");
|
|
101
121
|
}
|
|
102
122
|
// Next step: the phase handoff (phase.handoff) is the authoritative,
|
|
103
123
|
// actively-managed resume context. When a handoff is pending, point to it
|
|
@@ -119,9 +139,14 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
119
139
|
: `Resume advisory: evaluate the newest checkpoint, ${ref}, with ${taskStartCmd} before deciding whether to return to it or start other work.`);
|
|
120
140
|
}
|
|
121
141
|
else if (handoffs.length > 0) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
const unverified = handoffs.filter((handoff) => !handoff.resumeReady);
|
|
143
|
+
lines.push(unverified.length > 0
|
|
144
|
+
? (italian
|
|
145
|
+
? `Prossimo step: ${unverified.length} handoff candidato richiede ancora verifica read-back; non trattarlo come completo o autorevole finché handoff_verify non restituisce resumeReady=true.`
|
|
146
|
+
: `Next step: ${unverified.length} handoff candidate still requires read-back verification; do not treat it as complete or authoritative until ${cmd("handoff_verify", "planner-handoff-verify")} returns resumeReady=true.`)
|
|
147
|
+
: (italian
|
|
148
|
+
? `Prossimo step: leggi l'handoff di fase pendente sotto (fonte autorevole, gestita attivamente). I nextSteps legacy di resume.json sono soppressi perché possono essere stale.`
|
|
149
|
+
: `Next step: read the pending phase handoff below (authoritative, actively maintained). Legacy resume.json nextSteps are suppressed because they may be stale.`));
|
|
125
150
|
}
|
|
126
151
|
else if (!planComplete && resume?.nextSteps?.length) {
|
|
127
152
|
lines.push(`${italian ? "Prossimo step" : "Next step"}: ${resume.nextSteps[0]}`);
|
package/dist/refs.d.ts
CHANGED
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
* - Compos: "P002(F001)" -> phase.number with parent feature validation
|
|
12
12
|
* - Title: exact match, then includes (backward-compat fallback)
|
|
13
13
|
*/
|
|
14
|
-
import type { Phase, Feature } from "./schema.js";
|
|
14
|
+
import type { Phase, Feature, Idea } from "./schema.js";
|
|
15
|
+
/**
|
|
16
|
+
* Resolve an idea by UUID, I00x number, shortId, exact title, then title
|
|
17
|
+
* inclusion. Ideas are top-level and never require feature/phase context.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findIdeaByRef(ideas: Idea[], ref: string): Idea | undefined;
|
|
15
20
|
/**
|
|
16
21
|
* Resolve a phase reference to a Phase. Returns `undefined` when not found or
|
|
17
22
|
* when a composite (F00x) parent does not match the phase's featureId.
|
package/dist/refs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAMxD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAmB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,KAAK,GAAG,SAAS,CAgCnB;AACD;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAiBxC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CA2D1C"}
|
package/dist/refs.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
// P00x or P00x(F00x) — accept 1+ digits so "p1" == "p001".
|
|
2
2
|
const PHASE_REF_RE = /^p(\d+)(?:\(f(\d+)\))?$/;
|
|
3
|
+
const IDEA_REF_RE = /^i(\d+)$/;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve an idea by UUID, I00x number, shortId, exact title, then title
|
|
6
|
+
* inclusion. Ideas are top-level and never require feature/phase context.
|
|
7
|
+
*/
|
|
8
|
+
export function findIdeaByRef(ideas, ref) {
|
|
9
|
+
const normalized = ref.trim().toLowerCase();
|
|
10
|
+
if (!normalized)
|
|
11
|
+
return undefined;
|
|
12
|
+
const byId = ideas.find((idea) => idea.id.toLowerCase() === normalized);
|
|
13
|
+
if (byId)
|
|
14
|
+
return byId;
|
|
15
|
+
const match = normalized.match(IDEA_REF_RE);
|
|
16
|
+
if (match) {
|
|
17
|
+
const number = parseInt(match[1], 10);
|
|
18
|
+
const byNumber = ideas.find((idea) => idea.number === number);
|
|
19
|
+
if (byNumber)
|
|
20
|
+
return byNumber;
|
|
21
|
+
}
|
|
22
|
+
const byShortId = ideas.find((idea) => idea.shortId.toLowerCase() === normalized);
|
|
23
|
+
if (byShortId)
|
|
24
|
+
return byShortId;
|
|
25
|
+
return ideas.find((idea) => idea.title.toLowerCase() === normalized)
|
|
26
|
+
?? ideas.find((idea) => idea.title.toLowerCase().includes(normalized));
|
|
27
|
+
}
|
|
3
28
|
/**
|
|
4
29
|
* Resolve a phase reference to a Phase. Returns `undefined` when not found or
|
|
5
30
|
* when a composite (F00x) parent does not match the phase's featureId.
|
package/dist/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,OAAO,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,OAAO,EAAQ,KAAK,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAuB5G,qBAAa,YAAY;IACvB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM;IA+LvC,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM;IAIjC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,gBAAgB;IA6BxB,OAAO,CAAC,eAAe;IA2GvB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM;IAmDxD,mEAAmE;IACnE,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAejD"}
|
package/dist/renderer.js
CHANGED
|
@@ -11,9 +11,12 @@ function phaseLabel(phase) {
|
|
|
11
11
|
function taskLabel(task) {
|
|
12
12
|
return `T${seq(task.number)} — ${task.title}`;
|
|
13
13
|
}
|
|
14
|
+
function ideaLabel(idea) {
|
|
15
|
+
return `I${seq(idea.number)} — ${idea.title}`;
|
|
16
|
+
}
|
|
14
17
|
export class PlanRenderer {
|
|
15
18
|
renderPlan(plan) {
|
|
16
|
-
const { project, manifest, features, requirements, phases } = plan;
|
|
19
|
+
const { project, manifest, features, requirements, ideas, phases } = plan;
|
|
17
20
|
const lines = [];
|
|
18
21
|
lines.push(`# ${project.name} — Project Plan`);
|
|
19
22
|
lines.push("");
|
|
@@ -72,6 +75,13 @@ export class PlanRenderer {
|
|
|
72
75
|
lines.push(...renderAcceptedDecisions(project.acceptedDecisions));
|
|
73
76
|
lines.push("");
|
|
74
77
|
}
|
|
78
|
+
// ── Project Guidelines ──────────────────────────────────────────
|
|
79
|
+
if (project.projectGuidelines.content.trim()) {
|
|
80
|
+
lines.push("## Project Guidelines");
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(project.projectGuidelines.content.trim());
|
|
83
|
+
lines.push("");
|
|
84
|
+
}
|
|
75
85
|
// ── Global Rules ─────────────────────────────────────────────────
|
|
76
86
|
if (project.globalRules.length > 0) {
|
|
77
87
|
lines.push("## Global Rules");
|
|
@@ -98,6 +108,19 @@ export class PlanRenderer {
|
|
|
98
108
|
lines.push(bullet(wr.afterPhaseComplete));
|
|
99
109
|
lines.push("");
|
|
100
110
|
}
|
|
111
|
+
// ── Ideas Inbox ─────────────────────────────────────────────────
|
|
112
|
+
if (ideas.ideas.length > 0) {
|
|
113
|
+
lines.push("---");
|
|
114
|
+
lines.push("## Ideas Inbox");
|
|
115
|
+
lines.push("");
|
|
116
|
+
for (const idea of ideas.ideas) {
|
|
117
|
+
const promotion = idea.promotion ? ` → ${idea.promotion.targetRef}` : "";
|
|
118
|
+
lines.push(`- **${ideaLabel(idea)}**${promotion}`);
|
|
119
|
+
if (idea.description)
|
|
120
|
+
lines.push(` - ${idea.description}`);
|
|
121
|
+
}
|
|
122
|
+
lines.push("");
|
|
123
|
+
}
|
|
101
124
|
// ── Features ────────────────────────────────────────────────────
|
|
102
125
|
if (features.features.length > 0) {
|
|
103
126
|
lines.push("---");
|
|
@@ -146,7 +169,6 @@ export class PlanRenderer {
|
|
|
146
169
|
lines.push(req.description);
|
|
147
170
|
}
|
|
148
171
|
lines.push("");
|
|
149
|
-
lines.push(`Status: ${statusBadge(req.status)}`);
|
|
150
172
|
if (req.linkedPhaseIds.length > 0) {
|
|
151
173
|
lines.push(`Phases: ${req.linkedPhaseIds.map((p) => `\`${p}\``).join(", ")}`);
|
|
152
174
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MacroTask, MacroTaskStatus } from "./schema.js";
|
|
2
|
+
export interface MacroTaskMutationInput {
|
|
3
|
+
/** Existing persisted ID only. Omit it for a new macro task. */
|
|
4
|
+
id?: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
status: MacroTaskStatus;
|
|
8
|
+
}
|
|
9
|
+
export declare class RequirementMacroTaskError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reconciles user-authored macro-task fields while retaining all system-owned
|
|
14
|
+
* identity and timestamp values. The input array order is canonical, so a
|
|
15
|
+
* successful mutation also performs an explicit reorder atomically.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reconcileRequirementMacroTasks(existing: readonly MacroTask[], inputs: readonly MacroTaskMutationInput[], timestamp: string): MacroTask[];
|
|
18
|
+
//# sourceMappingURL=requirement-macro-tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement-macro-tasks.d.ts","sourceRoot":"","sources":["../src/requirement-macro-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAM9D,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAUD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,SAAS,SAAS,EAAE,EAC9B,MAAM,EAAE,SAAS,sBAAsB,EAAE,EACzC,SAAS,EAAE,MAAM,GAChB,SAAS,EAAE,CA+Bb"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const MACRO_TASK_STATUSES = new Set([
|
|
2
|
+
"planned", "in-progress", "done", "blocked", "canceled", "rejected", "deferred", "waiting",
|
|
3
|
+
]);
|
|
4
|
+
export class RequirementMacroTaskError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "RequirementMacroTaskError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function nextMacroTaskId(used) {
|
|
11
|
+
for (let number = 1; number <= 999; number += 1) {
|
|
12
|
+
const candidate = `MT-${String(number).padStart(3, "0")}`;
|
|
13
|
+
if (!used.has(candidate))
|
|
14
|
+
return candidate;
|
|
15
|
+
}
|
|
16
|
+
throw new RequirementMacroTaskError("A requirement cannot contain more than 999 macro tasks.");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Reconciles user-authored macro-task fields while retaining all system-owned
|
|
20
|
+
* identity and timestamp values. The input array order is canonical, so a
|
|
21
|
+
* successful mutation also performs an explicit reorder atomically.
|
|
22
|
+
*/
|
|
23
|
+
export function reconcileRequirementMacroTasks(existing, inputs, timestamp) {
|
|
24
|
+
const existingById = new Map(existing.map((task) => [task.id, task]));
|
|
25
|
+
const usedIds = new Set(existingById.keys());
|
|
26
|
+
const seenIds = new Set();
|
|
27
|
+
return inputs.map((input, index) => {
|
|
28
|
+
const title = input.title.trim();
|
|
29
|
+
if (!title)
|
|
30
|
+
throw new RequirementMacroTaskError(`Macro task ${index + 1} requires a title.`);
|
|
31
|
+
if (!MACRO_TASK_STATUSES.has(input.status)) {
|
|
32
|
+
throw new RequirementMacroTaskError(`Macro task ${index + 1} has an invalid status: ${input.status}.`);
|
|
33
|
+
}
|
|
34
|
+
const requestedId = input.id?.trim();
|
|
35
|
+
if (requestedId && seenIds.has(requestedId)) {
|
|
36
|
+
throw new RequirementMacroTaskError(`Macro task ID ${requestedId} appears more than once.`);
|
|
37
|
+
}
|
|
38
|
+
if (requestedId)
|
|
39
|
+
seenIds.add(requestedId);
|
|
40
|
+
const previous = requestedId ? existingById.get(requestedId) : undefined;
|
|
41
|
+
if (requestedId && !previous) {
|
|
42
|
+
throw new RequirementMacroTaskError(`Macro task ID ${requestedId} is not owned by this requirement.`);
|
|
43
|
+
}
|
|
44
|
+
const id = previous?.id ?? nextMacroTaskId(usedIds);
|
|
45
|
+
usedIds.add(id);
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
title,
|
|
49
|
+
description: input.description?.trim() ?? "",
|
|
50
|
+
status: input.status,
|
|
51
|
+
createdAt: previous?.createdAt ?? timestamp,
|
|
52
|
+
updatedAt: timestamp,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RuntimePackageVersion } from "./package-version.js";
|
|
2
|
+
export declare const PLAN_SCHEMA_VERSION = 1;
|
|
3
|
+
export declare const ALLOCATION_REGISTRY_VERSION = 1;
|
|
4
|
+
export declare const SUPPORTED_ALLOCATION_KINDS: readonly ["feature", "phase", "task", "idea"];
|
|
5
|
+
export type AllocationKind = typeof SUPPORTED_ALLOCATION_KINDS[number];
|
|
6
|
+
export interface RuntimePackageDiagnostic {
|
|
7
|
+
name: string;
|
|
8
|
+
installedVersion: string;
|
|
9
|
+
loadedVersion: string;
|
|
10
|
+
runtimeState: "loaded";
|
|
11
|
+
versionSource: "loaded-package-manifest";
|
|
12
|
+
packageJsonPath?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
export interface RuntimeCapabilities {
|
|
15
|
+
planSchema: {
|
|
16
|
+
manifestSchemaVersion: typeof PLAN_SCHEMA_VERSION;
|
|
17
|
+
};
|
|
18
|
+
allocationRegistry: {
|
|
19
|
+
version: typeof ALLOCATION_REGISTRY_VERSION;
|
|
20
|
+
supportedKinds: readonly AllocationKind[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function runtimePackageDiagnostic(pkg: RuntimePackageVersion): RuntimePackageDiagnostic;
|
|
24
|
+
export declare function runtimePackagesDiagnostic(packages: RuntimePackageVersion[]): Record<string, RuntimePackageDiagnostic>;
|
|
25
|
+
export declare function runtimeCapabilities(): RuntimeCapabilities;
|
|
26
|
+
export declare function isSupportedAllocationKind(kind: string): kind is AllocationKind;
|
|
27
|
+
export declare function unsupportedAllocationKindDetails(kind: string): {
|
|
28
|
+
errorCode: "PLAN_UNSUPPORTED_ALLOCATION_KIND";
|
|
29
|
+
kind: string;
|
|
30
|
+
supportedKinds: readonly ["feature", "phase", "task", "idea"];
|
|
31
|
+
requiredCapability: string;
|
|
32
|
+
action: string;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=runtime-diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-diagnostics.d.ts","sourceRoot":"","sources":["../src/runtime-diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAC7C,eAAO,MAAM,0BAA0B,+CAAgD,CAAC;AAExF,MAAM,MAAM,cAAc,GAAG,OAAO,0BAA0B,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,QAAQ,CAAC;IACvB,aAAa,EAAE,yBAAyB,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE;QACV,qBAAqB,EAAE,OAAO,mBAAmB,CAAC;KACnD,CAAC;IACF,kBAAkB,EAAE;QAClB,OAAO,EAAE,OAAO,2BAA2B,CAAC;QAC5C,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC;KAC3C,CAAC;CACH;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,qBAAqB,GAAG,wBAAwB,CAS7F;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAErH;AAED,wBAAgB,mBAAmB,IAAI,mBAAmB,CAUzD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,cAAc,CAE9E;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM;;;;;;EAQ5D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const PLAN_SCHEMA_VERSION = 1;
|
|
2
|
+
export const ALLOCATION_REGISTRY_VERSION = 1;
|
|
3
|
+
export const SUPPORTED_ALLOCATION_KINDS = ["feature", "phase", "task", "idea"];
|
|
4
|
+
export function runtimePackageDiagnostic(pkg) {
|
|
5
|
+
return {
|
|
6
|
+
name: pkg.name,
|
|
7
|
+
installedVersion: pkg.version,
|
|
8
|
+
loadedVersion: pkg.version,
|
|
9
|
+
runtimeState: "loaded",
|
|
10
|
+
versionSource: "loaded-package-manifest",
|
|
11
|
+
...(pkg.packageJsonPath ? { packageJsonPath: pkg.packageJsonPath } : {}),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function runtimePackagesDiagnostic(packages) {
|
|
15
|
+
return Object.fromEntries(packages.map((pkg) => [pkg.name, runtimePackageDiagnostic(pkg)]));
|
|
16
|
+
}
|
|
17
|
+
export function runtimeCapabilities() {
|
|
18
|
+
return {
|
|
19
|
+
planSchema: {
|
|
20
|
+
manifestSchemaVersion: PLAN_SCHEMA_VERSION,
|
|
21
|
+
},
|
|
22
|
+
allocationRegistry: {
|
|
23
|
+
version: ALLOCATION_REGISTRY_VERSION,
|
|
24
|
+
supportedKinds: SUPPORTED_ALLOCATION_KINDS,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function isSupportedAllocationKind(kind) {
|
|
29
|
+
return SUPPORTED_ALLOCATION_KINDS.includes(kind);
|
|
30
|
+
}
|
|
31
|
+
export function unsupportedAllocationKindDetails(kind) {
|
|
32
|
+
return {
|
|
33
|
+
errorCode: "PLAN_UNSUPPORTED_ALLOCATION_KIND",
|
|
34
|
+
kind,
|
|
35
|
+
supportedKinds: SUPPORTED_ALLOCATION_KINDS,
|
|
36
|
+
requiredCapability: "allocationRegistry.supportedKinds",
|
|
37
|
+
action: "Upgrade all Agent Plan packages and reload the harness so the loaded runtime supports this allocation kind before retrying the mutation.",
|
|
38
|
+
};
|
|
39
|
+
}
|