@buildaharness/aielia 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 (127) hide show
  1. package/README.md +832 -0
  2. package/dist/action-approval-service.d.ts +52 -0
  3. package/dist/action-snapshot.d.ts +173 -0
  4. package/dist/action-tools.d.ts +38 -0
  5. package/dist/agent-loop.d.ts +372 -0
  6. package/dist/answer-claim.d.ts +51 -0
  7. package/dist/ask-clarification-service.d.ts +70 -0
  8. package/dist/ask-mode-flag.d.ts +25 -0
  9. package/dist/ask-response-format.d.ts +8 -0
  10. package/dist/assistant-session.d.ts +267 -0
  11. package/dist/assistant-source.d.ts +14 -0
  12. package/dist/assistant-types.d.ts +158 -0
  13. package/dist/assistant.d.ts +419 -0
  14. package/dist/batch-list-detector.d.ts +15 -0
  15. package/dist/batch-research-fixtures.d.ts +50 -0
  16. package/dist/benchmark-injected-failure.d.ts +27 -0
  17. package/dist/claude-cli-llm-client.d.ts +139 -0
  18. package/dist/claude-cli-prompt.d.ts +86 -0
  19. package/dist/cli-config.d.ts +27 -0
  20. package/dist/cli-session-D0uLdT2A.js +7863 -0
  21. package/dist/cli-session-D0uLdT2A.js.map +1 -0
  22. package/dist/cli-session.d.ts +63 -0
  23. package/dist/cli.d.ts +65 -0
  24. package/dist/cli.js +1693 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/config.d.ts +175 -0
  27. package/dist/contradiction-checker.d.ts +27 -0
  28. package/dist/debug-log.d.ts +16 -0
  29. package/dist/decomposition-classifier.d.ts +38 -0
  30. package/dist/doctor-checks.d.ts +8 -0
  31. package/dist/email-smtp.d.ts +19 -0
  32. package/dist/email.d.ts +60 -0
  33. package/dist/error-classifier.d.ts +13 -0
  34. package/dist/execution-mode.d.ts +85 -0
  35. package/dist/fact-extraction.d.ts +143 -0
  36. package/dist/failure-mode-matcher.d.ts +32 -0
  37. package/dist/file-tools-mcp-server.mjs +1027 -0
  38. package/dist/file-tools.d.ts +179 -0
  39. package/dist/first-run.d.ts +22 -0
  40. package/dist/harness-bridge.d.ts +103 -0
  41. package/dist/index.d.ts +58 -0
  42. package/dist/index.js +212 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/ink-test-render.d.ts +24 -0
  45. package/dist/lexical/patterns/batch-list-markers.json +8 -0
  46. package/dist/lexical/patterns/batch-list-markers.json.d.ts +11 -0
  47. package/dist/lexical/patterns/coding-fact-markers.json +8 -0
  48. package/dist/lexical/patterns/coding-fact-markers.json.d.ts +11 -0
  49. package/dist/lexical/patterns/enumeration-markers.json +20 -0
  50. package/dist/lexical/patterns/enumeration-markers.json.d.ts +23 -0
  51. package/dist/lexical/patterns/fact-markers.json +16 -0
  52. package/dist/lexical/patterns/fact-markers.json.d.ts +19 -0
  53. package/dist/lexical/patterns/injection-patterns.json +20 -0
  54. package/dist/lexical/patterns/injection-patterns.json.d.ts +23 -0
  55. package/dist/lexical/patterns/plan-mode-markers.json +10 -0
  56. package/dist/lexical/patterns/plan-mode-markers.json.d.ts +13 -0
  57. package/dist/lexical/patterns/risk-patterns.json +122 -0
  58. package/dist/lexical/patterns/risk-patterns.json.d.ts +125 -0
  59. package/dist/lexical/patterns/task-cancel-markers.json +22 -0
  60. package/dist/lexical/patterns/task-cancel-markers.json.d.ts +25 -0
  61. package/dist/lexical/patterns/template-keywords.json +20 -0
  62. package/dist/lexical/patterns/template-keywords.json.d.ts +23 -0
  63. package/dist/lexical/patterns/tool-yield-markers.json +22 -0
  64. package/dist/lexical/patterns/tool-yield-markers.json.d.ts +25 -0
  65. package/dist/lexical/patterns.d.ts +64 -0
  66. package/dist/memory-service.d.ts +219 -0
  67. package/dist/model-pricing.d.ts +19 -0
  68. package/dist/network-containment.d.ts +21 -0
  69. package/dist/node-config-store.d.ts +29 -0
  70. package/dist/node-display-names.d.ts +26 -0
  71. package/dist/node-fs-backend.d.ts +8 -0
  72. package/dist/non-interactive-mode.d.ts +22 -0
  73. package/dist/one-loop-flag.d.ts +38 -0
  74. package/dist/plan-approval-service.d.ts +62 -0
  75. package/dist/plan-builder.d.ts +32 -0
  76. package/dist/plan-drafting-service.d.ts +87 -0
  77. package/dist/plan-drafting.d.ts +58 -0
  78. package/dist/plan-mode-flag.d.ts +34 -0
  79. package/dist/plan-service.d.ts +61 -0
  80. package/dist/plan-sketch-service.d.ts +24 -0
  81. package/dist/plan-sketch.d.ts +20 -0
  82. package/dist/plan-store.d.ts +232 -0
  83. package/dist/plan-templates/data/content_creation.json.d.ts +75 -0
  84. package/dist/plan-templates/data/decision_making.json.d.ts +66 -0
  85. package/dist/plan-templates/data/problem_solving.json.d.ts +75 -0
  86. package/dist/plan-templates/data/process_improvement.json.d.ts +57 -0
  87. package/dist/plan-templates/data/project_planning.json.d.ts +66 -0
  88. package/dist/plan-templates/data/research_analysis.json.d.ts +66 -0
  89. package/dist/plan-templates/data/trip_planning.json.d.ts +66 -0
  90. package/dist/plan-templates/index.d.ts +33 -0
  91. package/dist/plan-verification.d.ts +31 -0
  92. package/dist/reminder-tools.d.ts +13 -0
  93. package/dist/response-service.d.ts +73 -0
  94. package/dist/review-checker.d.ts +22 -0
  95. package/dist/risk-classifier.d.ts +7 -0
  96. package/dist/scripted-llm-client.d.ts +37 -0
  97. package/dist/semantic-criterion-coverage.d.ts +30 -0
  98. package/dist/shell-executor.d.ts +14 -0
  99. package/dist/shell-tools.d.ts +84 -0
  100. package/dist/spend-cap.d.ts +41 -0
  101. package/dist/spike.d.ts +1 -0
  102. package/dist/spike.js +18 -0
  103. package/dist/spike.js.map +1 -0
  104. package/dist/supervisor-decider.d.ts +3 -0
  105. package/dist/system-prompt.d.ts +8 -0
  106. package/dist/task-mapping.d.ts +42 -0
  107. package/dist/tool-control-plane.d.ts +67 -0
  108. package/dist/tool-policy.d.ts +45 -0
  109. package/dist/tool-step.d.ts +23 -0
  110. package/dist/tool-yield-classifier.d.ts +7 -0
  111. package/dist/trace-events.d.ts +126 -0
  112. package/dist/transcript-compaction.d.ts +13 -0
  113. package/dist/trust-tagging.d.ts +38 -0
  114. package/dist/tui-app-DxLPSGh2.js +481 -0
  115. package/dist/tui-app-DxLPSGh2.js.map +1 -0
  116. package/dist/tui-app.d.ts +125 -0
  117. package/dist/tui-input.d.ts +51 -0
  118. package/dist/tui-mode-flag.d.ts +36 -0
  119. package/dist/tui-output-capture.d.ts +30 -0
  120. package/dist/tui-spike.d.ts +1 -0
  121. package/dist/turn-intent-classifier.d.ts +96 -0
  122. package/dist/turn-interpreter.d.ts +71 -0
  123. package/dist/turn-policy.d.ts +45 -0
  124. package/dist/web-fetch-core.d.ts +65 -0
  125. package/dist/web-search-provider.d.ts +12 -0
  126. package/dist/web-tools.d.ts +26 -0
  127. package/package.json +69 -0
@@ -0,0 +1,87 @@
1
+ import { ILLMClient, TokenUsage, MemoryAdapter } from '@buildaharness/runtime';
2
+ import { AskResponse } from '@buildaharness/harness';
3
+ import { AssistantSession } from './assistant-session.js';
4
+ import { PlanService } from './plan-service.js';
5
+ import { PlanApprovalService } from './plan-approval-service.js';
6
+ import { AssistantTurnResult } from './assistant-types.js';
7
+ import { TraceEvent } from './trace-events.js';
8
+ import { AgentLoop } from './agent-loop.js';
9
+ /**
10
+ * `{ fallThrough: true }` only ever comes from a *fresh, auto-triggered* entry (`seed` set —
11
+ * assistant.ts's P3 auto-trigger, never PersonalAssistant.enterPlanMode's manual/test entry point)
12
+ * whose very first drafting call failed (malformed/insufficient LLM output, or a thrown error —
13
+ * draftPlanRevision's own try/catch already normalizes both to `null`) — this phase's Validation
14
+ * requires falling back to *not entering plan mode at all* rather than staging a broken draft or
15
+ * getting the user stuck in a "couldn't update the draft" loop they never asked to enter (same
16
+ * "null means fall through" convention `buildPlanFromTemplate` already used). Mirrors
17
+ * `PlanApprovalOutcome`'s exact shape/discipline. A manual `enterPlanMode` entry with no `seed`
18
+ * keeps today's pre-P3 behavior on a failed first draft: stay in drafting, ask the user to
19
+ * rephrase — that path is an explicit, already-active choice to draft, not a guess the system
20
+ * made on the user's behalf.
21
+ */
22
+ export type PlanDraftOutcome = {
23
+ fallThrough: true;
24
+ } | AssistantTurnResult;
25
+ /**
26
+ * P3 of plans/ask_question_and_plan_mode_plan.html — how `assistant.ts`'s auto-trigger tells
27
+ * `draftTurn` what to seed a *fresh* draft with (ignored on every later revision of an
28
+ * already-drafting plan). `templateName` set means a template matched — the draft starts from
29
+ * that template's own skeleton (today's personalization behavior, reused, not discarded — see
30
+ * `buildPlanFromTemplate`'s doc comment — except the drafting call itself, not a separate
31
+ * personalization call, does the adapting, so it's free to add/remove/split/reorder relative to
32
+ * the skeleton rather than preserving it 1:1). `grounded: true` (only meaningful when
33
+ * `templateName` is null) means ground the from-scratch draft in real repo state via a bounded
34
+ * read_file/list_directory walk before drafting.
35
+ */
36
+ export interface PlanDraftSeed {
37
+ templateName: string | null;
38
+ grounded: boolean;
39
+ }
40
+ /**
41
+ * Owns plan mode's P1 exclusive drafting loop: while `AssistantSession.getPlanModeState(sessionId)`
42
+ * is `active`, `assistant.ts`'s runTurn routes every message here instead of TurnInterpreter's
43
+ * normal classify/tool-loop pipeline (INV-30 — no tool other than this call itself executes for
44
+ * that session while drafting is active). Two designed exits: an explicit cancel/abort phrase
45
+ * (handled entirely here), and P2's approval response once a draft is staged for
46
+ * `awaiting_approval` (handed off to `PlanApprovalService.stageAndRespond` below the moment a
47
+ * revision's `readyForApproval` is true — resolved via `turn(message, { planApprovalId,
48
+ * planDecision })`, checked earlier in `assistant.ts`'s runTurn than this drafting path at all).
49
+ *
50
+ * The drafting call itself (plan-drafting.ts's draftPlanRevision) has no tool access of its own —
51
+ * P3 grounds a from-scratch draft by running a separate, bounded read_file/list_directory
52
+ * investigation walk first (see the `seed?.grounded` branch in `draftTurn` below) and feeding its
53
+ * findings in as extra context, rather than giving the drafting call itself live tool-calling.
54
+ * P3 also wired the automatic judgment-based trigger this class was previously waiting on:
55
+ * `assistant.ts`'s runTurn now calls `session.enterPlanMode` and routes into `draftTurn` (with a
56
+ * `PlanDraftSeed`) the moment `classifyTurnIntent` reports `matchedPlanTemplate !== null ||
57
+ * needsMultiStepPlan` and no plan is already active — `PersonalAssistant.enterPlanMode` remains
58
+ * available as a direct entry point too (tests, a future `/plan` command), unchanged.
59
+ */
60
+ export declare class PlanDraftingService {
61
+ private readonly planService;
62
+ private readonly session;
63
+ private readonly llmClient;
64
+ private readonly model;
65
+ private readonly planApproval;
66
+ private readonly onTrace;
67
+ private readonly agentLoop?;
68
+ private readonly memory?;
69
+ constructor(planService: PlanService, session: AssistantSession, llmClient: ILLMClient, model: () => string | undefined, planApproval: PlanApprovalService, onTrace: ((event: TraceEvent) => void) | undefined, agentLoop?: Pick<AgentLoop, "runSupervisorInvestigation"> | undefined, memory?: MemoryAdapter | undefined);
70
+ private planAskPendingKey;
71
+ /** Peeked by `assistant.ts`'s runTurn before it decides whether a `pendingClarificationId` belongs to this nested-ask side channel (P8) or AskClarificationService's harness-resume path (Q2) — the two staging stores are otherwise independent, so an opaque ID has no other way to say which one it came from. */
72
+ isPendingAsk(id: string): Promise<boolean>;
73
+ /**
74
+ * Resolves a nested plan-drafting ask by ID (P8) — the drafting counterpart to
75
+ * AskClarificationService.resolvePendingClarification, but folds the answer into the *next
76
+ * drafting revision call* instead of resuming a paused harness run (there is none mid-draft).
77
+ * Fail-closed (Protected Invariants) exactly like Q2: no answer, or one that doesn't validate
78
+ * against exactly the staged questions (INV-28), leaves the turn in `needs_clarification`
79
+ * rather than silently proceeding or discarding the batch.
80
+ */
81
+ resolvePendingAsk(sessionId: string, transcriptKey: string, pendingAskId: string, response: AskResponse | undefined, onUsage: (usage: TokenUsage) => void): Promise<AssistantTurnResult>;
82
+ draftTurn(sessionId: string, transcriptKey: string, userMessage: string, onUsage: (usage: TokenUsage) => void, seed?: PlanDraftSeed): Promise<PlanDraftOutcome>;
83
+ private stillAwaitingApproval;
84
+ /** Stages a single nested question (P8) and returns the `needs_clarification` result — mirrors AskClarificationService.stageAndRespond's "append the user's message, return no reply yet" shape, but into `plan-ask-pending:` instead of `ask-pending:`, and resolved by `resolvePendingAsk` above instead of a harness resume. */
85
+ private stageNestedAsk;
86
+ private cancelDrafting;
87
+ }
@@ -0,0 +1,58 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ import { AskQuestion } from '@buildaharness/harness';
3
+ import { DecomposedTaskSpec } from './decomposition-classifier.js';
4
+ import { PlanTaskRecord } from './plan-store.js';
5
+ /**
6
+ * P1 of plans/ask_question_and_plan_mode_plan.html — the "plan-drafting revision call" P1's
7
+ * routing (assistant.ts's runTurn, PlanDraftingService) sends every message to while
8
+ * `planMode.active`. Deliberately narrower than P3's eventual version: no tools at all (P3 adds
9
+ * read_file/list_directory grounding for a from-scratch draft — see Section &amp; non-goals),
10
+ * only revises the running draft from conversation text. Kept in its own module, same reasoning
11
+ * plan-builder.ts documents for buildPlanFromTemplate: a single-purpose LLM call, unit-testable
12
+ * without the rest of the drafting plumbing.
13
+ */
14
+ export interface PlanDraftTurn {
15
+ /** A short, human-readable summary of the current draft, shown back to the user as the turn's reply. */
16
+ reply: string;
17
+ tasks: DecomposedTaskSpec[];
18
+ successCriteria: string;
19
+ rationale: string;
20
+ /**
21
+ * P2 of plans/ask_question_and_plan_mode_plan.html — true only once the model judges the user
22
+ * has clearly signaled they're satisfied with the current draft and want to proceed, at which
23
+ * point PlanDraftingService hands off to PlanApprovalService instead of returning another plain
24
+ * drafting reply. Defaults false on any malformed/missing value (see parsing below) — a
25
+ * misparsed response must never accidentally fast-track a plan into the approval gate.
26
+ */
27
+ readyForApproval: boolean;
28
+ /**
29
+ * P8 of plans/ask_question_and_plan_mode_plan.html — set only when this revision hit a genuine
30
+ * ambiguity worth pausing on instead of guessing (an ambiguous scope boundary, or a
31
+ * MEDIUM/HIGH-risk branch point with more than one viable approach) — e.g. "which of these two
32
+ * frameworks should the plan target?" A drafting call that sets this always also leaves
33
+ * `readyForApproval` false; `PlanDraftingService.draftTurn` stages it as a nested
34
+ * `needs_clarification` exchange (reusing Q0's `AskQuestion` shape and Q2/Q5/Q6's existing
35
+ * rendering) instead of treating `reply`/`tasks` as an ordinary revision, and the user's answer
36
+ * folds into the *next* drafting call as its `userMessage` rather than resuming a harness run
37
+ * (there is no harness run mid-draft to resume — see plan-drafting-service.ts's doc comment).
38
+ * Absent on every ordinary revision, same "absent when unused" convention the rest of this
39
+ * module follows.
40
+ */
41
+ question?: AskQuestion;
42
+ }
43
+ /**
44
+ * Spends one real LLM call revising the draft given `userMessage` — same "malformed/incomplete
45
+ * JSON is the expected failure mode, not the edge case" fallback buildPlanFromTemplate uses: any
46
+ * parse failure, or a response with zero usable tasks, returns null (caller keeps the draft
47
+ * unchanged and reports a generic "couldn't update the draft" reply rather than staging garbage).
48
+ */
49
+ export declare function draftPlanRevision(llmClient: ILLMClient, userMessage: string, currentTasks: PlanTaskRecord[], currentSuccessCriteria: string, currentRationale: string, model?: string, onUsage?: (usage: TokenUsage) => void,
50
+ /**
51
+ * P3 of plans/ask_question_and_plan_mode_plan.html — grounding text for a from-scratch (no
52
+ * template match) draft, gathered from a bounded read_file/list_directory investigation walk
53
+ * (see AgentLoop.runSupervisorInvestigation, reused rather than building a new search engine —
54
+ * Scope & non-goals) so a code-shaped request's draft can reference real repo state instead of
55
+ * guessing. Absent for every other call (a template-seeded draft, or any revision after the
56
+ * first) — same "absent when unused" convention the rest of this module follows.
57
+ */
58
+ groundingContext?: string): Promise<PlanDraftTurn | null>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Pure logic backing PersonalAssistant's ASSISTANT_PLAN_MODE handling — split out so it's
3
+ * unit-testable in isolation, mirroring one-loop-flag.ts/ask-mode-flag.ts's exact shape
4
+ * (module-level default, an injectable resolver so tests never touch real process.env).
5
+ *
6
+ * P11 of plans/ask_question_and_plan_mode_plan.html — the rollout flag gating P1-P8's whole
7
+ * plan-mode behavior change (exclusive drafting, mandatory approval, the generalized trigger,
8
+ * auto-advance, the markdown/delegate/trust-mode extensions) behind one switch. 'legacy' (the
9
+ * default, for the whole rollout window, per `DEFAULT_PLAN_MODE`): P3's judgment-based
10
+ * auto-trigger (assistant.ts's `runTurn`, the one production call site that ever calls
11
+ * `PersonalAssistant.enterPlanMode`) never fires, so `planMode.active` never becomes true for
12
+ * real user traffic and the entire P1-P8/P10 apparatus downstream of it stays dormant by
13
+ * construction — same "inert until its one entry point is reached" shape P1 itself already
14
+ * documents for the pre-P3 window, and the same discipline the Trajectory Supervisor's flag
15
+ * uses (a single call-site gate rather than scattering flag checks through every downstream
16
+ * consumer). Explicit/manual entry (`enterPlanMode` called directly by a test, or a future
17
+ * `/plan enter`-style CLI verb) is deliberately left ungated by this flag, since it has no
18
+ * production caller today independent of P3 (see `enterPlanMode`'s own doc comment) — there is
19
+ * nothing for 'legacy' to preserve byte-identically there. 'gated' lets P3's auto-trigger run.
20
+ */
21
+ export type PlanRolloutMode = 'gated' | 'legacy';
22
+ export declare const DEFAULT_PLAN_MODE: PlanRolloutMode;
23
+ /**
24
+ * Value-level resolver, shared by every surface: `resolvePlanMode` (CLI, reads
25
+ * `process.env.ASSISTANT_PLAN_MODE`) and chat-ui's `envOverridesFromImportMetaEnv` (browser
26
+ * build, reads Vite's `import.meta.env.VITE_ASSISTANT_PLAN_MODE`). An unset or empty value
27
+ * falls back to the default silently; an unrecognized non-empty value falls back with a
28
+ * startup warning naming `varName` — same typo-tolerance-with-a-warning convention
29
+ * normalizeOneLoopMode/normalizeAskMode use, since a silently-misread flag here is
30
+ * safety-relevant (it decides whether real turns can auto-enter a durable, approval-gated
31
+ * planning loop at all).
32
+ */
33
+ export declare function normalizePlanMode(raw: string | undefined, varName?: string): PlanRolloutMode;
34
+ export declare function resolvePlanMode(env: NodeJS.ProcessEnv): PlanRolloutMode;
@@ -0,0 +1,61 @@
1
+ import { MemoryAdapter } from '@buildaharness/runtime';
2
+ import { TaskStatus } from '@buildaharness/harness';
3
+ import { Plan } from './plan-builder.js';
4
+ import { nextPendingTask, PlanRecord, PlanPosition, TaskCancelMatch, PlanFsPersistence } from './plan-store.js';
5
+ /**
6
+ * Thin constructor-scoped facade over `memory` for durable-plan bookkeeping — plan-store.ts (and
7
+ * plan-builder.ts/plan-templates/, untouched by this split) already hold the real logic, so this
8
+ * module exists only so TurnInterpreter, HarnessBridge (mid-run `onCheckpoint`), and
9
+ * ResponseService (post-run planStatus assembly) share one collaborator instead of each importing
10
+ * plan-store.ts's free functions directly and threading `memory` themselves.
11
+ *
12
+ * `fsPersistence` (plan mode's P5) is the same `{backend, workspaceRoot}` pair
13
+ * AssistantSession.undoWorkspace() resolves for write_file/run_shell_command — `undefined` on a
14
+ * surface with no real filesystem (e.g. a browser tab), in which case every method below behaves
15
+ * exactly as it did before P5, Dexie-only.
16
+ */
17
+ export declare class PlanService {
18
+ private readonly memory;
19
+ private readonly fsPersistence?;
20
+ constructor(memory: MemoryAdapter, fsPersistence?: PlanFsPersistence | undefined);
21
+ loadActivePlan(sessionId: string): Promise<PlanRecord | null>;
22
+ /** Unlike loadActivePlan, returns the stored record regardless of `mode` — used by plan mode's P1 drafting loop, which needs to resume a `mode: 'drafting'` record loadActivePlan would never return. */
23
+ loadPlanRecord(sessionId: string): Promise<PlanRecord | null>;
24
+ createDraftPlanRecord(templateName: string | null): PlanRecord;
25
+ matchTaskCancelAttempt(message: string, plan: PlanRecord): TaskCancelMatch | null;
26
+ cancelPlanTask(sessionId: string, plan: PlanRecord, taskId: string): Promise<PlanRecord>;
27
+ editPlanTask(sessionId: string, plan: PlanRecord, taskId: string, newDescription: string): Promise<PlanRecord>;
28
+ abandonPlan(sessionId: string, plan: PlanRecord): Promise<void>;
29
+ stagePlanForApproval(sessionId: string, plan: PlanRecord): Promise<PlanRecord>;
30
+ activatePlanRecord(sessionId: string, plan: PlanRecord): Promise<PlanRecord>;
31
+ savePlan(sessionId: string, plan: PlanRecord): Promise<void>;
32
+ createPlanRecord(plan: Plan): PlanRecord;
33
+ updatePlanFromRun(plan: PlanRecord, taskGraphTasks: {
34
+ id: string;
35
+ status: TaskStatus;
36
+ }[]): PlanRecord;
37
+ planCompletionPct(plan: PlanRecord): number;
38
+ computePlanPosition(plan: PlanRecord, taskGraphTasks: {
39
+ id: string;
40
+ status: TaskStatus;
41
+ }[]): PlanPosition | null;
42
+ nextPendingTask(plan: PlanRecord): ReturnType<typeof nextPendingTask>;
43
+ /** Persists `plan`'s current task statuses and returns the AssistantTurnResult.planStatus shape both the paused and success branches of ResponseService build identically. */
44
+ saveAndSummarize(sessionId: string, plan: PlanRecord, taskGraphTasks: {
45
+ id: string;
46
+ status: TaskStatus;
47
+ }[]): Promise<{
48
+ plan: PlanRecord;
49
+ completionPct: number;
50
+ planStatus: {
51
+ templateName: string | null;
52
+ successCriteria: string;
53
+ completionPct: number;
54
+ tasks: {
55
+ id: string;
56
+ description: string;
57
+ status: string;
58
+ }[];
59
+ };
60
+ }>;
61
+ }
@@ -0,0 +1,24 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ import { AssistantTurnResult } from './assistant-types.js';
3
+ import { TraceEvent } from './trace-events.js';
4
+ import { AgentLoop } from './agent-loop.js';
5
+ /**
6
+ * P9 of plans/ask_question_and_plan_mode_plan.html — the lightweight plan-sketch delegate: one
7
+ * bounded LLM call (grounded, best-effort, via the same read_file/list_directory investigation
8
+ * walk P3's from-scratch drafting reuses — AgentLoop.runSupervisorInvestigation, not a new search
9
+ * engine) that returns a plain proposed task list in the chat reply itself. Deliberately NOT
10
+ * PlanDraftingService: it never touches PlanService/PlanRecord, never calls
11
+ * AssistantSession.enterPlanMode, and stages nothing for PlanApprovalService to see (INV-35) —
12
+ * advice for one turn, not a commitment to execute. Kept in its own module/class, same
13
+ * single-purpose-collaborator discipline every other plan-mode service in this package follows,
14
+ * so a caller that only wants the free-form sketch never pulls in the stateful drafting/approval
15
+ * machinery at all.
16
+ */
17
+ export declare class PlanSketchService {
18
+ private readonly llmClient;
19
+ private readonly model;
20
+ private readonly onTrace;
21
+ private readonly agentLoop?;
22
+ constructor(llmClient: ILLMClient, model: () => string | undefined, onTrace: ((event: TraceEvent) => void) | undefined, agentLoop?: Pick<AgentLoop, "runSupervisorInvestigation"> | undefined);
23
+ sketch(request: string, onUsage: (usage: TokenUsage) => void): Promise<AssistantTurnResult>;
24
+ }
@@ -0,0 +1,20 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ /**
3
+ * P9 of plans/ask_question_and_plan_mode_plan.html — the lightweight plan-sketch delegate's one
4
+ * bounded LLM call. Deliberately the cheapest possible shape: a single plain-text reply the
5
+ * caller shows directly in the chat, not a structured task list staged anywhere — there is no
6
+ * PlanRecord, no PlanTaskRecord, nothing for P2's approval gate to see (INV-35). Distinct from
7
+ * plan-drafting.ts's draftPlanRevision, which is the first call in an iterative, stateful,
8
+ * eventually-approved drafting loop; this is one-shot advice, thrown away the moment the reply
9
+ * is shown — there is no "revise the sketch" follow-up call, no running draft to revise.
10
+ */
11
+ export interface PlanSketchResult {
12
+ reply: string;
13
+ }
14
+ /**
15
+ * Spends one real LLM call sketching a plan for `request` — same "malformed/incomplete output is
16
+ * the expected failure mode, not the edge case" fallback plan-drafting.ts's draftPlanRevision
17
+ * uses: any thrown error, or an empty reply, returns null so the caller can show a generic
18
+ * "couldn't sketch that" message instead of an empty bubble.
19
+ */
20
+ export declare function sketchPlan(llmClient: ILLMClient, request: string, groundingContext: string | undefined, model: string | undefined, onUsage: ((usage: TokenUsage) => void) | undefined): Promise<PlanSketchResult | null>;
@@ -0,0 +1,232 @@
1
+ import { FsBackend, MemoryAdapter } from '@buildaharness/runtime';
2
+ import { TaskStatus } from '@buildaharness/harness';
3
+ import { Plan } from './plan-builder.js';
4
+ export interface PlanTaskRecord {
5
+ id: string;
6
+ description: string;
7
+ depends_on: string[];
8
+ status: TaskStatus;
9
+ /**
10
+ * Per-task risk, attached at plan-creation time (see plan-builder.ts's buildPlanFromTemplate)
11
+ * instead of re-derived from the description text on every resume. Optional only for backward
12
+ * compatibility with a plan persisted before this field existed — assistant.ts's
13
+ * toHarnessTasks falls back to the lexical planTaskRiskLevel/classifyRisk for those.
14
+ */
15
+ riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH';
16
+ /**
17
+ * True once the user explicitly cancelled this specific task (see matchTaskCancelAttempt/
18
+ * cancelPlanTask) — distinct from `status`, which gets set to 'COMPLETE' alongside this so the
19
+ * harness's task-graph selection and dependent-unblocking treat it as resolved the same way a
20
+ * genuinely finished task would be (TaskStatus, from @buildaharness/harness, has no CANCELLED
21
+ * value of its own — extending that union is a cross-package change out of scope here).
22
+ * formatPlanProgress/planCompletionPct read this flag to show/count it accurately instead of
23
+ * claiming the user's own work actually got done.
24
+ */
25
+ cancelled?: boolean;
26
+ }
27
+ /**
28
+ * Superset of the old 3-value `status`: 'drafting' and 'awaiting_approval' are new (plan mode's
29
+ * P1/P2 — not wired to any producer yet, this phase is schema-only), 'active'/'done'/'abandoned'
30
+ * are the pre-existing values, unchanged in meaning.
31
+ */
32
+ export type PlanMode = 'drafting' | 'awaiting_approval' | 'active' | 'done' | 'abandoned';
33
+ export interface PlanRecord {
34
+ /** null for a fully custom-drafted plan (P3) that isn't seeded from one of the named templates. */
35
+ templateName: string | null;
36
+ successCriteria: string;
37
+ /** Why this approach, not just the success criteria — see Plan.rationale. */
38
+ rationale: string;
39
+ tasks: PlanTaskRecord[];
40
+ mode: PlanMode;
41
+ /** Populated by plan mode's P6 self-verification pass. */
42
+ reviewNotes?: string[];
43
+ verifiedAt?: string;
44
+ /** Whether the harness should auto-advance through this plan's tasks without a per-step pacing pause (plan mode's P4). */
45
+ executingOnPlan: boolean;
46
+ /**
47
+ * Plan mode's P10 "trust this approved plan" opt-in — absent/false (the default, "absent when
48
+ * unused" per this codebase's convention for additive fields) means every write/shell/email
49
+ * action this plan's execution proposes stays individually gated exactly as before this phase.
50
+ * `true` only once the user chose `'approve_trusted'` at the P2 approval screen (never the
51
+ * default choice there — see PlanApprovalService) — while true, an action proposed during
52
+ * execution of whichever task is this plan's currently RUNNING one is auto-applied instead of
53
+ * staged (INV-36; see assistant.ts's buildToolLoopPauseResult). Per-plan, not global or
54
+ * per-session: a fresh plan, or this same plan reloaded after `abandonPlan`, starts back at
55
+ * false.
56
+ */
57
+ trustApprovedSteps?: boolean;
58
+ /**
59
+ * Set only while `mode === 'awaiting_approval'` (plan mode's P2) — correlates a
60
+ * `turn(message, { planApprovalId, planDecision })` resolution to exactly this staged snapshot,
61
+ * same "resolved by ID, never re-derived" discipline pendingActionId/pendingClarificationId
62
+ * already follow. Cleared (undefined) once the plan is activated or the draft is abandoned.
63
+ */
64
+ planApprovalId?: string;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ }
68
+ /** Pre-P0 persisted shape: `status` instead of `mode`, no `rationale`/`executingOnPlan`. */
69
+ interface LegacyPlanRecordShape {
70
+ templateName: string;
71
+ successCriteria: string;
72
+ tasks: PlanTaskRecord[];
73
+ status: 'active' | 'done' | 'abandoned';
74
+ createdAt: string;
75
+ updatedAt: string;
76
+ }
77
+ /**
78
+ * Maps a persisted record — old 3-value `status` shape or the current 5-value `mode` shape — to
79
+ * the current PlanRecord shape, same "default pre-existing records without demoting a real value"
80
+ * convention fact-extraction.ts's migrateFact already uses elsewhere in this package. A migrated
81
+ * `active` plan gets `executingOnPlan: true` so an in-flight plan from before this change keeps
82
+ * progressing rather than silently stalling.
83
+ */
84
+ export declare function migratePlanRecord(record: PlanRecord | LegacyPlanRecordShape): PlanRecord;
85
+ /**
86
+ * Plan mode's P5 file-backed persistence: when a real filesystem is available (CLI/desktop —
87
+ * the same `{backend, workspaceRoot}` pair AssistantSession.undoWorkspace() already resolves for
88
+ * write_file/run_shell_command), every plan write is mirrored to
89
+ * `<workspaceRoot>/.buildaharness/plans/<sessionId>.plan.json` (plus a generated `.plan.md`
90
+ * view), and every load prefers that file over the Dexie/IndexedDB State-tier record so a
91
+ * hand-edit to the JSON between turns is picked up. On a surface with no filesystem (a browser
92
+ * tab), this is `undefined` throughout and Dexie alone remains the source of truth, unchanged
93
+ * from before P5.
94
+ */
95
+ export interface PlanFsPersistence {
96
+ backend: FsBackend;
97
+ workspaceRoot: string;
98
+ }
99
+ /**
100
+ * Reads `sessionId`'s stored plan record regardless of `mode` — unlike `loadActivePlan`, which
101
+ * filters to `mode === 'active'` only. Plan mode's P1 drafting path needs to resume a
102
+ * `mode: 'drafting'` record across turns (and P2 needs `awaiting_approval` the same way), neither
103
+ * of which `loadActivePlan` would ever return.
104
+ *
105
+ * When `fsPersistence` is configured and its plan JSON file exists, that file wins over the
106
+ * Dexie/State-tier record (P5 — the file is the editable-and-reloadable artifact, so a hand-edit
107
+ * between turns takes effect on the very next load) and is written back into `memory` to
108
+ * reconcile the two (INV-33: after any interrupted dual write, the next load's result is
109
+ * self-consistent and Dexie catches back up to it, rather than the two staying silently split).
110
+ */
111
+ export declare function loadPlanRecord(memory: MemoryAdapter, sessionId: string, fsPersistence?: PlanFsPersistence): Promise<PlanRecord | null>;
112
+ /** Returns null when no plan exists for this session, or the stored plan is already done/abandoned — a finished plan never auto-resumes. */
113
+ export declare function loadActivePlan(memory: MemoryAdapter, sessionId: string, fsPersistence?: PlanFsPersistence): Promise<PlanRecord | null>;
114
+ /**
115
+ * Builds an immediately-`active` PlanRecord with no drafting/approval step at all. Before P3 of
116
+ * plans/ask_question_and_plan_mode_plan.html, TurnInterpreter.resolveTasks called this the instant
117
+ * classifyTurnIntent matched a template; P3 retired that call site — a template match (or the
118
+ * newer general needsMultiStepPlan judgment) now always routes through plan mode's exclusive
119
+ * drafting+approval loop instead (see assistant.ts's auto-trigger and
120
+ * PlanDraftingService.draftTurn's template-seeding). This function itself is unchanged and still
121
+ * used directly by tests/callers that want to seed an already-active plan without going through
122
+ * drafting. The ONLY other place `mode: 'active'` is ever set is `activatePlanRecord` below,
123
+ * reached solely via a drafted plan's explicit approve/approve-with-edits (INV-31) — this function
124
+ * remains the one intentional exception to "exactly one place", now purely a low-level primitive
125
+ * rather than something classification wires up automatically.
126
+ */
127
+ export declare function createPlanRecord(plan: Plan): PlanRecord;
128
+ /**
129
+ * The funnel every plan mutation in this module writes through. When `fsPersistence` is
130
+ * configured, the fs file is written first and Dexie second (P5, INV-33): if the process dies
131
+ * between the two, the fs file — which `loadPlanRecord` always prefers when present — already
132
+ * reflects either the old state (rename never happened) or the new one (rename completed),
133
+ * never a half-written file, so the next load resolves to one consistent version of both instead
134
+ * of a silently split pair.
135
+ */
136
+ export declare function savePlan(memory: MemoryAdapter, sessionId: string, plan: PlanRecord, fsPersistence?: PlanFsPersistence): Promise<void>;
137
+ /**
138
+ * A fresh, empty `mode: 'drafting'` record — the seed for plan mode's P1 drafting loop, started
139
+ * either from nothing (a from-scratch draft, P3) or already carrying a `templateName` seed.
140
+ * Unlike `createPlanRecord`, `executingOnPlan` starts false (P4's auto-advance only ever applies
141
+ * once P2 approval flips the record to `active`) and `mode` starts `'drafting'`, not `'active'`.
142
+ */
143
+ export declare function createDraftPlanRecord(templateName: string | null): PlanRecord;
144
+ export declare function abandonPlan(memory: MemoryAdapter, sessionId: string, plan: PlanRecord, fsPersistence?: PlanFsPersistence): Promise<void>;
145
+ /**
146
+ * Stages a drafted plan for plan mode's P2 mandatory whole-plan approval gate: `mode` moves to
147
+ * `'awaiting_approval'` and a fresh `planApprovalId` is minted so the eventual
148
+ * `turn(message, { planApprovalId, planDecision })` resolves exactly this snapshot, never a
149
+ * second, re-derived one (see PlanRecord.planApprovalId's doc comment).
150
+ */
151
+ export declare function stagePlanForApproval(memory: MemoryAdapter, sessionId: string, plan: PlanRecord, fsPersistence?: PlanFsPersistence): Promise<PlanRecord>;
152
+ /**
153
+ * The sole place a *drafted* plan reaches `mode: 'active'` (INV-31) — called only from
154
+ * PlanApprovalService's approve/approve-with-edits branch, once any edits have already landed on
155
+ * `plan`. `createPlanRecord` above sets `mode: 'active'` too, but that's the pre-existing,
156
+ * unrelated template-instant-match path — not a second way for a *drafted* plan to skip approval.
157
+ * `planApprovalId` is cleared since the staged snapshot it correlated to no longer exists once
158
+ * activated.
159
+ */
160
+ export declare function activatePlanRecord(memory: MemoryAdapter, sessionId: string, plan: PlanRecord, fsPersistence?: PlanFsPersistence): Promise<PlanRecord>;
161
+ export interface TaskCancelMatch {
162
+ taskId: string;
163
+ taskDescription: string;
164
+ }
165
+ /**
166
+ * Detects a request to cancel/skip ONE task within an active plan — distinct from
167
+ * classifyTurnIntent's isAbandonRequest judgment, which is about ending the WHOLE plan (see
168
+ * conv59/conv70's h9 finding: "cancel the daily-budget task" isn't asking to abandon a trip-planning
169
+ * plan entirely, just to drop one of its steps, and there was no feature to route that to at
170
+ * all). Matches a cancel-shaped verb (cancel/skip/drop/remove), an explicit reference to the plan
171
+ * or one of its tasks/steps (TASK_REFERENCE_MARKER), together with a distinctive word (4+ letters,
172
+ * not a common stopword) shared with one of the plan's own not-yet-complete task
173
+ * descriptions/ids — deliberately conservative: a bare "cancel" with no recognizable task
174
+ * reference (e.g. "cancel my gym membership", unrelated to anything in this plan, or "cancel my
175
+ * travel insurance policy with my current provider", a genuine external request that merely
176
+ * happens to share a word with a task description) returns null and falls through to the ordinary
177
+ * message-level risk gate, same as today. Returns the first matching task in plan order, or null.
178
+ */
179
+ export declare function matchTaskCancelAttempt(message: string, plan: PlanRecord): TaskCancelMatch | null;
180
+ /**
181
+ * Cancels one task within an active plan — internal bookkeeping only (see PlanTaskRecord.cancelled
182
+ * for why status becomes 'COMPLETE' alongside the cancelled flag). Unlike abandonPlan, the plan
183
+ * itself stays 'active' so the remaining tasks continue normally.
184
+ */
185
+ export declare function cancelPlanTask(memory: MemoryAdapter, sessionId: string, plan: PlanRecord, taskId: string, fsPersistence?: PlanFsPersistence): Promise<PlanRecord>;
186
+ /**
187
+ * Changes one task's description without marking it complete — a lighter sibling of
188
+ * cancelPlanTask, for plan mode's P2 approve-with-edits path (a user can want a task reworded
189
+ * without dropping it entirely, which cancelPlanTask's "mark COMPLETE + cancelled" would do).
190
+ */
191
+ export declare function editPlanTask(memory: MemoryAdapter, sessionId: string, plan: PlanRecord, taskId: string, newDescription: string, fsPersistence?: PlanFsPersistence): Promise<PlanRecord>;
192
+ /**
193
+ * Maps the harness's resulting task statuses back onto the plan's own task list —
194
+ * mirrors what adapter/harness/plan_store.py's task_graph_to_plan does for the
195
+ * Python planner, just keyed to a chat session instead of a snapshot file. Marks
196
+ * the plan 'done' once every task is COMPLETE, so loadActivePlan stops resuming it.
197
+ * Also flips `executingOnPlan` back to false in that same allComplete branch (plan
198
+ * mode's P4, INV-32) — one of exactly two places that happens, the other being
199
+ * `abandonPlan`'s explicit user-abort path; every other event (a task failing, a
200
+ * needs_approval/needs_clarification interrupt, a harness error) leaves it untouched.
201
+ */
202
+ export declare function updatePlanFromRun(plan: PlanRecord, taskGraphTasks: {
203
+ id: string;
204
+ status: TaskStatus;
205
+ }[]): PlanRecord;
206
+ /** Cancelled tasks are excluded from both sides of the ratio — they're neither remaining work nor
207
+ * something the user actually did, so counting them as "complete" would overstate real progress.
208
+ * A plan with no tasks at all is 0% (unchanged); a plan whose remaining tasks were all cancelled
209
+ * is 100% (nothing left to do) — two different situations, not the same "empty" case. */
210
+ export declare function planCompletionPct(plan: PlanRecord): number;
211
+ /**
212
+ * Live, mid-run position within a durable plan — see the harness layer activation plan's
213
+ * Phase 3.2. `stepIndex` is 1-based: the task currently RUNNING, or (once nothing is running)
214
+ * the last COMPLETE task, or the first task before anything has started.
215
+ */
216
+ export interface PlanPosition {
217
+ templateName: string | null;
218
+ stepIndex: number;
219
+ stepCount: number;
220
+ currentTaskDescription: string;
221
+ completionPct: number;
222
+ }
223
+ /** Computes live plan position from a live (possibly mid-run) task-status list — `plan.tasks`' own order is authoritative; `taskGraphTasks` only supplies current status per id. */
224
+ export declare function computePlanPosition(plan: PlanRecord, taskGraphTasks: {
225
+ id: string;
226
+ status: TaskStatus;
227
+ }[]): PlanPosition | null;
228
+ /** The next not-yet-COMPLETE task in plan order — used to phrase a pause/resume prompt ("Ready to continue with: <description>?"). Returns null once every task is COMPLETE. */
229
+ export declare function nextPendingTask(plan: PlanRecord): PlanTaskRecord | null;
230
+ /** Human-readable plan status — mirrors agents/planner/utils.py's format_plan_progress, for callers (the CLI) that want text instead of the structured planStatus field. */
231
+ export declare function formatPlanProgress(plan: PlanRecord): string;
232
+ export {};
@@ -0,0 +1,75 @@
1
+ declare const _default: {
2
+ "name": "content_creation",
3
+ "version": "1.0.0",
4
+ "success_criteria": "A polished, peer-reviewed piece of content is finalised and ready for publication or delivery.",
5
+ "tags": ["content", "writing", "communication"],
6
+ "tasks": [
7
+ {
8
+ "id": "brief",
9
+ "title": "Define brief",
10
+ "description": "Clarify the content goal, target audience, format, tone, length, and success metrics.",
11
+ "depends_on": [],
12
+ "risk_level": "LOW",
13
+ "abstraction_level": 0,
14
+ "parallel_write_domains": []
15
+ },
16
+ {
17
+ "id": "outline",
18
+ "title": "Create outline",
19
+ "description": "Structure the content: define sections, key messages per section, and the logical flow.",
20
+ "depends_on": ["brief"],
21
+ "risk_level": "LOW",
22
+ "abstraction_level": 1,
23
+ "parallel_write_domains": []
24
+ },
25
+ {
26
+ "id": "draft",
27
+ "title": "Write draft",
28
+ "description": "Produce the first full draft following the outline. Focus on completeness over perfection.",
29
+ "depends_on": ["outline"],
30
+ "risk_level": "MEDIUM",
31
+ "abstraction_level": 1,
32
+ "parallel_write_domains": ["draft"]
33
+ },
34
+ {
35
+ "id": "gather_references",
36
+ "title": "Gather references",
37
+ "description": "Collect supporting sources, data, quotes, and visuals that will strengthen the content.",
38
+ "depends_on": ["outline"],
39
+ "risk_level": "LOW",
40
+ "abstraction_level": 1,
41
+ "parallel_write_domains": ["references"]
42
+ },
43
+ {
44
+ "id": "peer_review",
45
+ "title": "Peer review",
46
+ "description": "Have at least one reviewer assess the draft for accuracy, clarity, tone, and completeness.",
47
+ "depends_on": ["draft", "gather_references"],
48
+ "risk_level": "LOW",
49
+ "abstraction_level": 0,
50
+ "parallel_write_domains": []
51
+ },
52
+ {
53
+ "id": "revise",
54
+ "title": "Revise",
55
+ "description": "Incorporate review feedback. Address all critical comments before finalisation.",
56
+ "depends_on": ["peer_review"],
57
+ "risk_level": "LOW",
58
+ "abstraction_level": 1,
59
+ "parallel_write_domains": []
60
+ },
61
+ {
62
+ "id": "finalise",
63
+ "title": "Finalise",
64
+ "description": "Apply final formatting, proofread, and approve for publication or delivery.",
65
+ "depends_on": ["revise"],
66
+ "risk_level": "LOW",
67
+ "abstraction_level": 0,
68
+ "parallel_write_domains": []
69
+ }
70
+ ],
71
+ "metadata": { "author": "buildaharness", "created": "2026-06-22" }
72
+ }
73
+ ;
74
+
75
+ export default _default;