@agent-plan/pi-adapter 0.2.22-next.2 → 0.2.22-next.3
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,CAu6H9D"}
|
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
|
}
|
|
@@ -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 };
|
|
@@ -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,6 +4466,17 @@ 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 };
|
|
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
|
+
}
|
|
4456
4480
|
const project = await st.loadProject();
|
|
4457
4481
|
const phases = await st.loadAllPhases();
|
|
4458
4482
|
const eligibility = checkExplicitTaskStart(features, phases, task.id, project.workDeviations);
|
|
@@ -4537,16 +4561,10 @@ export default function planPiExtension(pi) {
|
|
|
4537
4561
|
await st.writeGenerated();
|
|
4538
4562
|
if (!startedTask)
|
|
4539
4563
|
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
4564
|
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}
|
|
4565
|
+
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
4566
|
details: startedTask,
|
|
4548
4567
|
...(resumeProposal ? { resumeRequired: resumeProposal.structured } : {}),
|
|
4549
|
-
...(readAdvisory || requirementAdvisory ? { readRequired: true } : {}),
|
|
4550
4568
|
};
|
|
4551
4569
|
},
|
|
4552
4570
|
});
|
|
@@ -4994,7 +5012,7 @@ export default function planPiExtension(pi) {
|
|
|
4994
5012
|
"",
|
|
4995
5013
|
"Operational rules:",
|
|
4996
5014
|
"- Handoffs are context, not locks. Read the relevant handoff with handoff show <ref> before resuming.",
|
|
4997
|
-
"- BEFORE work: read
|
|
5015
|
+
"- 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
5016
|
"- Record every new decision or user-agreed modification in both the relevant feature and phase before treating the discussion as complete.",
|
|
4999
5017
|
"- Use task_update with motivation for blocked/canceled/rejected/deferred/waiting/planned(from non-planned).",
|
|
5000
5018
|
"- 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.3",
|
|
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.3",
|
|
32
|
+
"@agent-plan/server": "0.2.22-next.3"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|