@agent-plan/pi-adapter 0.2.22-next.2 → 0.2.22-next.4
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/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AA40BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AA40BtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAs6H9D"}
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
import { paginatedSelect, paginatedNotify } from "./ui/paginate.js";
|
|
13
|
-
import { ExportService, PlanStore, setWriteBusyHook, setWriteNotifyHook, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, buildPhaseContextBlock, checkExplicitTaskStart, recommendNextTask, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, markFeatureRead, markPhaseRead, markTaskRead,
|
|
13
|
+
import { ExportService, PlanStore, setWriteBusyHook, setWriteNotifyHook, withFeatureLock, needsMotivation, findPhaseByRef, findTaskByRef, buildRecap, addChecklistItem, removeChecklistItem, toggleChecklistItem, buildPhaseContextBlock, checkExplicitTaskStart, recommendNextTask, buildResumeRequiredProposal, packageVersionFromModule, resolvedPackageVersion, markFeatureRead, markPhaseRead, markTaskRead, contextReadEligibility, hasReadRequirements, markRequirementRead, invalidateReads } from "@agent-plan/core";
|
|
14
14
|
import { createChecklistItemId, createFeatureId, createPhaseId, createTaskId, clampSlug, normalizeSlug, formatPhaseRef, formatFeatureRef, featureNumberOfPhase, validateResolvedTarget } from "@agent-plan/core/naming";
|
|
15
15
|
import { join, dirname } from "node:path";
|
|
16
16
|
import { homedir } from "node:os";
|
|
@@ -786,6 +786,7 @@ export default function planPiExtension(pi) {
|
|
|
786
786
|
capturedPi = pi;
|
|
787
787
|
// ── Restore on session start/reload ─────────────────────────────────
|
|
788
788
|
pi.on("session_start", async (_event, ctx) => {
|
|
789
|
+
invalidateReads();
|
|
789
790
|
try {
|
|
790
791
|
ctx.ui.addAutocompleteProvider((current) => ({
|
|
791
792
|
triggerCharacters: ["/", " "],
|
|
@@ -1464,9 +1465,9 @@ export default function planPiExtension(pi) {
|
|
|
1464
1465
|
if (b === "language") {
|
|
1465
1466
|
const project = await st.loadProject();
|
|
1466
1467
|
const contentLanguage = await ctx.ui.input(`Plan content language [${project.contentLanguage || "English"}]`);
|
|
1467
|
-
const chatLanguage = await ctx.ui.input(`Chat language [${project.chatLanguage || contentLanguage?.trim() || project.contentLanguage || "
|
|
1468
|
+
const chatLanguage = await ctx.ui.input(`Chat language [${project.chatLanguage || contentLanguage?.trim() || project.contentLanguage || "English"}]`);
|
|
1468
1469
|
project.contentLanguage = contentLanguage?.trim() || project.contentLanguage || "English";
|
|
1469
|
-
project.chatLanguage = chatLanguage?.trim() || project.chatLanguage || project.contentLanguage || "
|
|
1470
|
+
project.chatLanguage = chatLanguage?.trim() || project.chatLanguage || project.contentLanguage || "English";
|
|
1470
1471
|
await st.saveProject(project);
|
|
1471
1472
|
await st.writeGenerated();
|
|
1472
1473
|
ctx.ui.notify(`Saved language preferences: content=${project.contentLanguage}, chat=${project.chatLanguage}`, "info");
|
|
@@ -2159,8 +2160,9 @@ export default function planPiExtension(pi) {
|
|
|
2159
2160
|
ctx.ui.notify(`Invalid status. Use: ${valid.join(", ")}`, "error");
|
|
2160
2161
|
return;
|
|
2161
2162
|
}
|
|
2162
|
-
if (normalizedStatus === "in-progress" &&
|
|
2163
|
-
ctx.ui.notify("
|
|
2163
|
+
if (normalizedStatus === "in-progress" && normalizedStatus !== task.status) {
|
|
2164
|
+
ctx.ui.notify("Task start/resume transitions require /planner task start so ordered full context reads and lifecycle state remain consistent.", "warning");
|
|
2165
|
+
return;
|
|
2164
2166
|
}
|
|
2165
2167
|
applyTaskLifecycleDates(task, normalizedStatus, now);
|
|
2166
2168
|
}
|
|
@@ -2198,16 +2200,19 @@ export default function planPiExtension(pi) {
|
|
|
2198
2200
|
return;
|
|
2199
2201
|
}
|
|
2200
2202
|
}
|
|
2201
|
-
if (phase && !hasReadParents(phase.featureId, phase.id)) {
|
|
2202
|
-
ctx.ui.notify("⚠️ READ REQUIRED before proceeding: read the parent feature and phase (full=true) for this task before starting it.", "warning");
|
|
2203
|
-
}
|
|
2204
2203
|
if (phase) {
|
|
2204
|
+
const contextEligibility = contextReadEligibility(task.id, phase.id, phase.featureId);
|
|
2205
|
+
if (!contextEligibility.eligible) {
|
|
2206
|
+
ctx.ui.notify(`Task start denied: context reads required. ${contextEligibility.reason}`, "warning");
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2205
2209
|
const linkedRequirementIds = [
|
|
2206
2210
|
...(await st.linkedRequirementsForPhase(phase.id)).map((r) => r.id),
|
|
2207
2211
|
...(phase.featureId ? (await st.linkedRequirementsForFeature(phase.featureId)).map((r) => r.id) : []),
|
|
2208
2212
|
];
|
|
2209
|
-
if (
|
|
2210
|
-
ctx.ui.notify("
|
|
2213
|
+
if (!hasReadRequirements(linkedRequirementIds)) {
|
|
2214
|
+
ctx.ui.notify("Task start denied: read the requirements linked to this phase and feature first.", "warning");
|
|
2215
|
+
return;
|
|
2211
2216
|
}
|
|
2212
2217
|
}
|
|
2213
2218
|
if (task.status === "in-progress") {
|
|
@@ -3271,7 +3276,8 @@ export default function planPiExtension(pi) {
|
|
|
3271
3276
|
if (!resolvedFeature.ok)
|
|
3272
3277
|
return { content: [{ type: "text", text: resolvedFeature.error }], details: {} };
|
|
3273
3278
|
const feature = resolvedFeature.feature;
|
|
3274
|
-
|
|
3279
|
+
if (params.full)
|
|
3280
|
+
markFeatureRead(feature.id);
|
|
3275
3281
|
const phases = (await st.loadAllPhases()).filter((p) => p.featureId === feature.id);
|
|
3276
3282
|
const linkedRequirements = await st.linkedRequirementsForFeature(feature.id);
|
|
3277
3283
|
const summary = `${feature.name} — ${formatFeatureRef(feature.number)}${feature.shortId ? ` · ${feature.shortId}` : ""} (${feature.status}; ${phases.length} phases${linkedRequirements.length ? `; ${linkedRequirements.length} linked requirement${linkedRequirements.length === 1 ? "" : "s"}` : ""})`;
|
|
@@ -3570,7 +3576,8 @@ export default function planPiExtension(pi) {
|
|
|
3570
3576
|
const phase = findPhaseByRef(await st.loadAllPhases(), features, params.phaseId.trim());
|
|
3571
3577
|
if (!phase)
|
|
3572
3578
|
return { content: [{ type: "text", text: `Phase not found: ${params.phaseId}` }], details: {} };
|
|
3573
|
-
|
|
3579
|
+
if (params.full)
|
|
3580
|
+
markPhaseRead(phase.id, phase.featureId);
|
|
3574
3581
|
const linkedRequirements = await st.linkedRequirementsForPhase(phase.id);
|
|
3575
3582
|
const reqCount = linkedRequirements.length;
|
|
3576
3583
|
const summary = `${phase.title} — ${formatPhaseRef(phase.number, featureNumberOfPhase(phase, features))}${phase.shortId ? ` · ${phase.shortId}` : ""} (${phase.status}; ${phase.tasks.length} tasks${reqCount ? `; ${reqCount} linked requirement${reqCount === 1 ? "" : "s"}` : ""})`;
|
|
@@ -3921,7 +3928,8 @@ export default function planPiExtension(pi) {
|
|
|
3921
3928
|
const found = findTaskByRef(await st.loadAllPhases(), features, params.taskId.trim());
|
|
3922
3929
|
if (!found)
|
|
3923
3930
|
return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
|
|
3924
|
-
|
|
3931
|
+
if (params.full)
|
|
3932
|
+
markTaskRead(found.task.id, found.phase.id, found.phase.featureId);
|
|
3925
3933
|
const summary = `${found.task.title} — ${formatPhaseRef(found.phase.number, featureNumberOfPhase(found.phase, features))}/T${pad(found.task.number)}${found.task.shortId ? ` · ${found.task.shortId}` : ""} (${found.task.status})`;
|
|
3926
3934
|
if (!params.full)
|
|
3927
3935
|
return { content: [{ type: "text", text: summary }], details: {} };
|
|
@@ -3999,15 +4007,15 @@ export default function planPiExtension(pi) {
|
|
|
3999
4007
|
const existingPhase = await st.loadPhase(phaseId);
|
|
4000
4008
|
if (!existingPhase)
|
|
4001
4009
|
return { content: [{ type: "text", text: `Resolved phase ${phaseId} no longer exists. Refusing to create task.` }], details: {} };
|
|
4010
|
+
const initialStatus = params.status ?? "planned";
|
|
4011
|
+
if (initialStatus !== "planned") {
|
|
4012
|
+
return { content: [{ type: "text", text: "Tasks must be created planned. Use task_start for in-progress work and task_update for other justified status changes." }], details: {} };
|
|
4013
|
+
}
|
|
4002
4014
|
const shortName = clampSlug(params.shortName ?? title, 30, `task-${Date.now().toString(36)}`); // clamp+strip trailing dash; never empty
|
|
4003
4015
|
const taskId = createTaskId();
|
|
4004
4016
|
const identity = await st.allocateEntityIdentity("task", taskId);
|
|
4005
4017
|
const priority = await st.nextPriority("task", phaseId);
|
|
4006
4018
|
const now = nowISO();
|
|
4007
|
-
const initialStatus = params.status ?? "planned";
|
|
4008
|
-
if (initialStatus === "paused") {
|
|
4009
|
-
return { content: [{ type: "text", text: "A task cannot be created paused without an in-progress checkpoint. Create it planned, then use task_pause/task_switch." }], details: {} };
|
|
4010
|
-
}
|
|
4011
4019
|
const task = {
|
|
4012
4020
|
id: taskId, phaseId, number: identity.number, shortId: identity.shortId, priority, shortName,
|
|
4013
4021
|
title,
|
|
@@ -4025,8 +4033,8 @@ export default function planPiExtension(pi) {
|
|
|
4025
4033
|
dependsOn: [],
|
|
4026
4034
|
pauseSnapshot: null,
|
|
4027
4035
|
pauseHistory: [],
|
|
4028
|
-
startedAt:
|
|
4029
|
-
completedAt:
|
|
4036
|
+
startedAt: "",
|
|
4037
|
+
completedAt: "",
|
|
4030
4038
|
createdAt: now, updatedAt: now,
|
|
4031
4039
|
};
|
|
4032
4040
|
// Atomic read-modify-write on the phase file: serializes concurrent
|
|
@@ -4080,9 +4088,9 @@ export default function planPiExtension(pi) {
|
|
|
4080
4088
|
// Validate the motivation requirement for restrictive status transitions,
|
|
4081
4089
|
// mirroring the MCP adapter and the /planner task update command
|
|
4082
4090
|
// (AGENTS.md rule 8 — rejected writes must leave the files unchanged).
|
|
4083
|
-
if (
|
|
4091
|
+
if (params.status === "in-progress" && params.status !== found.task.status) {
|
|
4084
4092
|
return {
|
|
4085
|
-
content: [{ type: "text", text: "
|
|
4093
|
+
content: [{ type: "text", text: "Task start/resume transitions require task_start so lifecycle state, checkpoints, and return stacks remain consistent." }],
|
|
4086
4094
|
details: {},
|
|
4087
4095
|
};
|
|
4088
4096
|
}
|
|
@@ -4180,14 +4188,14 @@ export default function planPiExtension(pi) {
|
|
|
4180
4188
|
pi.registerTool({
|
|
4181
4189
|
name: "task_recommend",
|
|
4182
4190
|
label: "Task Recommend",
|
|
4183
|
-
description: "Return the recommended next task without starting work: continue one active task, otherwise choose ready work by feature → phase → task priority. Reports approved deviation/resume context and never blocks an explicit user choice.",
|
|
4191
|
+
description: "Return the recommended next task without starting work: continue one active task or the current phase, otherwise choose ready work by feature → phase → task priority. Reports approved deviation/resume context and never blocks an explicit user choice.",
|
|
4184
4192
|
parameters: Type.Object({}),
|
|
4185
4193
|
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
4186
4194
|
const st = await requirePlan(ctx);
|
|
4187
4195
|
if (!st)
|
|
4188
4196
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
4189
|
-
const [features, phases, project] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject()]);
|
|
4190
|
-
const result = recommendNextTask(features.features, phases, project.workDeviations);
|
|
4197
|
+
const [features, phases, project, resume] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject(), st.loadResume()]);
|
|
4198
|
+
const result = recommendNextTask(features.features, phases, project.workDeviations, resume?.currentPhaseId);
|
|
4191
4199
|
if (!result.candidate)
|
|
4192
4200
|
return { content: [{ type: "text", text: `No task recommendation: ${result.reason}` }], details: result };
|
|
4193
4201
|
const { candidate } = result;
|
|
@@ -4211,11 +4219,11 @@ export default function planPiExtension(pi) {
|
|
|
4211
4219
|
const st = await requirePlan(ctx);
|
|
4212
4220
|
if (!st)
|
|
4213
4221
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
4214
|
-
const [features, phases, project] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject()]);
|
|
4222
|
+
const [features, phases, project, focus] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject(), st.loadResume()]);
|
|
4215
4223
|
const temporary = findTaskByRef(phases, features.features, params.temporary_task.trim());
|
|
4216
4224
|
if (!temporary)
|
|
4217
4225
|
return { content: [{ type: "text", text: `Task not found: ${params.temporary_task}` }], details: {} };
|
|
4218
|
-
const selected = recommendNextTask(features.features, phases, project.workDeviations);
|
|
4226
|
+
const selected = recommendNextTask(features.features, phases, project.workDeviations, focus?.currentPhaseId);
|
|
4219
4227
|
const resume = params.resume_task ? findTaskByRef(phases, features.features, params.resume_task.trim()) : selected.candidate;
|
|
4220
4228
|
if (!resume)
|
|
4221
4229
|
return { content: [{ type: "text", text: `No resume task is available. Provide resume_task explicitly. ${selected.reason}` }], details: selected };
|
|
@@ -4311,7 +4319,7 @@ export default function planPiExtension(pi) {
|
|
|
4311
4319
|
const st = await requirePlan(ctx);
|
|
4312
4320
|
if (!st)
|
|
4313
4321
|
return { content: [{ type: "text", text: "No .planner/ found." }], details: {} };
|
|
4314
|
-
const [featuresDoc, phases, project] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject()]);
|
|
4322
|
+
const [featuresDoc, phases, project, focus] = await Promise.all([st.loadFeatures(), st.loadAllPhases(), st.loadProject(), st.loadResume()]);
|
|
4315
4323
|
const features = featuresDoc.features;
|
|
4316
4324
|
const source = findTaskByRef(phases, features, params.from_task.trim());
|
|
4317
4325
|
const target = findTaskByRef(phases, features, params.to_task.trim());
|
|
@@ -4324,6 +4332,17 @@ export default function planPiExtension(pi) {
|
|
|
4324
4332
|
if (source.task.status !== "in-progress" && !source.task.pauseSnapshot) {
|
|
4325
4333
|
return { content: [{ type: "text", text: `Task switch denied: source is ${source.task.status}; only in-progress or checkpointed work can be switched.` }], details: {} };
|
|
4326
4334
|
}
|
|
4335
|
+
const contextEligibility = contextReadEligibility(target.task.id, target.phase.id, target.phase.featureId);
|
|
4336
|
+
if (!contextEligibility.eligible) {
|
|
4337
|
+
return { content: [{ type: "text", text: `Task switch denied: context reads required. ${contextEligibility.reason}` }], details: contextEligibility };
|
|
4338
|
+
}
|
|
4339
|
+
const linkedRequirementIds = [
|
|
4340
|
+
...(await st.linkedRequirementsForPhase(target.phase.id)).map((requirement) => requirement.id),
|
|
4341
|
+
...(target.phase.featureId ? (await st.linkedRequirementsForFeature(target.phase.featureId)).map((requirement) => requirement.id) : []),
|
|
4342
|
+
];
|
|
4343
|
+
if (!hasReadRequirements(linkedRequirementIds)) {
|
|
4344
|
+
return { content: [{ type: "text", text: "Task switch denied: read the requirements linked to the target phase and feature before switching." }], details: { requirementIds: linkedRequirementIds } };
|
|
4345
|
+
}
|
|
4327
4346
|
const eligibility = checkExplicitTaskStart(features, phases, target.task.id, project.workDeviations);
|
|
4328
4347
|
if (!eligibility.eligible)
|
|
4329
4348
|
return { content: [{ type: "text", text: `Task switch denied: ${eligibility.reason}` }], details: eligibility };
|
|
@@ -4333,7 +4352,7 @@ export default function planPiExtension(pi) {
|
|
|
4333
4352
|
resumeLocation: params.resume_location.trim(), howToResume: params.how_to_resume.trim(),
|
|
4334
4353
|
relatedTaskId: target.task.id, pausedAt: timestamp, pausedBy: params.switched_by?.trim() ?? "",
|
|
4335
4354
|
};
|
|
4336
|
-
const selection = recommendNextTask(features, phases, project.workDeviations);
|
|
4355
|
+
const selection = recommendNextTask(features, phases, project.workDeviations, focus?.currentPhaseId);
|
|
4337
4356
|
const record = {
|
|
4338
4357
|
id: crypto.randomUUID(), recommendedTaskId: selection.candidate?.task.id ?? source.task.id,
|
|
4339
4358
|
temporaryTaskId: target.task.id, resumeTaskId: source.task.id, reason: snapshot.reason, snapshot,
|
|
@@ -4413,30 +4432,24 @@ export default function planPiExtension(pi) {
|
|
|
4413
4432
|
if (target.phase.id !== source.phase.id)
|
|
4414
4433
|
await st.syncTaskStatusRollup(target.phase.id);
|
|
4415
4434
|
await st.writeGenerated();
|
|
4435
|
+
invalidateReads();
|
|
4416
4436
|
const sourceRef = `${formatPhaseRef(source.phase.number, featureNumberOfPhase(source.phase, features))}/T${String(source.task.number).padStart(3, "0")}`;
|
|
4417
4437
|
const targetRef = `${formatPhaseRef(target.phase.number, featureNumberOfPhase(target.phase, features))}/T${String(target.task.number).padStart(3, "0")}`;
|
|
4418
|
-
const targetReadAdvisory = parentReadAdvisory(target.phase.featureId, target.phase.id);
|
|
4419
|
-
const targetRequirementAdvisory = requirementReadAdvisory([
|
|
4420
|
-
...(await st.linkedRequirementsForPhase(target.phase.id)).map((r) => r.id),
|
|
4421
|
-
...(target.phase.featureId ? (await st.linkedRequirementsForFeature(target.phase.featureId)).map((r) => r.id) : []),
|
|
4422
|
-
]);
|
|
4423
4438
|
return {
|
|
4424
4439
|
content: [{ type: "text", text: [
|
|
4425
4440
|
`🔀 Task switched: ${sourceRef} → ${targetRef}`,
|
|
4426
4441
|
`Resume checkpoint: ${snapshot.whatWasBeingDone}`,
|
|
4427
4442
|
`Return target: ${sourceRef} from ${snapshot.resumeLocation}`,
|
|
4428
4443
|
"Normal priority was deliberately overridden; completing the temporary task will emit RESUME REQUIRED.",
|
|
4429
|
-
...(targetReadAdvisory ? [targetReadAdvisory.trimStart()] : []),
|
|
4430
|
-
...(targetRequirementAdvisory ? [targetRequirementAdvisory.trimStart()] : []),
|
|
4431
4444
|
].join("\n") }],
|
|
4432
|
-
details: { deviation: record, snapshot, resumeTaskId: source.task.id, temporaryTaskId: target.task.id
|
|
4445
|
+
details: { deviation: record, snapshot, resumeTaskId: source.task.id, temporaryTaskId: target.task.id },
|
|
4433
4446
|
};
|
|
4434
4447
|
},
|
|
4435
4448
|
});
|
|
4436
4449
|
pi.registerTool({
|
|
4437
4450
|
name: "task_start",
|
|
4438
4451
|
label: "Task Start",
|
|
4439
|
-
description: "Set a task to in-progress or resume checkpointed work. BEFORE calling, read
|
|
4452
|
+
description: "Set a task to in-progress or resume checkpointed work. BEFORE calling, read task_get(full=true), then its parent phase_get(full=true), then its parent feature_get(full=true), in that exact order. Read linked requirements with requirement_list when present. A different active task must first be suspended through task_switch.",
|
|
4440
4453
|
parameters: Type.Object({
|
|
4441
4454
|
taskId: Type.String({ description: "Task ref: F00x/P00x/T00x, bare T00x (global), 5-char shortId, UUID, or title to start" }),
|
|
4442
4455
|
}),
|
|
@@ -4453,12 +4466,22 @@ export default function planPiExtension(pi) {
|
|
|
4453
4466
|
return { content: [{ type: "text", text: `Task ${task.id} is already in-progress.` }], details: task };
|
|
4454
4467
|
if (task.status === "done")
|
|
4455
4468
|
return { content: [{ type: "text", text: `Task ${task.id} is done. Use task_update to reopen.` }], details: task };
|
|
4456
|
-
const
|
|
4457
|
-
|
|
4469
|
+
const contextEligibility = contextReadEligibility(task.id, found.phase.id, found.phase.featureId);
|
|
4470
|
+
if (!contextEligibility.eligible) {
|
|
4471
|
+
return { content: [{ type: "text", text: `Task start denied: context reads required. ${contextEligibility.reason}` }], details: contextEligibility };
|
|
4472
|
+
}
|
|
4473
|
+
const linkedRequirementIds = [
|
|
4474
|
+
...(await st.linkedRequirementsForPhase(found.phase.id)).map((requirement) => requirement.id),
|
|
4475
|
+
...(found.phase.featureId ? (await st.linkedRequirementsForFeature(found.phase.featureId)).map((requirement) => requirement.id) : []),
|
|
4476
|
+
];
|
|
4477
|
+
if (!hasReadRequirements(linkedRequirementIds)) {
|
|
4478
|
+
return { content: [{ type: "text", text: "Task start denied: read the requirements linked to the task's phase and feature first." }], details: { requirementIds: linkedRequirementIds } };
|
|
4479
|
+
}
|
|
4480
|
+
const [project, phases, focus] = await Promise.all([st.loadProject(), st.loadAllPhases(), st.loadResume()]);
|
|
4458
4481
|
const eligibility = checkExplicitTaskStart(features, phases, task.id, project.workDeviations);
|
|
4459
4482
|
if (!eligibility.eligible)
|
|
4460
4483
|
return { content: [{ type: "text", text: `Task start denied: ${eligibility.reason}` }], details: eligibility };
|
|
4461
|
-
const selection = recommendNextTask(features, phases, project.workDeviations);
|
|
4484
|
+
const selection = recommendNextTask(features, phases, project.workDeviations, focus?.currentPhaseId);
|
|
4462
4485
|
if (selection.kind === "conflict") {
|
|
4463
4486
|
return { content: [{ type: "text", text: `${selection.reason} Pause/reconcile active tasks before starting another task.` }], details: selection };
|
|
4464
4487
|
}
|
|
@@ -4537,16 +4560,10 @@ export default function planPiExtension(pi) {
|
|
|
4537
4560
|
await st.writeGenerated();
|
|
4538
4561
|
if (!startedTask)
|
|
4539
4562
|
return { content: [{ type: "text", text: `Task not found: ${params.taskId}` }], details: {} };
|
|
4540
|
-
const readAdvisory = parentReadAdvisory(parentFeature?.id, found.phase.id);
|
|
4541
|
-
const requirementAdvisory = requirementReadAdvisory([
|
|
4542
|
-
...(phaseWithRequirements.linkedRequirements ?? []).map((r) => r.id),
|
|
4543
|
-
...featureRequirements.map((r) => r.id),
|
|
4544
|
-
]);
|
|
4545
4563
|
return {
|
|
4546
|
-
content: [{ type: "text", text: `✅ Task started: ${formatPhaseRef(found.phase.number, featureNumberOfPhase(found.phase, features))}/T${String(startedTask.number).padStart(3, "0")} — ${startedTask.title} (in-progress)${startedTask.shortId ? ` · ${startedTask.shortId}` : ""}${phaseContext}${advisory}
|
|
4564
|
+
content: [{ type: "text", text: `✅ Task started: ${formatPhaseRef(found.phase.number, featureNumberOfPhase(found.phase, features))}/T${String(startedTask.number).padStart(3, "0")} — ${startedTask.title} (in-progress)${startedTask.shortId ? ` · ${startedTask.shortId}` : ""}${phaseContext}${advisory}` }],
|
|
4547
4565
|
details: startedTask,
|
|
4548
4566
|
...(resumeProposal ? { resumeRequired: resumeProposal.structured } : {}),
|
|
4549
|
-
...(readAdvisory || requirementAdvisory ? { readRequired: true } : {}),
|
|
4550
4567
|
};
|
|
4551
4568
|
},
|
|
4552
4569
|
});
|
|
@@ -4994,7 +5011,7 @@ export default function planPiExtension(pi) {
|
|
|
4994
5011
|
"",
|
|
4995
5012
|
"Operational rules:",
|
|
4996
5013
|
"- Handoffs are context, not locks. Read the relevant handoff with handoff show <ref> before resuming.",
|
|
4997
|
-
"- BEFORE work: read
|
|
5014
|
+
"- BEFORE work: read task_get(full=true), then its parent phase_get(full=true), then its parent feature_get(full=true), in that exact order. Read linked requirements with requirement_list when present. task_start and task_switch reject work until these reads are complete; only then touch code. AFTER finishing a task: task_complete.",
|
|
4998
5015
|
"- Record every new decision or user-agreed modification in both the relevant feature and phase before treating the discussion as complete.",
|
|
4999
5016
|
"- Use task_update with motivation for blocked/canceled/rejected/deferred/waiting/planned(from non-planned).",
|
|
5000
5017
|
"- Planner ops (status/handoff/planner metadata) are NOT code edits; they are always allowed.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-plan/pi-adapter",
|
|
3
|
-
"version": "0.2.22-next.
|
|
3
|
+
"version": "0.2.22-next.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pi agent extension for Agent Plan — structured project planning in .planner/",
|
|
6
6
|
"keywords": [
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"typebox": "^1.1.24",
|
|
30
30
|
"zod": "^3.24.0",
|
|
31
|
-
"@agent-plan/core": "0.2.22-next.
|
|
32
|
-
"@agent-plan/server": "0.2.22-next.
|
|
31
|
+
"@agent-plan/core": "0.2.22-next.4",
|
|
32
|
+
"@agent-plan/server": "0.2.22-next.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|