@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.
Files changed (79) hide show
  1. package/dist/accepted-decision-guard.d.ts +94 -0
  2. package/dist/accepted-decision-guard.d.ts.map +1 -0
  3. package/dist/accepted-decision-guard.js +110 -0
  4. package/dist/checklist.d.ts +28 -0
  5. package/dist/checklist.d.ts.map +1 -1
  6. package/dist/checklist.js +59 -0
  7. package/dist/feature-delete.d.ts +62 -0
  8. package/dist/feature-delete.d.ts.map +1 -0
  9. package/dist/feature-delete.js +49 -0
  10. package/dist/guard-decision.d.ts +118 -0
  11. package/dist/guard-decision.d.ts.map +1 -0
  12. package/dist/guard-decision.js +203 -0
  13. package/dist/handoff-context.d.ts +130 -1
  14. package/dist/handoff-context.d.ts.map +1 -1
  15. package/dist/handoff-context.js +167 -9
  16. package/dist/handoff-reply.d.ts +108 -0
  17. package/dist/handoff-reply.d.ts.map +1 -0
  18. package/dist/handoff-reply.js +279 -0
  19. package/dist/index.d.ts +15 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +15 -1
  22. package/dist/mutation-reply.d.ts +105 -0
  23. package/dist/mutation-reply.d.ts.map +1 -0
  24. package/dist/mutation-reply.js +203 -0
  25. package/dist/naming.d.ts +1 -1
  26. package/dist/naming.d.ts.map +1 -1
  27. package/dist/naming.js +1 -1
  28. package/dist/pagination.d.ts +19 -0
  29. package/dist/pagination.d.ts.map +1 -0
  30. package/dist/pagination.js +15 -0
  31. package/dist/payload-fallback.js +1 -1
  32. package/dist/plan-store.d.ts +141 -8
  33. package/dist/plan-store.d.ts.map +1 -1
  34. package/dist/plan-store.js +459 -87
  35. package/dist/project-context-migration.d.ts +5 -0
  36. package/dist/project-context-migration.d.ts.map +1 -1
  37. package/dist/project-context-migration.js +13 -0
  38. package/dist/project-context.d.ts +395 -0
  39. package/dist/project-context.d.ts.map +1 -0
  40. package/dist/project-context.js +295 -0
  41. package/dist/recap.d.ts.map +1 -1
  42. package/dist/recap.js +27 -0
  43. package/dist/recommend-reply.d.ts +51 -0
  44. package/dist/recommend-reply.d.ts.map +1 -0
  45. package/dist/recommend-reply.js +84 -0
  46. package/dist/refs.d.ts +16 -0
  47. package/dist/refs.d.ts.map +1 -1
  48. package/dist/refs.js +38 -0
  49. package/dist/renderer.d.ts.map +1 -1
  50. package/dist/schema.d.ts +7 -2
  51. package/dist/schema.d.ts.map +1 -1
  52. package/dist/schema.js +8 -3
  53. package/dist/stranded-phase.d.ts +73 -0
  54. package/dist/stranded-phase.d.ts.map +1 -0
  55. package/dist/stranded-phase.js +93 -0
  56. package/dist/task-completion-gate.d.ts +84 -0
  57. package/dist/task-completion-gate.d.ts.map +1 -0
  58. package/dist/task-completion-gate.js +72 -0
  59. package/dist/task-context.d.ts +1 -0
  60. package/dist/task-context.d.ts.map +1 -1
  61. package/dist/task-context.js +31 -13
  62. package/dist/task-lifecycle.d.ts +12 -0
  63. package/dist/task-lifecycle.d.ts.map +1 -0
  64. package/dist/task-lifecycle.js +23 -0
  65. package/dist/task-order-context.d.ts +72 -0
  66. package/dist/task-order-context.d.ts.map +1 -0
  67. package/dist/task-order-context.js +82 -0
  68. package/dist/task-selection.d.ts.map +1 -1
  69. package/dist/task-selection.js +14 -12
  70. package/dist/task-start-outcome.d.ts +26 -0
  71. package/dist/task-start-outcome.d.ts.map +1 -1
  72. package/dist/task-start-outcome.js +23 -0
  73. package/dist/text-bounds.d.ts +32 -0
  74. package/dist/text-bounds.d.ts.map +1 -0
  75. package/dist/text-bounds.js +43 -0
  76. package/dist/write-coordination.d.ts +2 -2
  77. package/dist/write-coordination.js +2 -2
  78. package/package.json +1 -1
  79. package/planner-skill.md +30 -12
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from "./naming.js";
2
2
  export * from "./refs.js";
3
+ export * from "./render-utils.js";
4
+ export * from "./pagination.js";
5
+ export * from "./task-lifecycle.js";
3
6
  export * from "./schema.js";
4
7
  export * from "./checklist.js";
5
8
  export * from "./recap.js";
@@ -7,18 +10,29 @@ export * from "./planner-rules.js";
7
10
  export * from "./planner-skill.js";
8
11
  export * from "./display-status.js";
9
12
  export * from "./description-freshness.js";
13
+ export * from "./text-bounds.js";
10
14
  export * from "./task-context.js";
11
15
  export * from "./task-selection.js";
12
16
  export * from "./task-start-outcome.js";
17
+ export * from "./task-order-context.js";
18
+ export * from "./task-completion-gate.js";
19
+ export * from "./stranded-phase.js";
13
20
  export * from "./read-tracking.js";
14
21
  export * from "./payload-fallback.js";
15
22
  export * from "./handoff-context.js";
23
+ export * from "./handoff-reply.js";
24
+ export * from "./mutation-reply.js";
25
+ export * from "./recommend-reply.js";
26
+ export * from "./feature-delete.js";
27
+ export * from "./accepted-decision-guard.js";
16
28
  export * from "./project-context-migration.js";
29
+ export * from "./project-context.js";
17
30
  export * from "./requirement-macro-tasks.js";
18
31
  export * from "./package-version.js";
19
32
  export * from "./runtime-diagnostics.js";
20
33
  export * from "./write-coordination.js";
21
- export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, type IdeaCreateInput, type IdeaUpdateInput, type IdeaPromotionTargetInput, type AcceptedDecisionOwner, type AcceptedDecisionCreateInput, type AcceptedDecisionUpdateInput, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
34
+ export * from "./guard-decision.js";
35
+ export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, nowISO, type IdeaCreateInput, type IdeaUpdateInput, type IdeaPromotionTargetInput, type AcceptedDecisionOwner, type AcceptedDecisionCreateInput, type AcceptedDecisionUpdateInput, type RecordProjectContextReadInput, type RecordProjectContextReadResult, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
22
36
  export { PlanRenderer } from "./renderer.js";
23
37
  export { ExportService } from "./export-service.js";
24
38
  export type { CodebaseProfile, ResumeFocus, ActivityEntry, ActivityLog, AmbientFacts } from "./schema.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,kCAAkC,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,KAAK,2BAA2B,EAAE,KAAK,2BAA2B,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1c,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,kCAAkC,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,KAAK,2BAA2B,EAAE,KAAK,2BAA2B,EAAE,KAAK,6BAA6B,EAAE,KAAK,8BAA8B,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3hB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from "./naming.js";
2
2
  export * from "./refs.js";
3
+ export * from "./render-utils.js";
4
+ export * from "./pagination.js";
5
+ export * from "./task-lifecycle.js";
3
6
  export * from "./schema.js";
4
7
  export * from "./checklist.js";
5
8
  export * from "./recap.js";
@@ -7,17 +10,28 @@ export * from "./planner-rules.js";
7
10
  export * from "./planner-skill.js";
8
11
  export * from "./display-status.js";
9
12
  export * from "./description-freshness.js";
13
+ export * from "./text-bounds.js";
10
14
  export * from "./task-context.js";
11
15
  export * from "./task-selection.js";
12
16
  export * from "./task-start-outcome.js";
17
+ export * from "./task-order-context.js";
18
+ export * from "./task-completion-gate.js";
19
+ export * from "./stranded-phase.js";
13
20
  export * from "./read-tracking.js";
14
21
  export * from "./payload-fallback.js";
15
22
  export * from "./handoff-context.js";
23
+ export * from "./handoff-reply.js";
24
+ export * from "./mutation-reply.js";
25
+ export * from "./recommend-reply.js";
26
+ export * from "./feature-delete.js";
27
+ export * from "./accepted-decision-guard.js";
16
28
  export * from "./project-context-migration.js";
29
+ export * from "./project-context.js";
17
30
  export * from "./requirement-macro-tasks.js";
18
31
  export * from "./package-version.js";
19
32
  export * from "./runtime-diagnostics.js";
20
33
  export * from "./write-coordination.js";
21
- export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock } from "./plan-store.js";
34
+ export * from "./guard-decision.js";
35
+ export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, nowISO } from "./plan-store.js";
22
36
  export { PlanRenderer } from "./renderer.js";
23
37
  export { ExportService } from "./export-service.js";
@@ -0,0 +1,105 @@
1
+ import type { PlannerPayloadEntity, PlannerPayloadOperation } from "./payload-fallback.js";
2
+ /**
3
+ * Per-field echo budget. A changed field is worth showing back so the caller
4
+ * can confirm what landed without a second call; a changed field the size of
5
+ * a task description is not. 2,000 characters is about three screens — long
6
+ * enough to read a title, a status, a checklist or a short note whole, short
7
+ * enough that echoing a full description is impossible.
8
+ */
9
+ export declare const MUTATION_ECHO_MAX_FIELD_CHARS = 2000;
10
+ /**
11
+ * Ceiling for the whole structured payload of a mutation reply. Chosen to
12
+ * match MAX_PHASE_WORK_MAP_CHARS and MAX_ACCEPTED_DECISION_CONTEXT_CHARS so
13
+ * an agent has one number in mind for planner payloads rather than four.
14
+ * When the assembled payload exceeds it, echoed values are dropped to names
15
+ * (see `boundMutationEcho`) — the caller still learns exactly which fields
16
+ * changed, which is the part it cannot get anywhere else.
17
+ */
18
+ export declare const MUTATION_REPLY_MAX_CHARS = 8000;
19
+ /**
20
+ * The long-text size a planner tool accepts in one call, stated up front so
21
+ * a caller does not discover it by being rejected. This is not a limit the
22
+ * planner imposes for its own sake: a long `description` can be dropped by
23
+ * the transport before it ever reaches the planner, which arrives here as
24
+ * "no mutable fields received" and is why DESCRIPTION_MARKDOWN_FALLBACK_REQUIRED
25
+ * exists. Measured: ~13,500 characters accepted, ~17,000 rejected. 12,000 is
26
+ * under the observed floor with room to spare.
27
+ *
28
+ * Above this, write the prose to a committed file under `.planner/docs/` and
29
+ * pass `descriptionRef` — see suggestedDescriptionRefPath in payload-fallback.ts.
30
+ */
31
+ export declare const PLANNER_LONG_TEXT_FIELD_MAX_CHARS = 12000;
32
+ /**
33
+ * One sentence for a tool description, so the limit is discoverable at the
34
+ * point of the call instead of after it fails. Every tool taking a long text
35
+ * field appends this to its own description.
36
+ */
37
+ export declare function longTextFieldLimitNotice(field?: string): string;
38
+ /** A tool reply as one result: the human-readable body and the structured
39
+ * payload, with no field present in both. */
40
+ export interface MutationReply {
41
+ text: string;
42
+ structured: Record<string, unknown>;
43
+ }
44
+ /** Who was mutated, in the terms a caller refers to it by. The canonical
45
+ * `ref` is what travels — never the raw text the caller happened to type,
46
+ * which may be a short id, a title or a UUID. */
47
+ export interface MutationIdentity {
48
+ entity: PlannerPayloadEntity;
49
+ ref: string;
50
+ id?: string;
51
+ shortId?: string;
52
+ title?: string;
53
+ status?: string;
54
+ }
55
+ export interface MutationReplyInput {
56
+ identity: MutationIdentity;
57
+ operation: PlannerPayloadOperation;
58
+ /** Fields the caller actually changed, in the order they were received. */
59
+ updatedFields: readonly string[];
60
+ /** The persisted values of those fields, to be echoed back within budget.
61
+ * Keys outside `updatedFields` are ignored: a mutation reply never carries
62
+ * a field the mutation did not touch. */
63
+ changedValues?: Record<string, unknown>;
64
+ /** Diagnostics that are not the entity — description freshness, stale
65
+ * parents, lost checklist ticks, resume-required notices. These are the
66
+ * reason to read a mutation result at all, so they are never trimmed. */
67
+ extras?: Record<string, unknown>;
68
+ /** Extra lines for the human channel, such as a lost-tick warning. */
69
+ notices?: readonly string[];
70
+ /** The command that returns the full entity, for a caller that wants what
71
+ * this reply deliberately leaves out. */
72
+ readBackCommand?: string;
73
+ }
74
+ /**
75
+ * Build the one reply every mutation tool returns.
76
+ *
77
+ * What a caller gets: that the write happened, which fields it touched, the
78
+ * canonical ref and identity of what it touched, the changed values within
79
+ * budget, and every diagnostic. What it does not get: the rest of the
80
+ * entity, which it can read with the command named in `readBack`.
81
+ *
82
+ * `updated` (or `discussed`), `updatedFields` and any `errorCode` keep their
83
+ * exact prior shape — mutation-integrity checking across the whole project
84
+ * reads those three and nothing else.
85
+ */
86
+ export declare function buildMutationReply(input: MutationReplyInput): MutationReply;
87
+ export interface DerivedStatusReadOnlyInput {
88
+ entity: "feature" | "phase";
89
+ ref: string;
90
+ attemptedStatus: string;
91
+ effectiveStatus: string;
92
+ /** Full "retry without the status field" instruction. Each adapter names
93
+ * its own update tool (`planner-feature-update` vs `feature_update`), so
94
+ * this one line stays adapter-supplied rather than templated here —
95
+ * moving it into the core would restate a tool name the core doesn't own. */
96
+ retryAction: string;
97
+ }
98
+ /**
99
+ * A feature/phase `status` field cannot be set directly — it is derived
100
+ * from child phases/tasks. Both adapters carried an identical copy of this
101
+ * refusal for feature_update/phase_update; only the retry line's tool name
102
+ * differs, which is why it is the one caller-supplied piece.
103
+ */
104
+ export declare function derivedStatusReadOnlyReply(input: DerivedStatusReadOnlyInput): MutationReply;
105
+ //# sourceMappingURL=mutation-reply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-reply.d.ts","sourceRoot":"","sources":["../src/mutation-reply.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAE3F;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,OAAQ,CAAC;AAEnD;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,QAAS,CAAC;AAExD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,SAAgB,GAAG,MAAM,CAEtE;AAED;6CAC6C;AAC7C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED;;iDAEiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,uBAAuB,CAAC;IACnC,2EAA2E;IAC3E,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;6CAEyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;6EAEyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,sEAAsE;IACtE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;6CACyC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CA6C3E;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB;;;kFAG8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,aAAa,CA4B3F"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Shared reply shaping for every mutation tool (update, discuss, and the
3
+ * small mutations around them).
4
+ *
5
+ * The rule this file exists to hold: a mutation result says what changed.
6
+ * It does not say what the entity now contains. Before P104(F005)/T417 each
7
+ * adapter hand-built `{ ...entity, updated: true }`, so changing one field
8
+ * on this repository's own P104 returned 139,805 characters — every task
9
+ * body in the phase, echoed back to a caller who already knew them. A task
10
+ * update peaked at 33,499. The harness had to spill those results to a file,
11
+ * and reading back whether `updated` was true then needed jq.
12
+ *
13
+ * The bound lives here rather than in an adapter for the reason in AGENTS.md
14
+ * rule 4: both adapters render what the core returns, so neither can drift
15
+ * and a third harness inherits the bound without knowing it exists. Four
16
+ * separate defects in P104 came from logic duplicated per adapter.
17
+ *
18
+ * Channel discipline matches handoff-reply.ts: `text` is the human-readable
19
+ * body, `structured` is the machine-readable payload, and no field is
20
+ * carried by both.
21
+ */
22
+ import { truncateAtSafeBoundary } from "./text-bounds.js";
23
+ /**
24
+ * Per-field echo budget. A changed field is worth showing back so the caller
25
+ * can confirm what landed without a second call; a changed field the size of
26
+ * a task description is not. 2,000 characters is about three screens — long
27
+ * enough to read a title, a status, a checklist or a short note whole, short
28
+ * enough that echoing a full description is impossible.
29
+ */
30
+ export const MUTATION_ECHO_MAX_FIELD_CHARS = 2_000;
31
+ /**
32
+ * Ceiling for the whole structured payload of a mutation reply. Chosen to
33
+ * match MAX_PHASE_WORK_MAP_CHARS and MAX_ACCEPTED_DECISION_CONTEXT_CHARS so
34
+ * an agent has one number in mind for planner payloads rather than four.
35
+ * When the assembled payload exceeds it, echoed values are dropped to names
36
+ * (see `boundMutationEcho`) — the caller still learns exactly which fields
37
+ * changed, which is the part it cannot get anywhere else.
38
+ */
39
+ export const MUTATION_REPLY_MAX_CHARS = 8_000;
40
+ /**
41
+ * The long-text size a planner tool accepts in one call, stated up front so
42
+ * a caller does not discover it by being rejected. This is not a limit the
43
+ * planner imposes for its own sake: a long `description` can be dropped by
44
+ * the transport before it ever reaches the planner, which arrives here as
45
+ * "no mutable fields received" and is why DESCRIPTION_MARKDOWN_FALLBACK_REQUIRED
46
+ * exists. Measured: ~13,500 characters accepted, ~17,000 rejected. 12,000 is
47
+ * under the observed floor with room to spare.
48
+ *
49
+ * Above this, write the prose to a committed file under `.planner/docs/` and
50
+ * pass `descriptionRef` — see suggestedDescriptionRefPath in payload-fallback.ts.
51
+ */
52
+ export const PLANNER_LONG_TEXT_FIELD_MAX_CHARS = 12_000;
53
+ /**
54
+ * One sentence for a tool description, so the limit is discoverable at the
55
+ * point of the call instead of after it fails. Every tool taking a long text
56
+ * field appends this to its own description.
57
+ */
58
+ export function longTextFieldLimitNotice(field = "description") {
59
+ return `Keep ${field} under ${PLANNER_LONG_TEXT_FIELD_MAX_CHARS.toLocaleString("en-US")} characters; above that the transport may drop it before it arrives. Put longer prose in a committed .planner/docs/ file and pass ${field}Ref instead.`;
60
+ }
61
+ /**
62
+ * Bound one echoed value. A short string travels whole. A long one is cut at
63
+ * a safe boundary — paragraph, then line, then word, the same helper the
64
+ * handoff path uses — and reports its true length, so a caller can tell a
65
+ * 2,000-character field from a 20,000-character one without receiving either
66
+ * in full. Arrays and objects are measured by their serialized size and
67
+ * degrade to a count rather than a preview, because half an array read as
68
+ * whole is worse than no array at all.
69
+ */
70
+ function boundEchoedValue(value) {
71
+ if (typeof value === "string") {
72
+ if (value.length <= MUTATION_ECHO_MAX_FIELD_CHARS)
73
+ return { value, truncated: false };
74
+ const bounded = truncateAtSafeBoundary(value, MUTATION_ECHO_MAX_FIELD_CHARS);
75
+ return {
76
+ value: { preview: bounded, fullLength: value.length, truncated: true },
77
+ truncated: true,
78
+ };
79
+ }
80
+ if (value === null || value === undefined || typeof value !== "object") {
81
+ return { value, truncated: false };
82
+ }
83
+ const serialized = JSON.stringify(value) ?? "";
84
+ if (serialized.length <= MUTATION_ECHO_MAX_FIELD_CHARS)
85
+ return { value, truncated: false };
86
+ if (Array.isArray(value)) {
87
+ return { value: { count: value.length, serializedLength: serialized.length, truncated: true }, truncated: true };
88
+ }
89
+ return { value: { keys: Object.keys(value), serializedLength: serialized.length, truncated: true }, truncated: true };
90
+ }
91
+ /**
92
+ * Assemble the echo of changed fields within the reply ceiling. Fields are
93
+ * taken in the order the caller changed them and the budget is shared, so
94
+ * one large field cannot crowd out the rest: what does not fit is named in
95
+ * `omittedValues` rather than silently dropped. Naming what was left out is
96
+ * the same degradation the prepare path uses — elided must never mean
97
+ * invisible.
98
+ */
99
+ function boundMutationEcho(updatedFields, changedValues, budget) {
100
+ const changed = {};
101
+ const omittedValues = [];
102
+ const truncatedFields = [];
103
+ let remaining = budget;
104
+ for (const field of updatedFields) {
105
+ if (!(field in changedValues))
106
+ continue;
107
+ const bounded = boundEchoedValue(changedValues[field]);
108
+ const cost = (JSON.stringify(bounded.value) ?? "").length + field.length + 4;
109
+ if (cost > remaining) {
110
+ omittedValues.push(field);
111
+ continue;
112
+ }
113
+ remaining -= cost;
114
+ changed[field] = bounded.value;
115
+ if (bounded.truncated)
116
+ truncatedFields.push(field);
117
+ }
118
+ return { changed, omittedValues, truncatedFields };
119
+ }
120
+ /**
121
+ * Build the one reply every mutation tool returns.
122
+ *
123
+ * What a caller gets: that the write happened, which fields it touched, the
124
+ * canonical ref and identity of what it touched, the changed values within
125
+ * budget, and every diagnostic. What it does not get: the rest of the
126
+ * entity, which it can read with the command named in `readBack`.
127
+ *
128
+ * `updated` (or `discussed`), `updatedFields` and any `errorCode` keep their
129
+ * exact prior shape — mutation-integrity checking across the whole project
130
+ * reads those three and nothing else.
131
+ */
132
+ export function buildMutationReply(input) {
133
+ const { identity, operation, updatedFields } = input;
134
+ const fields = [...updatedFields];
135
+ const extras = input.extras ?? {};
136
+ const identityPayload = {
137
+ entity: identity.entity,
138
+ ref: identity.ref,
139
+ ...(identity.id ? { id: identity.id } : {}),
140
+ ...(identity.shortId ? { shortId: identity.shortId } : {}),
141
+ ...(identity.title ? { title: identity.title } : {}),
142
+ ...(identity.status ? { status: identity.status } : {}),
143
+ };
144
+ // Everything except the echo is non-negotiable, so the echo gets whatever
145
+ // the ceiling leaves once identity, fields and diagnostics are counted.
146
+ const fixedCost = (JSON.stringify({ ...identityPayload, updatedFields: fields, ...extras }) ?? "").length;
147
+ const { changed, omittedValues, truncatedFields } = boundMutationEcho(fields, input.changedValues ?? {}, Math.max(0, MUTATION_REPLY_MAX_CHARS - fixedCost));
148
+ const structured = {
149
+ ...(operation === "discuss" ? { discussed: true } : { updated: true }),
150
+ ...identityPayload,
151
+ updatedFields: fields,
152
+ ...(Object.keys(changed).length > 0 ? { changed } : {}),
153
+ ...(truncatedFields.length > 0 ? { truncatedFields } : {}),
154
+ ...(omittedValues.length > 0 ? { omittedValues } : {}),
155
+ ...extras,
156
+ ...(input.readBackCommand ? { readBack: input.readBackCommand } : {}),
157
+ };
158
+ const descriptor = [identity.ref, identity.title].filter(Boolean).join(" — ");
159
+ const statusSuffix = identity.status ? ` (${identity.status})` : "";
160
+ const shortIdSuffix = identity.shortId ? ` · ${identity.shortId}` : "";
161
+ const verb = operation === "discuss" ? "discussed/updated" : "updated";
162
+ const entityLabel = identity.entity.charAt(0).toUpperCase() + identity.entity.slice(1);
163
+ const lines = [
164
+ `✅ ${entityLabel} ${verb}: ${descriptor}${statusSuffix}${shortIdSuffix}. Fields saved: ${fields.join(", ")}.`,
165
+ ...(input.notices ?? []).filter((notice) => notice.trim().length > 0),
166
+ ];
167
+ return { text: lines.join(" "), structured };
168
+ }
169
+ /**
170
+ * A feature/phase `status` field cannot be set directly — it is derived
171
+ * from child phases/tasks. Both adapters carried an identical copy of this
172
+ * refusal for feature_update/phase_update; only the retry line's tool name
173
+ * differs, which is why it is the one caller-supplied piece.
174
+ */
175
+ export function derivedStatusReadOnlyReply(input) {
176
+ const { entity, ref, attemptedStatus, effectiveStatus, retryAction } = input;
177
+ const childKind = entity === "feature" ? "phases" : "tasks";
178
+ const label = `${entity[0].toUpperCase()}${entity.slice(1)}`;
179
+ const nextActions = [
180
+ `${label} status is derived from child ${childKind}; update the child ${childKind} instead of setting ${entity}.status.`,
181
+ retryAction,
182
+ ];
183
+ return {
184
+ text: [
185
+ `❌ ${entity.toUpperCase()} UPDATE FAILED [DERIVED_STATUS_READ_ONLY]`,
186
+ `${label} status is derived from child ${childKind} and cannot be set directly.`,
187
+ `Attempted status: ${attemptedStatus}`,
188
+ `Effective derived status: ${effectiveStatus}`,
189
+ "No planner data was changed.",
190
+ "Next required actions:",
191
+ ...nextActions.map((action, index) => `${index + 1}. ${action}`),
192
+ ].join("\n"),
193
+ structured: {
194
+ updated: false,
195
+ errorCode: "DERIVED_STATUS_READ_ONLY",
196
+ entity,
197
+ ref,
198
+ attemptedStatus,
199
+ effectiveStatus,
200
+ nextActions,
201
+ },
202
+ };
203
+ }
package/dist/naming.d.ts CHANGED
@@ -52,7 +52,7 @@ export declare function createPhaseId(): string;
52
52
  /** True for legacy phase ids that are NOT feature-scoped (e.g. `phase-01-...`). */
53
53
  export declare function isLegacyPhaseId(phaseId: string): boolean;
54
54
  /** Compute the feature-scoped id for a legacy phase, preserving featureId/number/slug. */
55
- export declare function migratePhaseId(featureId: string, number: number, slug: string): string;
55
+ export declare function migratePhaseId(_featureId: string, _number: number, _slug: string): string;
56
56
  export declare function createTaskId(): string;
57
57
  export declare function createRequirementId(): string;
58
58
  export declare function createIdeaId(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAKA;iFACiF;AACjF,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAErE,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAEhD,4EAA4E;AAC5E,eAAO,MAAM,YAAY,QAAoE,CAAC;AAE9F,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtD;AAED;;gEAEgE;AAChE,wBAAsB,sBAAsB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACnE,IAAI,EAAE,SAAS,GAAG,OAAO,EACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GACnC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAStD;AAGD;;;;;qCAKqC;AACrC,wBAAgB,aAAa,CAAC,QAAQ,GAAE,GAAG,CAAC,MAAM,CAAa,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BtF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;8BAI8B;AAC9B,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,EAAE,QAAQ,SAAa,GAAG,MAAM,CAGnF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;iBAEiB;AACjB,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,EAChD,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GACzC,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C"}
1
+ {"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAKA;iFACiF;AACjF,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAErE,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAEhD,4EAA4E;AAC5E,eAAO,MAAM,YAAY,QAAoE,CAAC;AAE9F,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtD;AAED;;gEAEgE;AAChE,wBAAsB,sBAAsB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACnE,IAAI,EAAE,SAAS,GAAG,OAAO,EACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GACnC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAStD;AAGD;;;;;qCAKqC;AACrC,wBAAgB,aAAa,CAAC,QAAQ,GAAE,GAAG,CAAC,MAAM,CAAa,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BtF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;8BAI8B;AAC9B,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAK,EAAE,QAAQ,SAAa,GAAG,MAAM,CAGnF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;iBAEiB;AACjB,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,EAChD,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GACzC,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzF;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C"}
package/dist/naming.js CHANGED
@@ -110,7 +110,7 @@ export function isLegacyPhaseId(phaseId) {
110
110
  return phaseId.startsWith("phase-") && !phaseId.startsWith("feature-");
111
111
  }
112
112
  /** Compute the feature-scoped id for a legacy phase, preserving featureId/number/slug. */
113
- export function migratePhaseId(featureId, number, slug) {
113
+ export function migratePhaseId(_featureId, _number, _slug) {
114
114
  // In UUID world, this is just randomUUID, but we keep signature for compatibility if needed
115
115
  return randomUUID();
116
116
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared list-pagination primitive. Both adapters defined an identical
3
+ * `boundedPage` (page list tools: feature/phase/task/requirement listing,
4
+ * idea listing) with the same 1-25 page-size clamp and the same
5
+ * page-out-of-range clamp. One copy here so the bound is a single decision,
6
+ * not something each adapter must independently re-derive and keep in sync.
7
+ */
8
+ export interface BoundedPage<T> {
9
+ items: T[];
10
+ page: number;
11
+ pageSize: number;
12
+ total: number;
13
+ totalPages: number;
14
+ }
15
+ /** Slice `items` into a bounded page. `pageSize` is clamped to [1, 25];
16
+ * `page` is clamped to [1, totalPages] so an out-of-range request degrades
17
+ * to the nearest valid page instead of an empty result. */
18
+ export declare function boundedPage<T>(items: T[], page: number, pageSize: number): BoundedPage<T>;
19
+ //# sourceMappingURL=pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;4DAE4D;AAC5D,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAWzF"}
@@ -0,0 +1,15 @@
1
+ /** Slice `items` into a bounded page. `pageSize` is clamped to [1, 25];
2
+ * `page` is clamped to [1, totalPages] so an out-of-range request degrades
3
+ * to the nearest valid page instead of an empty result. */
4
+ export function boundedPage(items, page, pageSize) {
5
+ const size = Math.min(25, Math.max(1, Math.trunc(pageSize)));
6
+ const totalPages = Math.max(1, Math.ceil(items.length / size));
7
+ const currentPage = Math.min(totalPages, Math.max(1, Math.trunc(page)));
8
+ return {
9
+ items: items.slice((currentPage - 1) * size, currentPage * size),
10
+ page: currentPage,
11
+ pageSize: size,
12
+ total: items.length,
13
+ totalPages,
14
+ };
15
+ }
@@ -8,7 +8,7 @@ export function isPlannerDescriptionRef(value) {
8
8
  if (value.includes("\\") || value.includes("\0"))
9
9
  return false;
10
10
  const parts = value.split("/");
11
- if (parts.length < 4)
11
+ if (parts.length < 3)
12
12
  return false;
13
13
  if (parts[0] !== ".planner" || parts[1] !== "docs")
14
14
  return false;