@buildaharness/aielia 0.3.1 → 0.3.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.
Files changed (71) hide show
  1. package/README.md +31 -8
  2. package/dist/agent-loop.d.ts +8 -8
  3. package/dist/ask-clarification-service.d.ts +1 -1
  4. package/dist/ask-mode-flag.d.ts +1 -1
  5. package/dist/assistant-session.d.ts +1 -1
  6. package/dist/assistant-types.d.ts +3 -3
  7. package/dist/assistant.d.ts +12 -12
  8. package/dist/batch-research-fixtures.d.ts +1 -1
  9. package/dist/benchmark-injected-failure.d.ts +1 -1
  10. package/dist/bin-Btzhiyzi.js +2108 -0
  11. package/dist/bin-Btzhiyzi.js.map +1 -0
  12. package/dist/bin-sea-bootstrap.d.ts +1 -0
  13. package/dist/bin.d.ts +1 -0
  14. package/dist/claude-cli-llm-client.d.ts +2 -2
  15. package/dist/cli-args.d.ts +20 -0
  16. package/dist/cli-icons.d.ts +28 -0
  17. package/dist/{cli-session-D0uLdT2A.js → cli-session-DnSc6Hhx.js} +607 -127
  18. package/dist/cli-session-DnSc6Hhx.js.map +1 -0
  19. package/dist/cli.d.ts +31 -1
  20. package/dist/cli.js +1 -1691
  21. package/dist/cli.js.map +1 -1
  22. package/dist/config.d.ts +14 -6
  23. package/dist/contradiction-checker.d.ts +1 -1
  24. package/dist/diff-format.d.ts +16 -0
  25. package/dist/execution-mode.d.ts +1 -1
  26. package/dist/fact-extraction.d.ts +4 -4
  27. package/dist/failure-mode-matcher.d.ts +1 -1
  28. package/dist/file-tools-mcp-server.mjs +2 -2
  29. package/dist/file-tools.d.ts +10 -2
  30. package/dist/harness-bridge.d.ts +21 -4
  31. package/dist/index.js +68 -68
  32. package/dist/index.js.map +1 -1
  33. package/dist/ink-select-prompt.d.ts +21 -0
  34. package/dist/markdown-line.d.ts +42 -0
  35. package/dist/mcp-server-asset.d.ts +24 -0
  36. package/dist/memory-service.d.ts +12 -2
  37. package/dist/network-containment.d.ts +1 -1
  38. package/dist/node-display-names.d.ts +1 -1
  39. package/dist/one-loop-flag.d.ts +1 -1
  40. package/dist/plan-drafting-service.d.ts +1 -1
  41. package/dist/plan-drafting.d.ts +4 -4
  42. package/dist/plan-mode-flag.d.ts +1 -1
  43. package/dist/plan-sketch-service.d.ts +1 -1
  44. package/dist/plan-sketch.d.ts +1 -1
  45. package/dist/plan-store.d.ts +2 -2
  46. package/dist/plan-templates/index.d.ts +1 -1
  47. package/dist/plan-verification.d.ts +1 -1
  48. package/dist/review-checker.d.ts +10 -0
  49. package/dist/scripted-llm-client.d.ts +1 -1
  50. package/dist/sea-cache.d.ts +9 -0
  51. package/dist/self-update.d.ts +101 -0
  52. package/dist/semantic-criterion-coverage.d.ts +10 -0
  53. package/dist/shell-tools.d.ts +1 -1
  54. package/dist/spike.js.map +1 -1
  55. package/dist/tool-control-plane.d.ts +1 -1
  56. package/dist/tool-policy.d.ts +1 -1
  57. package/dist/tool-step.d.ts +10 -0
  58. package/dist/trace-events.d.ts +5 -5
  59. package/dist/trust-tagging.d.ts +1 -1
  60. package/dist/{tui-app-DxLPSGh2.js → tui-app-CFfOP2DC.js} +176 -20
  61. package/dist/tui-app-CFfOP2DC.js.map +1 -0
  62. package/dist/tui-app.d.ts +34 -9
  63. package/dist/tui-mode-flag.d.ts +1 -1
  64. package/dist/turn-intent-classifier.d.ts +5 -5
  65. package/dist/turn-interpreter.d.ts +12 -0
  66. package/dist/turn-policy.d.ts +1 -1
  67. package/dist/update-check-flag.d.ts +11 -0
  68. package/dist/version.d.ts +6 -0
  69. package/package.json +7 -3
  70. package/dist/cli-session-D0uLdT2A.js.map +0 -1
  71. package/dist/tui-app-DxLPSGh2.js.map +0 -1
@@ -3,7 +3,7 @@
3
3
  * MCP stdio server exposing read_file/list_directory/write_file/fetch_url/
4
4
  * create_reminder/list_reminders/run_shell_command to the Claude CLI — the same
5
5
  * MCP mechanism already proven for the coaching/planner agents (see
6
- * adapter/agents/coaching/mcp_server.py, adapter/agents/planner/mcp_server.py: both
6
+ * the coaching and planner agents' MCP servers: both
7
7
  * FastMCP stdio servers started via --mcp-config, alongside --dangerously-skip-permissions
8
8
  * so a headless `claude -p` call can actually invoke them without an interactive
9
9
  * permission prompt — this server is started the same way, for the same reason).
@@ -468,7 +468,7 @@ export async function fetchUrlSafely(url) {
468
468
  // DuckDuckGo's HTML endpoint resets the TLS connection outright for any
469
469
  // non-browser client, a block beneath the HTTP layer no request header or retry
470
470
  // can work around (confirmed live during
471
- // plans/browser_web_tools_via_proxy_plan.html's W8).
471
+ // the internal plan's W8).
472
472
 
473
473
  const WEB_SEARCH_MAX_RESULTS = 5
474
474
  const NO_WEB_RESULTS_LITERAL = 'No results found.'
@@ -27,6 +27,12 @@ export declare function resolveInWorkspace(workspaceRoot: string, requestedPath:
27
27
  * doesn't implement `realpath`.
28
28
  */
29
29
  export declare function assertRealPathInWorkspace(backend: FsBackend, workspaceRoot: string, resolvedPath: string): Promise<void>;
30
+ /**
31
+ * Reads a file's current on-disk content for building a diff preview against a proposed write
32
+ * (see diff-format.ts's formatWriteDiff) — `undefined` if the file doesn't exist yet, the same
33
+ * "nothing to diff against" signal `applyPendingAction`'s own `previousContent` uses.
34
+ */
35
+ export declare function readCurrentFileContent(backend: FsBackend, workspaceRoot: string, requestedPath: string): Promise<string | undefined>;
30
36
  export declare const READ_FILE_TOOL: ToolDefinition;
31
37
  export declare const LIST_DIRECTORY_TOOL: ToolDefinition;
32
38
  export declare const WRITE_FILE_TOOL: ToolDefinition;
@@ -115,7 +121,9 @@ export interface ShellExecutionResult {
115
121
  }
116
122
  export type ApplyPendingActionResult = ({
117
123
  kind: 'write';
118
- } & PendingActionRecord) | ({
124
+ } & PendingActionRecord & {
125
+ previousContent?: string;
126
+ }) | ({
119
127
  kind: 'shell';
120
128
  } & PendingActionRecord & {
121
129
  execution: ShellExecutionResult;
@@ -130,7 +138,7 @@ export type ApplyPendingActionResult = ({
130
138
  * Stages an action for later approval. `id` is a random UUID, not derived from the session — see
131
139
  * T4 of the file-tools plan.
132
140
  *
133
- * Structural hardening principle (plans/lexical_functions_hardening_plan.html Phase 4 step 1):
141
+ * Structural hardening principle (the internal plan Phase 4 step 1):
134
142
  * this gate runs unconditionally on the tool call's own concrete arguments (the literal path/
135
143
  * content or command/cwd being proposed), independent of whatever risk-classifier.ts's pre-flight
136
144
  * text patterns concluded about the user's phrasing. That's deliberate — it's the same "gate on
@@ -9,6 +9,23 @@ import { TurnIntentClassification } from './turn-intent-classifier.js';
9
9
  import { AssistantSource } from './assistant-source.js';
10
10
  import { AssistantProgress } from './assistant-types.js';
11
11
  import { TraceEvent } from './trace-events.js';
12
+ /**
13
+ * `AUDIT_VERIFICATION` gate — feature-value audit (Phase C5 of the internal plan). EVAL-ONLY:
14
+ * default **ON** (an unset / empty / truthy value keeps today's always-on verification). Only the
15
+ * benchmark's `verificationOff` arm sets a falsy value (`0` / `false` / `off` / `no` / `disabled`),
16
+ * which makes `HarnessBridge.run` pass `skipVerification: true` to HarnessRuntime. Read at exactly
17
+ * one call site (below); no product path sets it. Same shape as `semanticContradictionEnabled()`.
18
+ */
19
+ export declare function verificationEnabled(env?: Record<string, string | undefined>): boolean;
20
+ /**
21
+ * `AUDIT_REVIEWER_PASS` gate — feature-value audit (Phase C6 of the internal plan). EVAL-ONLY:
22
+ * default **ON** (an unset / empty / truthy value keeps today's always-on 3-lens reviewer pass).
23
+ * Only the benchmark's `reviewerPassOff` arm sets a falsy value (`0` / `false` / `off` / `no` /
24
+ * `disabled`), which makes `HarnessBridge.run` pass `skipReviewerPass: true` to HarnessRuntime.
25
+ * Read at exactly one call site (below); no product path sets it. Same shape as
26
+ * `verificationEnabled()`.
27
+ */
28
+ export declare function reviewerPassEnabled(env?: Record<string, string | undefined>): boolean;
12
29
  export type HarnessOutcome = {
13
30
  status: 'paused';
14
31
  checkpoint: HarnessCheckpoint;
@@ -25,7 +42,7 @@ export interface HarnessRunParams {
25
42
  userMessage: string;
26
43
  facts: UserFact[];
27
44
  /**
28
- * Phase 4 of plans/personal_assistant_fact_extraction_llm_confidence_plan.html: this turn's
45
+ * Phase 4 of the internal plan: this turn's
29
46
  * merged lexical+LLM fact list — memory-service.ts's `buildTurnFacts()`, the exact helper
30
47
  * `recordFacts()` itself uses to decide what to write to the fact stores — fed into
31
48
  * `factExtractor` below as this turn's "isNew" belief seed. Replaces this file's former direct
@@ -43,7 +60,7 @@ export interface HarnessRunParams {
43
60
  onProgress?: (progress: AssistantProgress) => void;
44
61
  onUsage: (usage: TokenUsage) => void;
45
62
  /**
46
- * R2 of plans/harness_d2_one_loop_rewire_plan.html: a harness-driven proposer (built by
63
+ * R2 of the internal plan: a harness-driven proposer (built by
47
64
  * AgentLoop.createHarnessProposer) swapped in as the toolExecutors 'default' entry instead of
48
65
  * `() => draftReply`, when the one-loop flag is enabled and a caller actually supplies one.
49
66
  * `undefined` (every caller today, before R3 wires runTurn to build one) means `run()` falls
@@ -54,7 +71,7 @@ export interface HarnessRunParams {
54
71
  oneLoopProposer?: (toolCtx: ToolExecutorContext) => unknown | Promise<unknown>;
55
72
  /**
56
73
  * Trajectory Supervisor GATHER_EVIDENCE host (S5 of
57
- * plans/harness_trajectory_supervisor_plan.html) — AgentLoop.runSupervisorInvestigation bound
74
+ * the internal plan) — AgentLoop.runSupervisorInvestigation bound
58
75
  * to this turn's read-only tools + risk hint. Passed straight through to
59
76
  * HarnessRunOptions.runInvestigation. `undefined` (every caller until a supervisorDecider is
60
77
  * also wired) → GATHER_EVIDENCE degrades to CONTINUE inside the harness, so this is inert by
@@ -62,7 +79,7 @@ export interface HarnessRunParams {
62
79
  */
63
80
  runInvestigation?: (req: InvestigationRequestData) => Promise<InvestigationFinding[]>;
64
81
  /**
65
- * Q2 of plans/ask_question_and_plan_mode_plan.html — the already-resolved effective askMode
82
+ * Q2 of the internal plan — the already-resolved effective askMode
66
83
  * (Q1's three-tier INV-29 resolution), threaded straight into HarnessRunOptions.askMode so the
67
84
  * harness's own supervisor ASK_USER path (S3) batches questions consistently with
68
85
  * AskClarificationService's own gating. Also decides whether a thrown EscalationHalt carrying
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { z as classifyRisk } from "./cli-session-D0uLdT2A.js";
5
- import { A, a, b, C, c, D, e, f, g, h, F, i, I, L, j, k, l, P, m, n, R, o, S, p, W, q, r, s, t, u, v, w, x, y, B, G, H, J, K, M, N, Q, T, V, X, Y, Z, _, a0, a1, a2, a6, a7, a8, ab, ae, af, ag, ah, ai, aj, ak, al, am, an, ao, ap, aq, as, at, au, av, aw, ax, ay, az, aB, aC, aE, aF, aG, aH } from "./cli-session-D0uLdT2A.js";
4
+ import { B as classifyRisk } from "./cli-session-DnSc6Hhx.js";
5
+ import { A, a, b, C, c, D, e, f, g, i, F, j, I, L, k, l, m, P, n, o, R, p, S, q, W, r, s, t, u, v, w, x, y, z, G, H, J, K, M, N, O, T, U, X, Y, Z, _, $, a1, a2, a3, a7, a8, a9, ac, af, ag, ah, ai, aj, ak, al, am, an, ao, ap, aq, ar, at, au, av, aw, ax, ay, az, aA, aC, aD, aF, aG, aH, aI } from "./cli-session-DnSc6Hhx.js";
6
6
  import { MAX_OPTIONS_PER_QUESTION, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, validateAskResponse } from "@buildaharness/harness";
7
7
  const PLAN_SCHEMA = {
8
8
  type: "object",
@@ -128,85 +128,85 @@ export {
128
128
  e as DEFAULT_CONFIG,
129
129
  f as DEFAULT_ONE_LOOP_MODE,
130
130
  g as DEFAULT_PLAN_MODE,
131
- h as EmailDeliveryError,
131
+ i as EmailDeliveryError,
132
132
  F as FETCH_URL_TOOL,
133
- i as FILE_TOOLS,
133
+ j as FILE_TOOLS,
134
134
  I as InvalidEmailArgsError,
135
135
  L as LAYER_DISPLAY_NAME,
136
- j as LAYER_ORDER,
137
- k as LAYER_SHORT_CODE,
138
- l as LIST_DIRECTORY_TOOL,
136
+ k as LAYER_ORDER,
137
+ l as LAYER_SHORT_CODE,
138
+ m as LIST_DIRECTORY_TOOL,
139
139
  MAX_OPTIONS_PER_QUESTION,
140
140
  MAX_QUESTIONS_PER_BATCH,
141
141
  MIN_OPTIONS_PER_QUESTION,
142
142
  P as PathOutsideWorkspaceError,
143
- m as PersonalAssistant,
144
- n as PrivateNetworkTargetError,
143
+ n as PersonalAssistant,
144
+ o as PrivateNetworkTargetError,
145
145
  R as READ_FILE_TOOL,
146
- o as RUN_SHELL_COMMAND_TOOL,
146
+ p as RUN_SHELL_COMMAND_TOOL,
147
147
  S as SEND_EMAIL_TOOL,
148
- p as SHELL_TOOLS,
148
+ q as SHELL_TOOLS,
149
149
  W as WEB_SEARCH_TOOL,
150
- q as WEB_TOOLS,
151
- r as WRITE_FILE_TOOL,
152
- s as abandonPlan,
153
- t as applyPendingAction,
154
- u as assertPublicHttpUrl,
155
- v as braveSearch,
156
- w as buildClaudePrompt,
150
+ r as WEB_TOOLS,
151
+ s as WRITE_FILE_TOOL,
152
+ t as abandonPlan,
153
+ u as applyPendingAction,
154
+ v as assertPublicHttpUrl,
155
+ w as braveSearch,
156
+ x as buildClaudePrompt,
157
157
  buildPlanFromTemplate,
158
- x as buildWhyChain,
159
- y as classifyError,
158
+ y as buildWhyChain,
159
+ z as classifyError,
160
160
  classifyRisk,
161
- B as classifyToolYield,
162
- G as classifyTurnIntent,
163
- H as computePlanPosition,
164
- J as createPlanRecord,
165
- K as createResendSender,
161
+ G as classifyToolYield,
162
+ H as classifyTurnIntent,
163
+ J as computePlanPosition,
164
+ K as createPlanRecord,
165
+ M as createResendSender,
166
166
  createScriptedLLMClient,
167
- M as createSmtpSender,
168
- N as defaultExportFilename,
169
- Q as detectHomogeneousBatchList,
170
- T as discardPendingAction,
171
- V as estimateCostUsd,
172
- X as executeActionTool,
173
- Y as executeFileTool,
174
- Z as executeShellTool,
175
- _ as executeWebTool,
176
- a0 as formatCostSummary,
177
- a1 as formatDoctorReport,
178
- a2 as formatEmailApprovalReason,
179
- a6 as formatMemorySummary,
180
- a7 as formatPlanProgress,
181
- a8 as formatSearchResults,
182
- ab as formatTranscriptMarkdown,
183
- ae as isLikelyEmailAddress,
184
- af as listTemplateNames,
185
- ag as loadActivePlan,
186
- ah as loadPendingAction,
187
- ai as loadTemplate,
188
- aj as matchTemplateIfConfident,
189
- ak as nextPendingTask,
190
- al as nodeDisplayName,
191
- am as nodeToLayer,
192
- an as normalizeAskMode,
193
- ao as normalizeOneLoopMode,
194
- ap as normalizePlanMode,
195
- aq as parseClaudeCliOutput,
196
- as as pickTemplateForTask,
197
- at as planCompletionPct,
198
- au as resolveAskMode,
199
- av as resolveConfig,
200
- aw as resolveInWorkspace,
201
- ax as resolveOneLoopMode,
202
- ay as resolvePlanMode,
203
- az as savePlan,
204
- aB as stagePendingAction,
205
- aC as stagedActionInput,
206
- aE as stripMcpToolPrefix,
207
- aF as summarizeToolStep,
208
- aG as updatePlanFromRun,
167
+ N as createSmtpSender,
168
+ O as defaultExportFilename,
169
+ T as detectHomogeneousBatchList,
170
+ U as discardPendingAction,
171
+ X as estimateCostUsd,
172
+ Y as executeActionTool,
173
+ Z as executeFileTool,
174
+ _ as executeShellTool,
175
+ $ as executeWebTool,
176
+ a1 as formatCostSummary,
177
+ a2 as formatDoctorReport,
178
+ a3 as formatEmailApprovalReason,
179
+ a7 as formatMemorySummary,
180
+ a8 as formatPlanProgress,
181
+ a9 as formatSearchResults,
182
+ ac as formatTranscriptMarkdown,
183
+ af as isLikelyEmailAddress,
184
+ ag as listTemplateNames,
185
+ ah as loadActivePlan,
186
+ ai as loadPendingAction,
187
+ aj as loadTemplate,
188
+ ak as matchTemplateIfConfident,
189
+ al as nextPendingTask,
190
+ am as nodeDisplayName,
191
+ an as nodeToLayer,
192
+ ao as normalizeAskMode,
193
+ ap as normalizeOneLoopMode,
194
+ aq as normalizePlanMode,
195
+ ar as parseClaudeCliOutput,
196
+ at as pickTemplateForTask,
197
+ au as planCompletionPct,
198
+ av as resolveAskMode,
199
+ aw as resolveConfig,
200
+ ax as resolveInWorkspace,
201
+ ay as resolveOneLoopMode,
202
+ az as resolvePlanMode,
203
+ aA as savePlan,
204
+ aC as stagePendingAction,
205
+ aD as stagedActionInput,
206
+ aF as stripMcpToolPrefix,
207
+ aG as summarizeToolStep,
208
+ aH as updatePlanFromRun,
209
209
  validateAskResponse,
210
- aH as validateConfig
210
+ aI as validateConfig
211
211
  };
212
212
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/plan-builder.ts","../src/scripted-llm-client.ts"],"sourcesContent":["import type { ILLMClient, TokenUsage } from '@buildaharness/runtime'\nimport type { DecomposedTaskSpec } from './decomposition-classifier.js'\nimport type { PlanTemplate } from './plan-templates/index.js'\n\nexport interface Plan {\n templateName: string\n successCriteria: string\n tasks: DecomposedTaskSpec[]\n /**\n * Why this approach, not just what the success criteria are — optional for now since neither\n * buildPlanFromTemplate's template-personalization call nor any other current producer of a\n * Plan asks the LLM for one; plan-store.ts's createPlanRecord defaults it to '' when absent.\n * A future from-scratch drafting path (plan mode's P3) is expected to populate it.\n */\n rationale?: string\n}\n\nconst PLAN_SCHEMA = {\n type: 'object',\n properties: {\n tasks: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n description: { type: 'string' },\n depends_on: { type: 'array', items: { type: 'string' } },\n },\n required: ['id', 'description', 'depends_on'],\n },\n },\n },\n required: ['tasks'],\n}\n\nfunction buildSystemPrompt(template: PlanTemplate): string {\n const skeleton = template.tasks\n .map((t) => `- id: ${t.id}; title: ${t.title}; depends_on: [${t.depends_on.join(', ')}]`)\n .join('\\n')\n return (\n `You are adapting a \"${template.name}\" plan template to a specific user request. ` +\n `Here is the template's task skeleton — keep the exact same ids and depends_on structure, ` +\n `one output task per skeleton task, but personalize each description to the actual request:\\n${skeleton}\\n\\n` +\n `Success criteria for this kind of plan: ${template.success_criteria}\\n\\n` +\n 'Phrase each `description` starting with the concrete subject or object it acts on (e.g. \"the login tests: ' +\n 'rerun after the config fix\" rather than \"rerun the login tests after the config fix\"), so later comparisons ' +\n \"against this task's completion/failure beliefs share matching vocabulary. \" +\n 'Respond with JSON only, no prose: {\"tasks\":[{\"id\": string, \"description\": string, \"depends_on\": ' +\n 'string[]}]}. `id` and `depends_on` values must exactly match the skeleton above.'\n )\n}\n\n// Only the shape the LLM actually returns (id/description/depends_on) — riskLevel isn't asked for\n// here at all (see buildPlanFromTemplate's doc comment for why) and gets attached afterward from\n// the template's own already-curated risk_level, so this is deliberately narrower than\n// DecomposedTaskSpec itself.\ninterface RawPlanTask {\n id: string\n description: string\n depends_on: string[]\n}\n\nfunction isRawPlanTask(value: unknown): value is RawPlanTask {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return (\n typeof v.id === 'string' &&\n typeof v.description === 'string' &&\n Array.isArray(v.depends_on) &&\n v.depends_on.every((d) => typeof d === 'string')\n )\n}\n\n/**\n * Spends one real LLM call personalizing `template`'s task skeleton to `message` —\n * only called once classifyTurnIntent has already named a matchedPlanTemplate, so an\n * ordinary turn never pays for this. Same \"malformed/incomplete JSON is the expected\n * failure mode, not the edge case\" fallback classifyTurnIntent itself uses: any parse\n * failure or a response with fewer than 2 usable tasks returns null, meaning \"fall back to\n * the caller's own ad hoc decomposition for this turn\" rather than throwing.\n *\n * Each output task's `riskLevel` is attached from the matching skeleton task's own\n * `risk_level` (curated by whoever wrote the template's JSON) by id, not asked of the LLM —\n * the template already has this, more accurately than an LLM re-deriving it from a\n * personalized one-line description would, and it costs zero extra output tokens. Falls back\n * to 'LOW' only for the pathological case of a task id the LLM invented that doesn't match any\n * skeleton task (the id/depends_on-must-match-skeleton instruction above means this should not\n * happen in practice).\n */\nexport async function buildPlanFromTemplate(\n llmClient: ILLMClient,\n message: string,\n template: PlanTemplate,\n model?: string,\n onUsage?: (usage: TokenUsage) => void,\n): Promise<Plan | null> {\n try {\n const response = await llmClient.callChatStructured(\n [\n { role: 'system', content: buildSystemPrompt(template) },\n { role: 'user', content: message },\n ],\n undefined,\n { model, onUsage, structuredOutput: { schema: PLAN_SCHEMA } },\n )\n const parsed = JSON.parse(response.content) as { tasks?: unknown }\n if (!Array.isArray(parsed.tasks)) return null\n const rawTasks = parsed.tasks.filter(isRawPlanTask)\n if (rawTasks.length <= 1) return null\n const riskByTemplateId = new Map(template.tasks.map((t) => [t.id, t.risk_level]))\n const tasks: DecomposedTaskSpec[] = rawTasks.map((t) => ({ ...t, riskLevel: riskByTemplateId.get(t.id) ?? 'LOW' }))\n return { templateName: template.name, successCriteria: template.success_criteria, tasks }\n } catch {\n return null\n }\n}\n","import type {\n ChatMessage,\n ChatOptions,\n ILLMClient,\n LLMStructuredResponse,\n ToolDefinition,\n} from '@buildaharness/runtime'\nimport { classifyRisk } from './risk-classifier.js'\n\n/**\n * A deterministic, network-free {@link ILLMClient} for tests and demos — the one publicly\n * supported way to drive a real `PersonalAssistant` turn end to end without a proxy, an API key,\n * or a live model. Built for plans/chat_ui_browser_e2e_plan.html phase B1 (the browser-e2e lane\n * that unblocks the `ASSISTANT_ONE_LOOP` default-flip), and reusable by chat-ui's `demo-seed.ts`.\n *\n * It mirrors the shape of the internal `ScriptedToolLLMClient` that `assistant.test.ts` has used\n * for a long time: every turn spends exactly one mandatory `classifyTurnIntent` call up front,\n * which this client recognises (by a distinctive phrase from that classifier's system prompt) and\n * answers on its own from a lightweight, deterministic default — so `responses` only has to\n * script the *tool loop / one-loop proposer's* own calls, not that bookkeeping call.\n *\n * `responses` entries are consumed in order, one per non-classifier `callChatStructured` call:\n * - a plain `string` → `{ content: string }` (a final answer, no tool calls)\n * - an `LLMStructuredResponse` → use as-is (set `toolCalls` to simulate the model calling a tool)\n *\n * `callChat`/`callChatSync` (the no-tools reply path, and post-approval synthesis) stream\n * `streamChunks` (default: `['']`).\n */\nexport interface ScriptedLLMClientScript {\n /** Ordered structured responses for the tool loop / one-loop proposer. A bare string is shorthand for a final answer with no tool calls. */\n responses?: Array<string | LLMStructuredResponse>\n /** Chunks streamed from `callChat`/`callChatSync`. Defaults to `['']`. */\n streamChunks?: string[]\n /**\n * Per-user-message override of the auto-answered `classifyTurnIntent` JSON fields (e.g.\n * `{ isTrivial: true }`, `{ riskLevel: 'HIGH' }`, `{ decomposedTasks: [...] }`). Return\n * `undefined` to keep the derived default for that message.\n */\n classify?: (userMessage: string) => Record<string, unknown> | undefined\n}\n\n/** The phrase that opens `turn-intent-classifier.ts`'s system prompt — stable, and how every scripted client tells that mandatory call apart from a real tool-loop call. */\nconst TURN_INTENT_MARKER = 'eight independent judgments'\n\nfunction isTurnIntentRequest(messages: ChatMessage[]): boolean {\n return messages.some((m) => m.role === 'system' && m.content.includes(TURN_INTENT_MARKER))\n}\n\n/**\n * The deterministic stand-in for what a real model would return for `classifyTurnIntent` —\n * risk comes from the same lexical `classifyRisk` the real fallback path uses; everything else\n * is inert (not trivial, not a reminder, no decomposition, no plan template) unless `override`\n * says otherwise. `isTrivial` defaults to `false` so a scripted turn actually exercises the\n * harness loop (the interesting case for one-loop parity tests); pass `{ isTrivial: true }` via\n * `classify` for the triviality fast path.\n */\nfunction deriveTurnIntentJSON(messages: ChatMessage[], override?: Record<string, unknown>): string {\n const userContent = messages.find((m) => m.role === 'user')?.content ?? ''\n const risk = classifyRisk(userContent)\n const isReminderRequest = risk.reason.includes('reminder')\n const base = {\n riskLevel: risk.riskLevel,\n riskReason: risk.reason,\n isTrivial: false,\n decomposedTasks: [],\n isReminderRequest,\n isBulkReminderRequest: isReminderRequest && risk.requiresApproval,\n isAbandonRequest: false,\n matchedPlanTemplate: null,\n needsMultiStepPlan: false,\n statesDurableFacts: [],\n }\n return JSON.stringify({ ...base, ...override })\n}\n\nclass ScriptedLLMClient implements ILLMClient {\n /** Non-classifier `callChatStructured` calls made so far — the index into `responses`. */\n toolCalls = 0\n private responseIndex = 0\n\n constructor(private readonly script: ScriptedLLMClientScript) {}\n\n private get streamChunks(): string[] {\n return this.script.streamChunks && this.script.streamChunks.length > 0 ? this.script.streamChunks : ['']\n }\n\n async *callChat(_messages: ChatMessage[], _options?: ChatOptions): AsyncIterable<string> {\n for (const chunk of this.streamChunks) yield chunk\n }\n\n async callChatSync(messages: ChatMessage[], options?: ChatOptions): Promise<string> {\n const chunks: string[] = []\n for await (const chunk of this.callChat(messages, options)) chunks.push(chunk)\n return chunks.join('')\n }\n\n async callChatStructured(messages: ChatMessage[], _tools?: ToolDefinition[], _options?: ChatOptions): Promise<LLMStructuredResponse> {\n if (isTurnIntentRequest(messages)) {\n const userContent = messages.find((m) => m.role === 'user')?.content ?? ''\n return { content: deriveTurnIntentJSON(messages, this.script.classify?.(userContent)) }\n }\n this.toolCalls++\n const responses = this.script.responses ?? []\n if (this.responseIndex >= responses.length) {\n throw new Error(\n `createScriptedLLMClient: no scripted response for tool-loop call #${this.responseIndex + 1} (scripted ${responses.length})`,\n )\n }\n const next = responses[this.responseIndex++]\n return typeof next === 'string' ? { content: next } : next\n }\n}\n\n/**\n * Builds a scripted {@link ILLMClient}. See {@link ScriptedLLMClientScript}. The returned value\n * is a plain `ILLMClient` — pass it straight to `PersonalAssistant.create({ llmClient })`.\n */\nexport function createScriptedLLMClient(script: ScriptedLLMClientScript = {}): ILLMClient {\n return new ScriptedLLMClient(script)\n}\n"],"names":["t","v","m"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/plan-builder.ts","../src/scripted-llm-client.ts"],"sourcesContent":["import type { ILLMClient, TokenUsage } from '@buildaharness/runtime'\nimport type { DecomposedTaskSpec } from './decomposition-classifier.js'\nimport type { PlanTemplate } from './plan-templates/index.js'\n\nexport interface Plan {\n templateName: string\n successCriteria: string\n tasks: DecomposedTaskSpec[]\n /**\n * Why this approach, not just what the success criteria are — optional for now since neither\n * buildPlanFromTemplate's template-personalization call nor any other current producer of a\n * Plan asks the LLM for one; plan-store.ts's createPlanRecord defaults it to '' when absent.\n * A future from-scratch drafting path (plan mode's P3) is expected to populate it.\n */\n rationale?: string\n}\n\nconst PLAN_SCHEMA = {\n type: 'object',\n properties: {\n tasks: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n description: { type: 'string' },\n depends_on: { type: 'array', items: { type: 'string' } },\n },\n required: ['id', 'description', 'depends_on'],\n },\n },\n },\n required: ['tasks'],\n}\n\nfunction buildSystemPrompt(template: PlanTemplate): string {\n const skeleton = template.tasks\n .map((t) => `- id: ${t.id}; title: ${t.title}; depends_on: [${t.depends_on.join(', ')}]`)\n .join('\\n')\n return (\n `You are adapting a \"${template.name}\" plan template to a specific user request. ` +\n `Here is the template's task skeleton — keep the exact same ids and depends_on structure, ` +\n `one output task per skeleton task, but personalize each description to the actual request:\\n${skeleton}\\n\\n` +\n `Success criteria for this kind of plan: ${template.success_criteria}\\n\\n` +\n 'Phrase each `description` starting with the concrete subject or object it acts on (e.g. \"the login tests: ' +\n 'rerun after the config fix\" rather than \"rerun the login tests after the config fix\"), so later comparisons ' +\n \"against this task's completion/failure beliefs share matching vocabulary. \" +\n 'Respond with JSON only, no prose: {\"tasks\":[{\"id\": string, \"description\": string, \"depends_on\": ' +\n 'string[]}]}. `id` and `depends_on` values must exactly match the skeleton above.'\n )\n}\n\n// Only the shape the LLM actually returns (id/description/depends_on) — riskLevel isn't asked for\n// here at all (see buildPlanFromTemplate's doc comment for why) and gets attached afterward from\n// the template's own already-curated risk_level, so this is deliberately narrower than\n// DecomposedTaskSpec itself.\ninterface RawPlanTask {\n id: string\n description: string\n depends_on: string[]\n}\n\nfunction isRawPlanTask(value: unknown): value is RawPlanTask {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return (\n typeof v.id === 'string' &&\n typeof v.description === 'string' &&\n Array.isArray(v.depends_on) &&\n v.depends_on.every((d) => typeof d === 'string')\n )\n}\n\n/**\n * Spends one real LLM call personalizing `template`'s task skeleton to `message` —\n * only called once classifyTurnIntent has already named a matchedPlanTemplate, so an\n * ordinary turn never pays for this. Same \"malformed/incomplete JSON is the expected\n * failure mode, not the edge case\" fallback classifyTurnIntent itself uses: any parse\n * failure or a response with fewer than 2 usable tasks returns null, meaning \"fall back to\n * the caller's own ad hoc decomposition for this turn\" rather than throwing.\n *\n * Each output task's `riskLevel` is attached from the matching skeleton task's own\n * `risk_level` (curated by whoever wrote the template's JSON) by id, not asked of the LLM —\n * the template already has this, more accurately than an LLM re-deriving it from a\n * personalized one-line description would, and it costs zero extra output tokens. Falls back\n * to 'LOW' only for the pathological case of a task id the LLM invented that doesn't match any\n * skeleton task (the id/depends_on-must-match-skeleton instruction above means this should not\n * happen in practice).\n */\nexport async function buildPlanFromTemplate(\n llmClient: ILLMClient,\n message: string,\n template: PlanTemplate,\n model?: string,\n onUsage?: (usage: TokenUsage) => void,\n): Promise<Plan | null> {\n try {\n const response = await llmClient.callChatStructured(\n [\n { role: 'system', content: buildSystemPrompt(template) },\n { role: 'user', content: message },\n ],\n undefined,\n { model, onUsage, structuredOutput: { schema: PLAN_SCHEMA } },\n )\n const parsed = JSON.parse(response.content) as { tasks?: unknown }\n if (!Array.isArray(parsed.tasks)) return null\n const rawTasks = parsed.tasks.filter(isRawPlanTask)\n if (rawTasks.length <= 1) return null\n const riskByTemplateId = new Map(template.tasks.map((t) => [t.id, t.risk_level]))\n const tasks: DecomposedTaskSpec[] = rawTasks.map((t) => ({ ...t, riskLevel: riskByTemplateId.get(t.id) ?? 'LOW' }))\n return { templateName: template.name, successCriteria: template.success_criteria, tasks }\n } catch {\n return null\n }\n}\n","import type {\n ChatMessage,\n ChatOptions,\n ILLMClient,\n LLMStructuredResponse,\n ToolDefinition,\n} from '@buildaharness/runtime'\nimport { classifyRisk } from './risk-classifier.js'\n\n/**\n * A deterministic, network-free {@link ILLMClient} for tests and demos — the one publicly\n * supported way to drive a real `PersonalAssistant` turn end to end without a proxy, an API key,\n * or a live model. Built for the internal plan phase B1 (the browser-e2e lane\n * that unblocks the `ASSISTANT_ONE_LOOP` default-flip), and reusable by chat-ui's `demo-seed.ts`.\n *\n * It mirrors the shape of the internal `ScriptedToolLLMClient` that `assistant.test.ts` has used\n * for a long time: every turn spends exactly one mandatory `classifyTurnIntent` call up front,\n * which this client recognises (by a distinctive phrase from that classifier's system prompt) and\n * answers on its own from a lightweight, deterministic default — so `responses` only has to\n * script the *tool loop / one-loop proposer's* own calls, not that bookkeeping call.\n *\n * `responses` entries are consumed in order, one per non-classifier `callChatStructured` call:\n * - a plain `string` → `{ content: string }` (a final answer, no tool calls)\n * - an `LLMStructuredResponse` → use as-is (set `toolCalls` to simulate the model calling a tool)\n *\n * `callChat`/`callChatSync` (the no-tools reply path, and post-approval synthesis) stream\n * `streamChunks` (default: `['']`).\n */\nexport interface ScriptedLLMClientScript {\n /** Ordered structured responses for the tool loop / one-loop proposer. A bare string is shorthand for a final answer with no tool calls. */\n responses?: Array<string | LLMStructuredResponse>\n /** Chunks streamed from `callChat`/`callChatSync`. Defaults to `['']`. */\n streamChunks?: string[]\n /**\n * Per-user-message override of the auto-answered `classifyTurnIntent` JSON fields (e.g.\n * `{ isTrivial: true }`, `{ riskLevel: 'HIGH' }`, `{ decomposedTasks: [...] }`). Return\n * `undefined` to keep the derived default for that message.\n */\n classify?: (userMessage: string) => Record<string, unknown> | undefined\n}\n\n/** The phrase that opens `turn-intent-classifier.ts`'s system prompt — stable, and how every scripted client tells that mandatory call apart from a real tool-loop call. */\nconst TURN_INTENT_MARKER = 'eight independent judgments'\n\nfunction isTurnIntentRequest(messages: ChatMessage[]): boolean {\n return messages.some((m) => m.role === 'system' && m.content.includes(TURN_INTENT_MARKER))\n}\n\n/**\n * The deterministic stand-in for what a real model would return for `classifyTurnIntent` —\n * risk comes from the same lexical `classifyRisk` the real fallback path uses; everything else\n * is inert (not trivial, not a reminder, no decomposition, no plan template) unless `override`\n * says otherwise. `isTrivial` defaults to `false` so a scripted turn actually exercises the\n * harness loop (the interesting case for one-loop parity tests); pass `{ isTrivial: true }` via\n * `classify` for the triviality fast path.\n */\nfunction deriveTurnIntentJSON(messages: ChatMessage[], override?: Record<string, unknown>): string {\n const userContent = messages.find((m) => m.role === 'user')?.content ?? ''\n const risk = classifyRisk(userContent)\n const isReminderRequest = risk.reason.includes('reminder')\n const base = {\n riskLevel: risk.riskLevel,\n riskReason: risk.reason,\n isTrivial: false,\n decomposedTasks: [],\n isReminderRequest,\n isBulkReminderRequest: isReminderRequest && risk.requiresApproval,\n isAbandonRequest: false,\n matchedPlanTemplate: null,\n needsMultiStepPlan: false,\n statesDurableFacts: [],\n }\n return JSON.stringify({ ...base, ...override })\n}\n\nclass ScriptedLLMClient implements ILLMClient {\n /** Non-classifier `callChatStructured` calls made so far — the index into `responses`. */\n toolCalls = 0\n private responseIndex = 0\n\n constructor(private readonly script: ScriptedLLMClientScript) {}\n\n private get streamChunks(): string[] {\n return this.script.streamChunks && this.script.streamChunks.length > 0 ? this.script.streamChunks : ['']\n }\n\n async *callChat(_messages: ChatMessage[], _options?: ChatOptions): AsyncIterable<string> {\n for (const chunk of this.streamChunks) yield chunk\n }\n\n async callChatSync(messages: ChatMessage[], options?: ChatOptions): Promise<string> {\n const chunks: string[] = []\n for await (const chunk of this.callChat(messages, options)) chunks.push(chunk)\n return chunks.join('')\n }\n\n async callChatStructured(messages: ChatMessage[], _tools?: ToolDefinition[], _options?: ChatOptions): Promise<LLMStructuredResponse> {\n if (isTurnIntentRequest(messages)) {\n const userContent = messages.find((m) => m.role === 'user')?.content ?? ''\n return { content: deriveTurnIntentJSON(messages, this.script.classify?.(userContent)) }\n }\n this.toolCalls++\n const responses = this.script.responses ?? []\n if (this.responseIndex >= responses.length) {\n throw new Error(\n `createScriptedLLMClient: no scripted response for tool-loop call #${this.responseIndex + 1} (scripted ${responses.length})`,\n )\n }\n const next = responses[this.responseIndex++]\n return typeof next === 'string' ? { content: next } : next\n }\n}\n\n/**\n * Builds a scripted {@link ILLMClient}. See {@link ScriptedLLMClientScript}. The returned value\n * is a plain `ILLMClient` — pass it straight to `PersonalAssistant.create({ llmClient })`.\n */\nexport function createScriptedLLMClient(script: ScriptedLLMClientScript = {}): ILLMClient {\n return new ScriptedLLMClient(script)\n}\n"],"names":["t","v","m"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { SelectOption } from './cli.js';
2
+ export interface SelectPromptProps {
3
+ /** The question text, rendered the same bold-yellow way `TuiInput`'s own `promptLabel` renders a pending askYesNo/askLine question. */
4
+ question: string;
5
+ /** Rendered in order, top to bottom — the highlighted (arrow-key-selected) option starts at index 0. Each option's `key` also doubles as its single-keystroke shortcut. */
6
+ options: SelectOption[];
7
+ /** Called once, with the chosen option's `key` — either Enter on the highlighted row, or a direct keystroke matching an option's `key`. */
8
+ onSubmit: (key: string) => void;
9
+ isActive?: boolean;
10
+ }
11
+ /**
12
+ * Phase 7 of the internal plan: an arrow-key + numbered/shortcut-key
13
+ * selector, replacing free-text `y`/`N` typed into the same box used for chat (the report's
14
+ * finding — Codex/Claude Code both use a dedicated selector for this). Deliberately a standalone
15
+ * component rather than a mode bolted onto `TuiInput`: `TuiInput` is a general-purpose free-text
16
+ * editor (word-wrap, cursor movement, history recall) that has no notion of a fixed option list,
17
+ * and grafting one on would tangle two unrelated input models together for no shared benefit — the
18
+ * two components share only the surrounding visual grammar (bold label above a round-bordered
19
+ * box), which each renders independently.
20
+ */
21
+ export declare function SelectPrompt(props: SelectPromptProps): React.JSX.Element;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Recognizes one line of `diff-format.ts`'s output by shape: `DIFF_INDENT` followed by a
3
+ * right-aligned line number and a unified-diff sign character. That combination — a fixed
4
+ * two-space indent no ordinary assistant prose produces, paired with a line-number gutter no
5
+ * markdown construct produces — doesn't occur in natural writing (a markdown bullet is always
6
+ * `- text`, dash *then* a space with no number in front, which `CHECKBOX_RE`/plain rendering
7
+ * already own), so this can't misfire on a real bullet list. Context lines (sign is a plain
8
+ * space) are recognized too, with `color: undefined` — they still need to short-circuit
9
+ * `renderMarkdownLine`'s plain-text fallback below (which would otherwise prepend `ASSISTANT_BULLET`
10
+ * to them as if they were ordinary prose), they just aren't colored the way a real diff tool
11
+ * doesn't color its context lines either.
12
+ */
13
+ export declare function matchDiffLine(text: string): {
14
+ content: string;
15
+ color: 'green' | 'red' | undefined;
16
+ } | undefined;
17
+ /**
18
+ * Renders one line as colored diff content if `matchDiffLine` recognizes it, otherwise
19
+ * `undefined` so the caller falls back to its own plain/markdown rendering. Used by
20
+ * `renderMarkdownLine` below (the `'assistant'`-kind, post-write confirmation) and by
21
+ * `tui-app.tsx`'s `'system'`-kind branch (the pre-approval preview) — the two `LogLine` kinds a
22
+ * `formatWriteDiff()` string actually reaches, per `classifyLineKind`.
23
+ */
24
+ export declare function renderDiffLine(text: string, key: React.Key): React.JSX.Element | undefined;
25
+ /**
26
+ * Colors the `[needs approval — write/shell command/…]`/`[needs approval — HIGH/…]` tag
27
+ * `cli.ts` prints ahead of every approval prompt (see its own `console.log` call sites) yellow —
28
+ * the same "worth your attention" color `StatusLine`'s own `⚠`-prefixed indicators already use —
29
+ * leaving the reason text after it in the default color. This line is `'system'`-classified (see
30
+ * `classifyLineKind`), not `'assistant'`, so it's checked from `tui-app.tsx`'s own `'system'`
31
+ * branch rather than from `renderMarkdownLine`.
32
+ */
33
+ export declare function renderNeedsApprovalLine(text: string, key: React.Key): React.JSX.Element | undefined;
34
+ /**
35
+ * Renders one committed line of assistant/system text, recognizing a leading `#`/`##`/… header or
36
+ * `- [ ]`/`- [x]` checkbox before falling back to plain inline-styled text. `key` is the React key
37
+ * for the returned element (the caller already has a per-line index from `<Static>`'s own render
38
+ * prop — see `tui-app.tsx`'s `LogLineText`). `continuation` is `LogLine.continuation` — true for
39
+ * every line of a multi-line reply after the first, which suppresses `ASSISTANT_BULLET` (the
40
+ * marker belongs once at the front of the whole message, not on every wrapped line).
41
+ */
42
+ export declare function renderMarkdownLine(text: string, key: React.Key, continuation?: boolean): React.JSX.Element;
@@ -0,0 +1,24 @@
1
+ export declare const MCP_SERVER_FILE_NAME = "file-tools-mcp-server.mjs";
2
+ /**
3
+ * The lexical pattern files file-tools-mcp-server.mjs reads at startup via
4
+ * `join(__dirname, 'lexical/patterns/<name>')` — they have to sit beside the extracted server,
5
+ * so they are embedded as SEA assets (under this same relative key) and extracted with it.
6
+ */
7
+ export declare const MCP_SERVER_PATTERN_FILES: readonly ["injection-patterns.json", "fact-markers.json", "risk-patterns.json"];
8
+ export interface SeaModule {
9
+ isSea(): boolean;
10
+ getRawAsset(key: string): ArrayBuffer;
11
+ }
12
+ /** `node:sea` if this process is a Node single executable application, else null. Never throws. */
13
+ export declare function loadSea(): SeaModule | null;
14
+ /**
15
+ * Absolute on-disk path to file-tools-mcp-server.mjs, which the claude-cli backend hands to the
16
+ * separate `claude` binary to spawn as a `node <path>` MCP subprocess.
17
+ *
18
+ * - Inside a SEA binary, `import.meta.url` is not a real file location, so the embedded asset is
19
+ * extracted once (version-gated, see sea-cache.ts) and that path returned.
20
+ * - Otherwise (npm install, tsx), it is the file sitting next to this module, as before.
21
+ *
22
+ * `seaOverride` and `home` exist for tests; production callers pass nothing.
23
+ */
24
+ export declare function resolveMcpServerPath(seaOverride?: SeaModule | null, home?: string): string;
@@ -8,7 +8,7 @@ export declare const DURABLE_FACTS_KEY = "facts:durable";
8
8
  /**
9
9
  * Global, never cleared by `/new` — same retention shape as DURABLE_FACTS_KEY (a pending fact
10
10
  * shouldn't vanish just because the session changed). Phase 3 of
11
- * plans/personal_assistant_fact_extraction_llm_confidence_plan.html: a `model_inferred` fact
11
+ * the internal plan: a `model_inferred` fact
12
12
  * that reached `durable: true, confidence: 'medium'` lands here instead of being silently
13
13
  * promoted or silently dropped — see `recordFacts()`'s promotion policy and
14
14
  * `confirmPendingFact()`/`rejectPendingFact()` below.
@@ -75,9 +75,19 @@ export interface RecordFactsResult {
75
75
  contradictions: ExternalContradictionInput[];
76
76
  corroborations: Corroboration[];
77
77
  }
78
+ /**
79
+ * `AUDIT_MODEL_INFERRED_FACTS` gate — feature-value audit (Phase C3 of the internal plan).
80
+ * Default **ON**: `classifyTurnIntent`'s `statesDurableFacts` (source `model_inferred`) reach
81
+ * memory today, so an unset / empty / truthy value keeps that behaviour. Set to a falsy value
82
+ * (`0` / `false` / `off` / `no` / `disabled`) to drop them, leaving only the lexical
83
+ * `user_asserted` pass. Gates the *consumption* of the classifier's facts, not the classifier call
84
+ * (which also produces the intent/risk fields every arm needs). Read at exactly one call site —
85
+ * `buildTurnFacts()` below. Same shape as `semanticContradictionEnabled()` (contradiction-checker.ts).
86
+ */
87
+ export declare function modelInferredFactsEnabled(env?: Record<string, string | undefined>): boolean;
78
88
  /**
79
89
  * The exact lexical+LLM merge `recordFacts()` uses to decide what to write to the fact stores,
80
- * exposed (Phase 4 of plans/personal_assistant_fact_extraction_llm_confidence_plan.html) so a
90
+ * exposed (Phase 4 of the internal plan) so a
81
91
  * caller can seed `HarnessRunParams.currentTurnFacts` (harness-bridge.ts's `factExtractor`) with
82
92
  * the identical merged, deduplicated list — instead of harness-bridge.ts recomputing only the free
83
93
  * lexical half and never seeing an LLM-caught fact at all, which left a same-turn LLM-caught fact
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Node-level network containment for an approved run_shell_command execution — Decision 6 of
3
- * plans/lexical_functions_hardening_plan.html (Phase 4 step 2). This is the network-reachability
3
+ * the internal plan (Phase 4 step 2). This is the network-reachability
4
4
  * half of that decision; the env-stripping half already existed in shell-executor.ts's
5
5
  * allowlistedEnv(). Deliberately not a real OS sandbox (see the plan's Decision 6 for why that
6
6
  * tradeoff was chosen): a minimal loopback-only forward proxy that relays a CONNECT tunnel
@@ -1,7 +1,7 @@
1
1
  import { LayerActivityEvent } from '@buildaharness/harness';
2
2
  export declare function nodeDisplayName(node: string | undefined): string | undefined;
3
3
  /**
4
- * The 11 harness layer slugs, in the fixed order CLAUDE.md's architecture table lists them —
4
+ * The 11 harness layer slugs, in the fixed order the internal developer notes' architecture table lists them —
5
5
  * used by both the CLI's `/layers` and chat-ui's layer grid to render a stable 2-3 letter
6
6
  * code per layer (Phase 3.3 of the harness layer activation plan). Matches the `layer` field
7
7
  * on the `layer_activity` TraceEvent (see trace-events.ts) and HarnessRunOptions'
@@ -5,7 +5,7 @@
5
5
  * a module-level default resolved once from process.env, threaded as an injectable constructor
6
6
  * option so tests never touch real process.env.
7
7
  *
8
- * Backs plans/harness_d2_one_loop_rewire_plan.html's R2 phase: the harness-driven proposer that
8
+ * Backs the internal plan's R2 phase: the harness-driven proposer that
9
9
  * lets HarnessRuntime's driveMainLoop actually call AgentLoop's tool-calling machinery once per
10
10
  * main-loop iteration, instead of receiving an already-finished draftReply after the fact. R2
11
11
  * landed it default OFF; R3/R4 built the rest of the wiring behind it; R5 gathered the rollout
@@ -23,7 +23,7 @@ export type PlanDraftOutcome = {
23
23
  fallThrough: true;
24
24
  } | AssistantTurnResult;
25
25
  /**
26
- * P3 of plans/ask_question_and_plan_mode_plan.html — how `assistant.ts`'s auto-trigger tells
26
+ * P3 of the internal plan — how `assistant.ts`'s auto-trigger tells
27
27
  * `draftTurn` what to seed a *fresh* draft with (ignored on every later revision of an
28
28
  * already-drafting plan). `templateName` set means a template matched — the draft starts from
29
29
  * that template's own skeleton (today's personalization behavior, reused, not discarded — see
@@ -3,7 +3,7 @@ import { AskQuestion } from '@buildaharness/harness';
3
3
  import { DecomposedTaskSpec } from './decomposition-classifier.js';
4
4
  import { PlanTaskRecord } from './plan-store.js';
5
5
  /**
6
- * P1 of plans/ask_question_and_plan_mode_plan.html — the "plan-drafting revision call" P1's
6
+ * P1 of the internal plan — the "plan-drafting revision call" P1's
7
7
  * routing (assistant.ts's runTurn, PlanDraftingService) sends every message to while
8
8
  * `planMode.active`. Deliberately narrower than P3's eventual version: no tools at all (P3 adds
9
9
  * read_file/list_directory grounding for a from-scratch draft — see Section &amp; non-goals),
@@ -18,7 +18,7 @@ export interface PlanDraftTurn {
18
18
  successCriteria: string;
19
19
  rationale: string;
20
20
  /**
21
- * P2 of plans/ask_question_and_plan_mode_plan.html — true only once the model judges the user
21
+ * P2 of the internal plan — true only once the model judges the user
22
22
  * has clearly signaled they're satisfied with the current draft and want to proceed, at which
23
23
  * point PlanDraftingService hands off to PlanApprovalService instead of returning another plain
24
24
  * drafting reply. Defaults false on any malformed/missing value (see parsing below) — a
@@ -26,7 +26,7 @@ export interface PlanDraftTurn {
26
26
  */
27
27
  readyForApproval: boolean;
28
28
  /**
29
- * P8 of plans/ask_question_and_plan_mode_plan.html — set only when this revision hit a genuine
29
+ * P8 of the internal plan — set only when this revision hit a genuine
30
30
  * ambiguity worth pausing on instead of guessing (an ambiguous scope boundary, or a
31
31
  * MEDIUM/HIGH-risk branch point with more than one viable approach) — e.g. "which of these two
32
32
  * frameworks should the plan target?" A drafting call that sets this always also leaves
@@ -48,7 +48,7 @@ export interface PlanDraftTurn {
48
48
  */
49
49
  export declare function draftPlanRevision(llmClient: ILLMClient, userMessage: string, currentTasks: PlanTaskRecord[], currentSuccessCriteria: string, currentRationale: string, model?: string, onUsage?: (usage: TokenUsage) => void,
50
50
  /**
51
- * P3 of plans/ask_question_and_plan_mode_plan.html — grounding text for a from-scratch (no
51
+ * P3 of the internal plan — grounding text for a from-scratch (no
52
52
  * template match) draft, gathered from a bounded read_file/list_directory investigation walk
53
53
  * (see AgentLoop.runSupervisorInvestigation, reused rather than building a new search engine —
54
54
  * Scope & non-goals) so a code-shaped request's draft can reference real repo state instead of
@@ -3,7 +3,7 @@
3
3
  * unit-testable in isolation, mirroring one-loop-flag.ts/ask-mode-flag.ts's exact shape
4
4
  * (module-level default, an injectable resolver so tests never touch real process.env).
5
5
  *
6
- * P11 of plans/ask_question_and_plan_mode_plan.html — the rollout flag gating P1-P8's whole
6
+ * P11 of the internal plan — the rollout flag gating P1-P8's whole
7
7
  * plan-mode behavior change (exclusive drafting, mandatory approval, the generalized trigger,
8
8
  * auto-advance, the markdown/delegate/trust-mode extensions) behind one switch. 'legacy' (the
9
9
  * default, for the whole rollout window, per `DEFAULT_PLAN_MODE`): P3's judgment-based
@@ -3,7 +3,7 @@ import { AssistantTurnResult } from './assistant-types.js';
3
3
  import { TraceEvent } from './trace-events.js';
4
4
  import { AgentLoop } from './agent-loop.js';
5
5
  /**
6
- * P9 of plans/ask_question_and_plan_mode_plan.html — the lightweight plan-sketch delegate: one
6
+ * P9 of the internal plan — the lightweight plan-sketch delegate: one
7
7
  * bounded LLM call (grounded, best-effort, via the same read_file/list_directory investigation
8
8
  * walk P3's from-scratch drafting reuses — AgentLoop.runSupervisorInvestigation, not a new search
9
9
  * engine) that returns a plain proposed task list in the chat reply itself. Deliberately NOT
@@ -1,6 +1,6 @@
1
1
  import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
2
  /**
3
- * P9 of plans/ask_question_and_plan_mode_plan.html — the lightweight plan-sketch delegate's one
3
+ * P9 of the internal plan — the lightweight plan-sketch delegate's one
4
4
  * bounded LLM call. Deliberately the cheapest possible shape: a single plain-text reply the
5
5
  * caller shows directly in the chat, not a structured task list staged anywhere — there is no
6
6
  * PlanRecord, no PlanTaskRecord, nothing for P2's approval gate to see (INV-35). Distinct from
@@ -113,7 +113,7 @@ export declare function loadPlanRecord(memory: MemoryAdapter, sessionId: string,
113
113
  export declare function loadActivePlan(memory: MemoryAdapter, sessionId: string, fsPersistence?: PlanFsPersistence): Promise<PlanRecord | null>;
114
114
  /**
115
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
116
+ * the internal plan, TurnInterpreter.resolveTasks called this the instant
117
117
  * classifyTurnIntent matched a template; P3 retired that call site — a template match (or the
118
118
  * newer general needsMultiStepPlan judgment) now always routes through plan mode's exclusive
119
119
  * drafting+approval loop instead (see assistant.ts's auto-trigger and
@@ -227,6 +227,6 @@ export declare function computePlanPosition(plan: PlanRecord, taskGraphTasks: {
227
227
  }[]): PlanPosition | null;
228
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
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. */
230
+ /** Human-readable plan status — mirrors the Python planner agent's format_plan_progress, for callers (the CLI) that want text instead of the structured planStatus field. */
231
231
  export declare function formatPlanProgress(plan: PlanRecord): string;
232
232
  export {};
@@ -20,7 +20,7 @@ export declare function listTemplateNames(): string[];
20
20
  /**
21
21
  * Heuristic: match description keywords to a template name. Always returns a
22
22
  * template — falls back to 'problem_solving' on zero keyword hits, exactly like
23
- * agents/planner/utils.py's pick_template_for_task. Used by buildPlanFromTemplate
23
+ * the Python planner agent's pick_template_for_task. Used by buildPlanFromTemplate
24
24
  * once a template has already been chosen some other way (e.g. matchTemplateIfConfident).
25
25
  */
26
26
  export declare function pickTemplateForTask(description: string): string;
@@ -1,7 +1,7 @@
1
1
  import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
2
  import { PlanTaskRecord } from './plan-store.js';
3
3
  /**
4
- * P6 of plans/ask_question_and_plan_mode_plan.html — runs between drafting (P1/P3) and staging
4
+ * P6 of the internal plan — runs between drafting (P1/P3) and staging
5
5
  * for approval (P2). Two independent checks, deliberately ordered cheapest-and-most-certain
6
6
  * first: (1) dependency-graph validity (pure, synchronous, no LLM call — the same three checks
7
7
  * `adapter/harness/task_graph.py`'s `validate_task_graph` runs Python-side: orphaned `depends_on`