@agent-plan/core 0.2.28 → 0.3.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/dist/accepted-decision-guard.d.ts +94 -0
- package/dist/accepted-decision-guard.d.ts.map +1 -0
- package/dist/accepted-decision-guard.js +110 -0
- package/dist/checklist.d.ts +28 -0
- package/dist/checklist.d.ts.map +1 -1
- package/dist/checklist.js +59 -0
- package/dist/feature-delete.d.ts +62 -0
- package/dist/feature-delete.d.ts.map +1 -0
- package/dist/feature-delete.js +49 -0
- package/dist/guard-decision.d.ts +118 -0
- package/dist/guard-decision.d.ts.map +1 -0
- package/dist/guard-decision.js +203 -0
- package/dist/handoff-context.d.ts +130 -1
- package/dist/handoff-context.d.ts.map +1 -1
- package/dist/handoff-context.js +167 -9
- package/dist/handoff-reply.d.ts +108 -0
- package/dist/handoff-reply.d.ts.map +1 -0
- package/dist/handoff-reply.js +279 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/mutation-reply.d.ts +105 -0
- package/dist/mutation-reply.d.ts.map +1 -0
- package/dist/mutation-reply.js +203 -0
- package/dist/naming.d.ts +1 -1
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +1 -1
- package/dist/pagination.d.ts +19 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +15 -0
- package/dist/payload-fallback.js +1 -1
- package/dist/plan-store.d.ts +141 -8
- package/dist/plan-store.d.ts.map +1 -1
- package/dist/plan-store.js +459 -87
- package/dist/project-context-migration.d.ts +5 -0
- package/dist/project-context-migration.d.ts.map +1 -1
- package/dist/project-context-migration.js +13 -0
- package/dist/project-context.d.ts +395 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +295 -0
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +27 -0
- package/dist/recommend-reply.d.ts +51 -0
- package/dist/recommend-reply.d.ts.map +1 -0
- package/dist/recommend-reply.js +84 -0
- package/dist/refs.d.ts +16 -0
- package/dist/refs.d.ts.map +1 -1
- package/dist/refs.js +38 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/schema.d.ts +7 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +8 -3
- package/dist/stranded-phase.d.ts +73 -0
- package/dist/stranded-phase.d.ts.map +1 -0
- package/dist/stranded-phase.js +93 -0
- package/dist/task-completion-gate.d.ts +84 -0
- package/dist/task-completion-gate.d.ts.map +1 -0
- package/dist/task-completion-gate.js +72 -0
- package/dist/task-context.d.ts +1 -0
- package/dist/task-context.d.ts.map +1 -1
- package/dist/task-context.js +31 -13
- package/dist/task-lifecycle.d.ts +12 -0
- package/dist/task-lifecycle.d.ts.map +1 -0
- package/dist/task-lifecycle.js +23 -0
- package/dist/task-order-context.d.ts +72 -0
- package/dist/task-order-context.d.ts.map +1 -0
- package/dist/task-order-context.js +82 -0
- package/dist/task-selection.d.ts.map +1 -1
- package/dist/task-selection.js +14 -12
- package/dist/task-start-outcome.d.ts +26 -0
- package/dist/task-start-outcome.d.ts.map +1 -1
- package/dist/task-start-outcome.js +23 -0
- package/dist/text-bounds.d.ts +32 -0
- package/dist/text-bounds.d.ts.map +1 -0
- package/dist/text-bounds.js +43 -0
- package/dist/write-coordination.d.ts +2 -2
- package/dist/write-coordination.js +2 -2
- package/package.json +1 -1
- package/planner-skill.md +30 -12
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { AcceptedDecisionSchema, DescriptionRefSchema, MacroTaskSchema, TimestampSchema, } from "./schema.js";
|
|
4
|
+
import { renderAcceptedDecisionsSection } from "./task-context.js";
|
|
5
|
+
export const PROJECT_CONTEXT_DELIVERY_VERSION = 1;
|
|
6
|
+
export const DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS = 16_000;
|
|
7
|
+
export const ProjectContextReadAttestationSchema = z.object({
|
|
8
|
+
sessionId: z.string().min(1),
|
|
9
|
+
createdAt: TimestampSchema,
|
|
10
|
+
fingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
11
|
+
});
|
|
12
|
+
export const ProjectContextReadStateDocumentSchema = z.object({
|
|
13
|
+
version: z.literal(PROJECT_CONTEXT_DELIVERY_VERSION).default(PROJECT_CONTEXT_DELIVERY_VERSION),
|
|
14
|
+
sessionInfo: z.array(ProjectContextReadAttestationSchema).default([]),
|
|
15
|
+
});
|
|
16
|
+
const ProjectContextRequirementSchema = z.object({
|
|
17
|
+
id: z.string(),
|
|
18
|
+
title: z.string().min(1),
|
|
19
|
+
description: z.string().default(""),
|
|
20
|
+
macroTasks: z.array(MacroTaskSchema).default([]),
|
|
21
|
+
linkedPhaseIds: z.array(z.string().min(1)).default([]),
|
|
22
|
+
createdAt: TimestampSchema,
|
|
23
|
+
updatedAt: TimestampSchema,
|
|
24
|
+
});
|
|
25
|
+
export const ProjectContextSnapshotSchema = z.object({
|
|
26
|
+
version: z.literal(PROJECT_CONTEXT_DELIVERY_VERSION),
|
|
27
|
+
project: z.object({
|
|
28
|
+
name: z.string().min(1),
|
|
29
|
+
goal: z.string(),
|
|
30
|
+
description: z.string(),
|
|
31
|
+
descriptionRef: DescriptionRefSchema.optional(),
|
|
32
|
+
scope: z.array(z.string().min(1)),
|
|
33
|
+
outOfScope: z.array(z.string().min(1)),
|
|
34
|
+
technologies: z.array(z.string().min(1)),
|
|
35
|
+
tools: z.array(z.string().min(1)),
|
|
36
|
+
contentLanguage: z.string(),
|
|
37
|
+
chatLanguage: z.string(),
|
|
38
|
+
projectGuidelines: z.object({
|
|
39
|
+
content: z.string(),
|
|
40
|
+
updatedAt: z.string(),
|
|
41
|
+
}),
|
|
42
|
+
acceptedDecisions: z.array(AcceptedDecisionSchema),
|
|
43
|
+
}),
|
|
44
|
+
requirements: z.array(ProjectContextRequirementSchema),
|
|
45
|
+
});
|
|
46
|
+
export const ProjectContextChunkSchema = z.object({
|
|
47
|
+
version: z.literal(PROJECT_CONTEXT_DELIVERY_VERSION),
|
|
48
|
+
fingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
49
|
+
status: z.literal("partial"),
|
|
50
|
+
complete: z.literal(false),
|
|
51
|
+
truncated: z.boolean(),
|
|
52
|
+
deliveredChunks: z.literal(1),
|
|
53
|
+
index: z.number().int().nonnegative(),
|
|
54
|
+
totalChunks: z.number().int().positive(),
|
|
55
|
+
cursor: z.string().min(1).optional(),
|
|
56
|
+
nextCursor: z.string().min(1).optional(),
|
|
57
|
+
content: z.string(),
|
|
58
|
+
});
|
|
59
|
+
export class ProjectContextDeliveryError extends Error {
|
|
60
|
+
code;
|
|
61
|
+
constructor(code, message) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "ProjectContextDeliveryError";
|
|
64
|
+
this.code = code;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Build the content-only project snapshot used by every harness. */
|
|
68
|
+
export function createProjectContextSnapshot(project, requirements) {
|
|
69
|
+
return ProjectContextSnapshotSchema.parse({
|
|
70
|
+
version: PROJECT_CONTEXT_DELIVERY_VERSION,
|
|
71
|
+
project: {
|
|
72
|
+
name: project.name,
|
|
73
|
+
goal: project.goal,
|
|
74
|
+
description: project.description,
|
|
75
|
+
...(project.descriptionRef ? { descriptionRef: project.descriptionRef } : {}),
|
|
76
|
+
scope: project.scope,
|
|
77
|
+
outOfScope: project.outOfScope,
|
|
78
|
+
technologies: project.technologies,
|
|
79
|
+
tools: project.tools,
|
|
80
|
+
contentLanguage: project.contentLanguage,
|
|
81
|
+
chatLanguage: project.chatLanguage,
|
|
82
|
+
projectGuidelines: {
|
|
83
|
+
content: project.projectGuidelines.content,
|
|
84
|
+
updatedAt: project.projectGuidelines.updatedAt,
|
|
85
|
+
},
|
|
86
|
+
acceptedDecisions: project.acceptedDecisions,
|
|
87
|
+
},
|
|
88
|
+
requirements: requirements.requirements.map((requirement) => ({
|
|
89
|
+
id: requirement.id,
|
|
90
|
+
title: requirement.title,
|
|
91
|
+
description: requirement.description,
|
|
92
|
+
macroTasks: requirement.macroTasks,
|
|
93
|
+
linkedPhaseIds: requirement.linkedPhaseIds,
|
|
94
|
+
createdAt: requirement.createdAt,
|
|
95
|
+
updatedAt: requirement.updatedAt,
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function serializeProjectContext(snapshot) {
|
|
100
|
+
return JSON.stringify(ProjectContextSnapshotSchema.parse(snapshot));
|
|
101
|
+
}
|
|
102
|
+
export function projectContextFingerprint(snapshot) {
|
|
103
|
+
return createHash("sha256").update(serializeProjectContext(snapshot), "utf8").digest("hex");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Split canonical JSON without dropping data. Individual chunks never assert
|
|
107
|
+
* completeness; only verifyProjectContextChunks can produce complete evidence.
|
|
108
|
+
*/
|
|
109
|
+
export function createProjectContextChunks(snapshot, maxChars = DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS) {
|
|
110
|
+
if (!Number.isInteger(maxChars) || maxChars < 1) {
|
|
111
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INVALID", "Project-context chunk size must be a positive integer.");
|
|
112
|
+
}
|
|
113
|
+
const serialized = serializeProjectContext(snapshot);
|
|
114
|
+
const fingerprint = projectContextFingerprint(snapshot);
|
|
115
|
+
const parts = serialized.length === 0
|
|
116
|
+
? [""]
|
|
117
|
+
: Array.from({ length: Math.ceil(serialized.length / maxChars) }, (_, index) => serialized.slice(index * maxChars, (index + 1) * maxChars));
|
|
118
|
+
return parts.map((content, index) => ({
|
|
119
|
+
version: PROJECT_CONTEXT_DELIVERY_VERSION,
|
|
120
|
+
fingerprint,
|
|
121
|
+
status: "partial",
|
|
122
|
+
complete: false,
|
|
123
|
+
truncated: parts.length > 1,
|
|
124
|
+
deliveredChunks: 1,
|
|
125
|
+
index,
|
|
126
|
+
totalChunks: parts.length,
|
|
127
|
+
...(parts.length > 1 ? { cursor: `${fingerprint}:${index}` } : {}),
|
|
128
|
+
...(index + 1 < parts.length ? { nextCursor: `${fingerprint}:${index + 1}` } : {}),
|
|
129
|
+
content,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
/** Verify an exact, ordered, complete chunk sequence and reconstruct its DTO. */
|
|
133
|
+
export function verifyProjectContextChunks(chunks) {
|
|
134
|
+
if (chunks.length === 0) {
|
|
135
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INCOMPLETE", "Project-context delivery contains no chunks.");
|
|
136
|
+
}
|
|
137
|
+
let parsedChunks;
|
|
138
|
+
try {
|
|
139
|
+
parsedChunks = ProjectContextChunkSchema.array().parse(chunks);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INVALID", `Project-context chunk metadata is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
}
|
|
144
|
+
const first = parsedChunks[0];
|
|
145
|
+
const receivedIndexes = parsedChunks.map((chunk) => chunk.index);
|
|
146
|
+
if (parsedChunks.length !== first.totalChunks || parsedChunks.some((chunk, index) => chunk.index !== index)) {
|
|
147
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INCOMPLETE", `Project-context delivery is incomplete: received ordered chunks [${receivedIndexes.join(", ")}], expected indexes 0 through ${first.totalChunks - 1}.`);
|
|
148
|
+
}
|
|
149
|
+
const chunked = first.totalChunks > 1;
|
|
150
|
+
if (parsedChunks.some((chunk) => chunk.version !== PROJECT_CONTEXT_DELIVERY_VERSION
|
|
151
|
+
|| chunk.fingerprint !== first.fingerprint
|
|
152
|
+
|| chunk.totalChunks !== first.totalChunks
|
|
153
|
+
|| chunk.truncated !== chunked
|
|
154
|
+
|| (chunked ? chunk.cursor !== `${first.fingerprint}:${chunk.index}` : chunk.cursor !== undefined)
|
|
155
|
+
|| (chunk.index + 1 < chunk.totalChunks
|
|
156
|
+
? chunk.nextCursor !== `${first.fingerprint}:${chunk.index + 1}`
|
|
157
|
+
: chunk.nextCursor !== undefined))) {
|
|
158
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INVALID", "Project-context chunks have inconsistent version, fingerprint, truncation, cursor, or sequence metadata.");
|
|
159
|
+
}
|
|
160
|
+
const serialized = parsedChunks.map((chunk) => chunk.content).join("");
|
|
161
|
+
let snapshot;
|
|
162
|
+
try {
|
|
163
|
+
snapshot = ProjectContextSnapshotSchema.parse(JSON.parse(serialized));
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INVALID", `Project-context chunks do not reconstruct a valid canonical snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
167
|
+
}
|
|
168
|
+
const fingerprint = projectContextFingerprint(snapshot);
|
|
169
|
+
if (fingerprint !== first.fingerprint) {
|
|
170
|
+
throw new ProjectContextDeliveryError("PROJECT_CONTEXT_DELIVERY_INVALID", "Project-context fingerprint does not match the reconstructed canonical snapshot.");
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
snapshot,
|
|
174
|
+
chunks: parsedChunks,
|
|
175
|
+
evidence: {
|
|
176
|
+
version: PROJECT_CONTEXT_DELIVERY_VERSION,
|
|
177
|
+
fingerprint,
|
|
178
|
+
status: "complete",
|
|
179
|
+
complete: true,
|
|
180
|
+
truncated: false,
|
|
181
|
+
chunked,
|
|
182
|
+
deliveredChunks: parsedChunks.length,
|
|
183
|
+
totalChunks: parsedChunks.length,
|
|
184
|
+
serializedChars: serialized.length,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export function createCompleteProjectContextDelivery(project, requirements, maxChars = DEFAULT_PROJECT_CONTEXT_CHUNK_CHARS) {
|
|
189
|
+
const snapshot = createProjectContextSnapshot(project, requirements);
|
|
190
|
+
return verifyProjectContextChunks(createProjectContextChunks(snapshot, maxChars));
|
|
191
|
+
}
|
|
192
|
+
export function projectContextReadState(snapshot, attestations, sessionId) {
|
|
193
|
+
const normalizedSessionId = sessionId.trim();
|
|
194
|
+
if (!normalizedSessionId)
|
|
195
|
+
return "missing";
|
|
196
|
+
const attestation = attestations?.find((entry) => entry.sessionId === normalizedSessionId);
|
|
197
|
+
if (!attestation)
|
|
198
|
+
return "missing";
|
|
199
|
+
return attestation.fingerprint === projectContextFingerprint(snapshot) ? "valid" : "stale";
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Non-blocking task-start advisory for when project-level context (delivered
|
|
203
|
+
* through explicit planner load) has drifted since this session's last
|
|
204
|
+
* attested read. Task-start's own gates stay scoped to feature/phase/task
|
|
205
|
+
* context (P102(F005)/T404 keeps that delivery separate and un-duplicated);
|
|
206
|
+
* this only signals that a targeted planner-load refresh is due, without
|
|
207
|
+
* blocking the task from starting.
|
|
208
|
+
*/
|
|
209
|
+
export function projectContextStaleAdvisory(state) {
|
|
210
|
+
if (state !== "stale")
|
|
211
|
+
return "";
|
|
212
|
+
return "\n\n⚠️ Project-context advisory: project-level context (description, scope, technologies, tools, languages, Requirements, or project Accepted Decisions) changed since your last planner-load. Call planner-load again to refresh it before relying on it.";
|
|
213
|
+
}
|
|
214
|
+
/** Render the complete snapshot without applying a transport truncation policy. */
|
|
215
|
+
export function renderProjectContext(snapshot) {
|
|
216
|
+
const { project, requirements } = snapshot;
|
|
217
|
+
const lines = [
|
|
218
|
+
`Project context v${snapshot.version}`,
|
|
219
|
+
`Name: ${project.name}`,
|
|
220
|
+
`Short description: ${project.description || "(not set)"}`,
|
|
221
|
+
`Description reference: ${project.descriptionRef ?? "(not set)"}`,
|
|
222
|
+
`Goal: ${project.goal || "(not set)"}`,
|
|
223
|
+
`Scope: ${project.scope.join("; ") || "(none)"}`,
|
|
224
|
+
`Out of scope: ${project.outOfScope.join("; ") || "(none)"}`,
|
|
225
|
+
`Technologies: ${project.technologies.join("; ") || "(none)"}`,
|
|
226
|
+
`Tools: ${project.tools.join("; ") || "(none)"}`,
|
|
227
|
+
`Content language: ${project.contentLanguage || "(not set)"}`,
|
|
228
|
+
`Chat language: ${project.chatLanguage || "(not set)"}`,
|
|
229
|
+
"",
|
|
230
|
+
"Project Guidelines:",
|
|
231
|
+
project.projectGuidelines.content || "(none)",
|
|
232
|
+
`Guidelines updated at: ${project.projectGuidelines.updatedAt || "(not set)"}`,
|
|
233
|
+
"",
|
|
234
|
+
`Requirements (${requirements.length}):`,
|
|
235
|
+
];
|
|
236
|
+
if (requirements.length === 0) {
|
|
237
|
+
lines.push(" - None.");
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
for (const requirement of requirements) {
|
|
241
|
+
lines.push(` - ${requirement.id} — ${requirement.title}`);
|
|
242
|
+
lines.push(` Description: ${requirement.description || "(not provided)"}`);
|
|
243
|
+
lines.push(` Linked phases: ${requirement.linkedPhaseIds.join(", ") || "None"}`);
|
|
244
|
+
lines.push(` Created at: ${requirement.createdAt}`);
|
|
245
|
+
lines.push(` Updated at: ${requirement.updatedAt}`);
|
|
246
|
+
lines.push(` Macro tasks (${requirement.macroTasks.length}):`);
|
|
247
|
+
if (requirement.macroTasks.length === 0)
|
|
248
|
+
lines.push(" - None.");
|
|
249
|
+
for (const macroTask of requirement.macroTasks) {
|
|
250
|
+
lines.push(` - ${macroTask.id} — ${macroTask.title} (${macroTask.status})`);
|
|
251
|
+
lines.push(` Description: ${macroTask.description || "(not provided)"}`);
|
|
252
|
+
lines.push(` Created at: ${macroTask.createdAt}`);
|
|
253
|
+
lines.push(` Updated at: ${macroTask.updatedAt}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
lines.push("", renderAcceptedDecisionsSection("Project Accepted Decisions", project.acceptedDecisions));
|
|
258
|
+
return lines.join("\n");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Build the reply for an explicit planner-load project-context delivery.
|
|
262
|
+
* `delivery` must come straight from `PlanStore.loadProjectContextDelivery`.
|
|
263
|
+
* This function only shapes the reply and never mutates; the caller is
|
|
264
|
+
* responsible for calling `PlanStore.recordProjectContextRead` with
|
|
265
|
+
* `delivery.chunks` immediately afterward, and only when, this returns
|
|
266
|
+
* `structured.contextComplete === true`.
|
|
267
|
+
*/
|
|
268
|
+
export function buildProjectContextLoadReply(delivery, maxChars) {
|
|
269
|
+
if (delivery.chunks.length > 1) {
|
|
270
|
+
return {
|
|
271
|
+
text: `Project context exceeds the ${maxChars}-character single-response bound (${delivery.evidence.serializedChars} characters across ${delivery.evidence.totalChunks} chunks). Project-level context (description, scope, out-of-scope, technologies, tools, languages, Project Guidelines, every Requirement, and every project Accepted Decision) was NOT delivered this turn, and the read was not attested.`,
|
|
272
|
+
structured: {
|
|
273
|
+
loaded: false,
|
|
274
|
+
contextComplete: false,
|
|
275
|
+
fingerprint: delivery.evidence.fingerprint,
|
|
276
|
+
totalChunks: delivery.evidence.totalChunks,
|
|
277
|
+
serializedChars: delivery.evidence.serializedChars,
|
|
278
|
+
nextActions: [
|
|
279
|
+
`Retry planner-load with a larger maxChars (at least ${delivery.evidence.serializedChars}) so the complete project context fits in one response.`,
|
|
280
|
+
"Do not treat this response as a complete project-context read: REQUIREMENTS_READ_REQUIRED and equivalent gates remain unsatisfied until a complete read is attested.",
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
text: renderProjectContext(delivery.snapshot),
|
|
287
|
+
structured: {
|
|
288
|
+
loaded: true,
|
|
289
|
+
contextComplete: true,
|
|
290
|
+
project: delivery.snapshot.project,
|
|
291
|
+
requirements: delivery.snapshot.requirements,
|
|
292
|
+
fingerprint: delivery.evidence.fingerprint,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
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;AAMjD;;;;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,CA2ShH"}
|
package/dist/recap.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { formatPhaseRef, formatTwoDigitNumber } from "./naming.js";
|
|
2
2
|
import { buildPhaseWorkMap } from "./task-context.js";
|
|
3
|
+
import { recommendNextTask } from "./task-selection.js";
|
|
4
|
+
import { listOpenPhaseWork, openPhaseWorkLines } from "./stranded-phase.js";
|
|
3
5
|
const fref = (n) => `F${formatTwoDigitNumber(n)}`;
|
|
4
6
|
const tref = (n) => `T${formatTwoDigitNumber(n)}`;
|
|
5
7
|
/**
|
|
@@ -74,6 +76,15 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
74
76
|
lines.push(italian
|
|
75
77
|
? `Avanzamento: feature ${doneF}/${totalF} completate (${activeF} attive) · fasi ${doneP}/${totalP} completate (${activeP} attive) · task ${doneT}/${totalT} completati (${activeT} attivi, ${checkpointedT} con checkpoint)`
|
|
76
78
|
: `Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active, ${checkpointedT} with checkpoints)`);
|
|
79
|
+
// P104(F005)/T422: a phase left in-progress with no active task had no
|
|
80
|
+
// signal anywhere cheap to read — this states it, priority-ordered, every
|
|
81
|
+
// recap, instead of leaving it discoverable only by paying for
|
|
82
|
+
// task_recommend's full claim evidence.
|
|
83
|
+
const openPhaseWork = listOpenPhaseWork(phases, feats);
|
|
84
|
+
const openPhaseWorkText = openPhaseWorkLines(openPhaseWork);
|
|
85
|
+
if (openPhaseWorkText) {
|
|
86
|
+
lines.push("", italian ? "In-progress: fasi con lavoro rimanente (ordine di priorità):" : "In-progress phases with work left (priority order):", openPhaseWorkText);
|
|
87
|
+
}
|
|
77
88
|
if (activeTasks.length > 1) {
|
|
78
89
|
const conflicts = activeTasks.map(({ phase, task }) => {
|
|
79
90
|
const feature = feats.find((entry) => entry.id === phase.featureId);
|
|
@@ -159,6 +170,22 @@ export async function buildRecap(st, web = {}, opts = {}) {
|
|
|
159
170
|
: `⚠️ nextSteps are free-text from resume.json — may be stale; verify against current state before acting.`);
|
|
160
171
|
lines.push(staleNote);
|
|
161
172
|
}
|
|
173
|
+
else if (!planComplete) {
|
|
174
|
+
// No active task, resume advisory, checkpoint, pending handoff, or
|
|
175
|
+
// legacy resume.json nextSteps: every branch above already names a
|
|
176
|
+
// concrete ref to act on, but this, the ordinary case, previously fell
|
|
177
|
+
// through with no "Next step" line at all — the reader had to go call
|
|
178
|
+
// task_recommend to learn what the recap already knew (P104(F005)/T421).
|
|
179
|
+
const recommendation = recommendNextTask(feats, phases, plan.project.workDeviations);
|
|
180
|
+
if (recommendation.kind === "priority" && recommendation.candidate) {
|
|
181
|
+
const { candidate } = recommendation;
|
|
182
|
+
const candidateFeature = feats.find((entry) => entry.id === candidate.phase.featureId);
|
|
183
|
+
const ref = `${formatPhaseRef(candidate.phase.number, candidateFeature?.number)}/${tref(candidate.task.number)}`;
|
|
184
|
+
lines.push(italian
|
|
185
|
+
? `Prossimo step: ${ref} — ${candidate.task.title} (priorità più bassa pronta). Avvia con ${taskStartCmd} ${ref}.`
|
|
186
|
+
: `Next step: ${ref} — ${candidate.task.title} (lowest-priority ready task). Start with ${taskStartCmd} ${ref}.`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
162
189
|
if (pendingResume) {
|
|
163
190
|
const feature = feats.find((entry) => entry.id === pendingResume.phase.featureId);
|
|
164
191
|
const ref = `${formatPhaseRef(pendingResume.phase.number, feature?.number)}/${tref(pendingResume.task.number)}`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared reply shaping for `planner-task-recommend` / `task_recommend`.
|
|
3
|
+
*
|
|
4
|
+
* Measured on this repository's own planner (P104(F005)/T419): the raw
|
|
5
|
+
* result of `recommendNextWork` was 48,869 characters, because both
|
|
6
|
+
* adapters spread `selection` whole into the reply. `selection.candidate`
|
|
7
|
+
* carries full `Feature`/`Phase`/`Task` entities — every sibling task's
|
|
8
|
+
* complete description and completion summary, every checklist, every
|
|
9
|
+
* statusLog entry, the whole persisted handoff body — to answer a question
|
|
10
|
+
* whose only useful payload is "which task, and why." The compact fields
|
|
11
|
+
* this builder already assembles (`taskId`, `phaseId`, `featureId`,
|
|
12
|
+
* `activeTask`, `nextFeature`, `nextPhase`, `nextTask`, `claims`) carry the
|
|
13
|
+
* answer; nothing reads the fat.
|
|
14
|
+
*
|
|
15
|
+
* Placed in plan-core per AGENTS.md rule 4: both adapters call
|
|
16
|
+
* `recommendNextWork` themselves (the pure selector stays adapter-visible
|
|
17
|
+
* for their own routing), then hand the result here once so neither can
|
|
18
|
+
* drift from the other. Follows the shape of handoff-reply.ts and
|
|
19
|
+
* mutation-reply.ts: one `text`/`structured` reply, built once, consumed by
|
|
20
|
+
* every adapter.
|
|
21
|
+
*/
|
|
22
|
+
import type { Feature } from "./schema.js";
|
|
23
|
+
import type { NextWorkRecommendation } from "./task-selection.js";
|
|
24
|
+
export interface RecommendReply {
|
|
25
|
+
text: string;
|
|
26
|
+
structured: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/** What `selection.candidate` shrinks to: the composite ref and title a
|
|
29
|
+
* caller acts on, not the full entities behind them. */
|
|
30
|
+
export interface RecommendationCandidateSummary {
|
|
31
|
+
ref: string;
|
|
32
|
+
taskId: string;
|
|
33
|
+
phaseId: string;
|
|
34
|
+
featureId?: string;
|
|
35
|
+
title: string;
|
|
36
|
+
status: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the reply for `planner-task-recommend` / `task_recommend`, covering
|
|
40
|
+
* both the candidate and no-candidate branches through one shared shape.
|
|
41
|
+
*
|
|
42
|
+
* `kind`, `reason`, `taskId`, `phaseId`, `featureId`, `deviation`,
|
|
43
|
+
* `activeTask`, `nextFeature`, `nextPhase`, `nextTask`, `claims`, and the
|
|
44
|
+
* no-candidate branch's `activeTaskIds` keep their exact prior shape and
|
|
45
|
+
* position — the lifecycle protocol and the recommendation claims contract
|
|
46
|
+
* read those fields. Only `selection.candidate` (and, symmetrically,
|
|
47
|
+
* `selection.activeCandidates` on the conflict branch) is replaced with
|
|
48
|
+
* bounded summaries instead of full entities.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildRecommendationReply(result: NextWorkRecommendation, features: Feature[]): RecommendReply;
|
|
51
|
+
//# sourceMappingURL=recommend-reply.d.ts.map
|
|
@@ -0,0 +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;AAEjF,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,CAsD5G"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { featureNumberOfPhase, formatPhaseRef } from "./naming.js";
|
|
2
|
+
function compositeTaskRef(candidate, features) {
|
|
3
|
+
const featureNumber = featureNumberOfPhase(candidate.phase, features);
|
|
4
|
+
return `${formatPhaseRef(candidate.phase.number, featureNumber)}/T${String(candidate.task.number).padStart(3, "0")}`;
|
|
5
|
+
}
|
|
6
|
+
function summarizeCandidate(candidate, features) {
|
|
7
|
+
return {
|
|
8
|
+
ref: compositeTaskRef(candidate, features),
|
|
9
|
+
taskId: candidate.task.id,
|
|
10
|
+
phaseId: candidate.phase.id,
|
|
11
|
+
...(candidate.feature?.id ? { featureId: candidate.feature.id } : {}),
|
|
12
|
+
title: candidate.task.title,
|
|
13
|
+
status: candidate.task.status,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function renderClaimsText(claims) {
|
|
17
|
+
return claims.length
|
|
18
|
+
? `\nClaims:\n${claims.map((claim) => `- ${claim.kind} (${claim.source}): ${claim.ref || claim.title} — ${claim.reason}`).join("\n")}`
|
|
19
|
+
: "";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build the reply for `planner-task-recommend` / `task_recommend`, covering
|
|
23
|
+
* both the candidate and no-candidate branches through one shared shape.
|
|
24
|
+
*
|
|
25
|
+
* `kind`, `reason`, `taskId`, `phaseId`, `featureId`, `deviation`,
|
|
26
|
+
* `activeTask`, `nextFeature`, `nextPhase`, `nextTask`, `claims`, and the
|
|
27
|
+
* no-candidate branch's `activeTaskIds` keep their exact prior shape and
|
|
28
|
+
* position — the lifecycle protocol and the recommendation claims contract
|
|
29
|
+
* read those fields. Only `selection.candidate` (and, symmetrically,
|
|
30
|
+
* `selection.activeCandidates` on the conflict branch) is replaced with
|
|
31
|
+
* bounded summaries instead of full entities.
|
|
32
|
+
*/
|
|
33
|
+
export function buildRecommendationReply(result, features) {
|
|
34
|
+
const { selection } = result;
|
|
35
|
+
const claimsText = renderClaimsText(result.claims);
|
|
36
|
+
if (!selection.candidate) {
|
|
37
|
+
const activeCandidates = selection.activeCandidates ?? [];
|
|
38
|
+
return {
|
|
39
|
+
text: `No work recommendation: ${selection.reason}${claimsText}`,
|
|
40
|
+
structured: {
|
|
41
|
+
kind: selection.kind,
|
|
42
|
+
reason: selection.reason,
|
|
43
|
+
activeTaskIds: activeCandidates.map((candidate) => candidate.task.id),
|
|
44
|
+
activeTask: result.activeTask,
|
|
45
|
+
nextFeature: result.nextFeature,
|
|
46
|
+
nextPhase: result.nextPhase,
|
|
47
|
+
nextTask: result.nextTask,
|
|
48
|
+
claims: result.claims,
|
|
49
|
+
selection: {
|
|
50
|
+
kind: selection.kind,
|
|
51
|
+
reason: selection.reason,
|
|
52
|
+
deviation: selection.deviation,
|
|
53
|
+
activeCandidates: activeCandidates.map((candidate) => summarizeCandidate(candidate, features)),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const { candidate } = selection;
|
|
59
|
+
const candidateSummary = summarizeCandidate(candidate, features);
|
|
60
|
+
const activeLine = result.activeTask ? `Active task: ${result.activeTask.id} — ${result.activeTask.title}\n` : "";
|
|
61
|
+
const deviationLine = selection.deviation ? `\nDeviation: ${selection.deviation.id}; resume target ${selection.deviation.resumeTaskId}.` : "";
|
|
62
|
+
const text = `${activeLine}Next work (${selection.kind}): ${candidateSummary.ref} — ${candidate.task.title}\n${selection.reason}${deviationLine}${claimsText}`;
|
|
63
|
+
return {
|
|
64
|
+
text,
|
|
65
|
+
structured: {
|
|
66
|
+
kind: selection.kind,
|
|
67
|
+
taskId: candidate.task.id,
|
|
68
|
+
phaseId: candidate.phase.id,
|
|
69
|
+
featureId: candidate.feature?.id,
|
|
70
|
+
deviation: selection.deviation,
|
|
71
|
+
activeTask: result.activeTask,
|
|
72
|
+
nextFeature: result.nextFeature,
|
|
73
|
+
nextPhase: result.nextPhase,
|
|
74
|
+
nextTask: result.nextTask,
|
|
75
|
+
claims: result.claims,
|
|
76
|
+
selection: {
|
|
77
|
+
kind: selection.kind,
|
|
78
|
+
reason: selection.reason,
|
|
79
|
+
deviation: selection.deviation,
|
|
80
|
+
candidate: candidateSummary,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
package/dist/refs.d.ts
CHANGED
|
@@ -17,6 +17,22 @@ import type { Phase, Feature, Idea } from "./schema.js";
|
|
|
17
17
|
* inclusion. Ideas are top-level and never require feature/phase context.
|
|
18
18
|
*/
|
|
19
19
|
export declare function findIdeaByRef(ideas: Idea[], ref: string): Idea | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a feature reference to a Feature, or a typed error when it can't
|
|
22
|
+
* be resolved unambiguously. Unlike `findPhaseByRef`/`findTaskByRef` (which
|
|
23
|
+
* return `undefined` on any miss), callers of this one — accepted-decision
|
|
24
|
+
* target resolution, feature-scoped tools — need to tell "not found" apart
|
|
25
|
+
* from "ambiguous" so the message can say which. Both adapters carried an
|
|
26
|
+
* identical copy; kept here so the ref grammar (number, shortId, UUID, exact
|
|
27
|
+
* name, partial name with ambiguity detection) is one decision.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveFeatureRefStrict(features: Feature[], ref: string): {
|
|
30
|
+
ok: true;
|
|
31
|
+
feature: Feature;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
error: string;
|
|
35
|
+
};
|
|
20
36
|
/**
|
|
21
37
|
* Resolve a phase reference to a Phase. Returns `undefined` when not found or
|
|
22
38
|
* 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,IAAI,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;AAOxD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAmB1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,GACpE;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAwB/B;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,3 +1,4 @@
|
|
|
1
|
+
import { formatFeatureRef } from "./naming.js";
|
|
1
2
|
// P00x or P00x(F00x) — accept 1+ digits so "p1" == "p001".
|
|
2
3
|
const PHASE_REF_RE = /^p(\d+)(?:\(f(\d+)\))?$/;
|
|
3
4
|
const IDEA_REF_RE = /^i(\d+)$/;
|
|
@@ -25,6 +26,43 @@ export function findIdeaByRef(ideas, ref) {
|
|
|
25
26
|
return ideas.find((idea) => idea.title.toLowerCase() === normalized)
|
|
26
27
|
?? ideas.find((idea) => idea.title.toLowerCase().includes(normalized));
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a feature reference to a Feature, or a typed error when it can't
|
|
31
|
+
* be resolved unambiguously. Unlike `findPhaseByRef`/`findTaskByRef` (which
|
|
32
|
+
* return `undefined` on any miss), callers of this one — accepted-decision
|
|
33
|
+
* target resolution, feature-scoped tools — need to tell "not found" apart
|
|
34
|
+
* from "ambiguous" so the message can say which. Both adapters carried an
|
|
35
|
+
* identical copy; kept here so the ref grammar (number, shortId, UUID, exact
|
|
36
|
+
* name, partial name with ambiguity detection) is one decision.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveFeatureRefStrict(features, ref) {
|
|
39
|
+
const raw = ref.trim();
|
|
40
|
+
if (!raw)
|
|
41
|
+
return { ok: false, error: "Feature ref is required." };
|
|
42
|
+
const normalized = raw.toLowerCase();
|
|
43
|
+
const byNumber = normalized.match(/^f(\d+)$/)
|
|
44
|
+
? features.find((feature) => feature.number === parseInt(normalized.slice(1), 10))
|
|
45
|
+
: undefined;
|
|
46
|
+
if (byNumber)
|
|
47
|
+
return { ok: true, feature: byNumber };
|
|
48
|
+
const byShortId = features.find((feature) => feature.shortId?.toLowerCase() === normalized);
|
|
49
|
+
if (byShortId)
|
|
50
|
+
return { ok: true, feature: byShortId };
|
|
51
|
+
const byId = features.find((feature) => feature.id.toLowerCase() === normalized);
|
|
52
|
+
if (byId)
|
|
53
|
+
return { ok: true, feature: byId };
|
|
54
|
+
const exactName = features.filter((feature) => feature.name.toLowerCase() === normalized);
|
|
55
|
+
if (exactName.length === 1)
|
|
56
|
+
return { ok: true, feature: exactName[0] };
|
|
57
|
+
if (exactName.length > 1)
|
|
58
|
+
return { ok: false, error: `Ambiguous feature ref: ${raw}. Multiple features have that exact name; use F00x, shortId, or UUID.` };
|
|
59
|
+
const partialName = features.filter((feature) => feature.name.toLowerCase().includes(normalized));
|
|
60
|
+
if (partialName.length === 1)
|
|
61
|
+
return { ok: true, feature: partialName[0] };
|
|
62
|
+
if (partialName.length > 1)
|
|
63
|
+
return { ok: false, error: `Ambiguous feature ref: ${raw}. Matches: ${partialName.map((feature) => formatFeatureRef(feature.number)).join(", ")}. Use a specific F00x, shortId, or UUID.` };
|
|
64
|
+
return { ok: false, error: `Feature not found: ${raw}` };
|
|
65
|
+
}
|
|
28
66
|
/**
|
|
29
67
|
* Resolve a phase reference to a Phase. Returns `undefined` when not found or
|
|
30
68
|
* 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,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAQ,KAAK,EAAE,aAAa,EAAQ,MAAM,aAAa,CAAC;AAuB7E,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/schema.d.ts
CHANGED
|
@@ -799,11 +799,16 @@ export declare const ChecklistItemSchema: z.ZodObject<{
|
|
|
799
799
|
export declare const STATUS_LOG_MOTIVATION_REQUIRED: Set<string>;
|
|
800
800
|
/**
|
|
801
801
|
* Returns true when a status transition requires a written motivation.
|
|
802
|
-
* - → DONE never requires motivation.
|
|
802
|
+
* - → DONE never requires motivation for an ordinary completion.
|
|
803
|
+
* - → DONE with `forced: true` — a completion pushed through past checklist
|
|
804
|
+
* items still open (see task-completion-gate.ts, P104(F005)/T428) — is the
|
|
805
|
+
* one case where a → DONE transition does require it: the same rule this
|
|
806
|
+
* function already enforces for blocked/canceled/deferred/rejected,
|
|
807
|
+
* applied to the one case where reaching DONE is itself an override.
|
|
803
808
|
* - → BLOCKED / CANCELED / DEFERRED / REJECTED / WAITING always require it.
|
|
804
809
|
* - → PLANNED from a non-PLANNED status requires it.
|
|
805
810
|
*/
|
|
806
|
-
export declare function needsMotivation(fromStatus: string, toStatus: string): boolean;
|
|
811
|
+
export declare function needsMotivation(fromStatus: string, toStatus: string, forced?: boolean): boolean;
|
|
807
812
|
export declare const SessionInfoSchema: z.ZodObject<{
|
|
808
813
|
sessionId: z.ZodString;
|
|
809
814
|
createdAt: z.ZodString;
|