zob-harness 0.10.0 → 0.12.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 (34) hide show
  1. package/.pi/capabilities/zob-public-runtime-capabilities.json +49 -1
  2. package/.pi/extensions/zob-harness/index.ts +7 -1
  3. package/.pi/extensions/zob-harness/src/core/constants.ts +7 -4
  4. package/.pi/extensions/zob-harness/src/domains/context/context-discovery.ts +83 -22
  5. package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/formatting.ts +5 -2
  6. package/.pi/extensions/zob-harness/src/domains/plan/plan-todos.ts +661 -0
  7. package/.pi/extensions/zob-harness/src/runtime/commands/misc.ts +50 -1
  8. package/.pi/extensions/zob-harness/src/runtime/commands/plans.ts +93 -0
  9. package/.pi/extensions/zob-harness/src/runtime/commands.ts +3 -0
  10. package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +6 -2
  11. package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +29 -1
  12. package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +5 -3
  13. package/.pi/extensions/zob-harness/src/runtime/events.ts +97 -3
  14. package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +203 -11
  15. package/.pi/extensions/zob-harness/src/runtime/plan-launch.ts +166 -0
  16. package/.pi/extensions/zob-harness/src/runtime/schemas.ts +1 -0
  17. package/.pi/extensions/zob-harness/src/runtime/state.ts +3 -0
  18. package/.pi/extensions/zob-harness/src/runtime/stop-restore.ts +144 -0
  19. package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +3 -3
  20. package/.pi/extensions/zob-harness/src/runtime/tools-delegation/helpers.ts +23 -3
  21. package/.pi/extensions/zob-harness/src/runtime/tools-delegation/register.ts +34 -11
  22. package/.pi/extensions/zob-harness/src/runtime/tools-plan.ts +70 -0
  23. package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +3 -0
  24. package/.pi/extensions/zob-harness/src/types.ts +1 -0
  25. package/.pi/skills/zob-coms-v2-live/SKILL.md +5 -2
  26. package/.pi/skills/zob-context-discovery/SKILL.md +1 -1
  27. package/.pi/skills/zob-goal-todo-tree/SKILL.md +11 -10
  28. package/.pi/skills/zob-harness/SKILL.md +5 -2
  29. package/.pi/skills/zob-tool-router/SKILL.md +5 -1
  30. package/.pi/skills/zob-zagent-creator/SKILL.md +22 -6
  31. package/AGENTS.md +3 -1
  32. package/package.json +1 -1
  33. package/scripts/context-discovery/shared.mjs +48 -14
  34. package/scripts/context-discovery/smoke.mjs +38 -5
@@ -321,6 +321,25 @@
321
321
  ],
322
322
  "noShipNotes": "Imports chain report TODOs into goal state; no source edits."
323
323
  },
324
+ {
325
+ "name": "zob_plan_launch",
326
+ "family": "plan",
327
+ "modes": [
328
+ "plan",
329
+ "implement",
330
+ "factory",
331
+ "orchestrator"
332
+ ],
333
+ "skillRefs": [
334
+ ".pi/skills/zob-harness/SKILL.md",
335
+ ".pi/skills/zob-goal-todo-tree/SKILL.md"
336
+ ],
337
+ "docRefs": [
338
+ ".pi/extensions/zob-harness/src/AGENTS.md",
339
+ "AGENTS.md"
340
+ ],
341
+ "noShipNotes": "Launches a saved plan TODO sidecar into runtime goal/TODO metadata; does not edit source files and must not recreate TODOs from prose. Blocks on active-goal/relaunch conflicts unless explicitly gated."
342
+ },
324
343
  {
325
344
  "name": "create_goal",
326
345
  "family": "goal",
@@ -1029,7 +1048,7 @@
1029
1048
  "README.md",
1030
1049
  "scripts/README.md"
1031
1050
  ],
1032
- "noShipNotes": "Bounded repo-local context discovery only; for exploratory/natural-language repo discovery start with zob_context_search/ColGREP when installed/ready; if the native tool is unavailable but bash is available, use compact npm run --silent zob:context:query before rg/grep; otherwise use grep/find/read fallback. Never auto-install ColGREP, run installer/network/package-manager commands, broad-grep .pi without pruning .pi/sessions and .pi/agent-sessions, read forbidden/secret/session/vendor/build paths, persist raw secret/session bodies, inject stale/global/unbounded prompt context, or treat broad search hits as exact proof without grep/read/file-ref verification. Oracle/no-ship review must check freshness, citation coverage, and forbidden-source violations."
1051
+ "noShipNotes": "Bounded repo-local context discovery only; for exploratory/natural-language repo discovery start with zob_context_search/ColGREP when installed/ready and reuse it at context pivot points (new subsystem/domain, ambiguous file area, fallback_status suggesting narrower paths, repeated low-signal grep/find, unfamiliar code before edits, or unknown validation/test failure); if the native tool is unavailable but bash is available, use compact npm run --silent zob:context:query before rg/grep; otherwise use grep/find/read fallback. Never auto-install ColGREP, run installer/network/package-manager commands, broad-grep .pi without pruning .pi/sessions and .pi/agent-sessions, read forbidden/secret/session/vendor/build paths, persist raw secret/session bodies, inject stale/global/unbounded prompt context, or treat broad search hits as exact proof without grep/read/file-ref verification; grep/read remain the exact-proof path after semantic discovery and when exact identifiers/paths are already known. Oracle/no-ship review must check freshness, citation coverage, and forbidden-source violations."
1033
1052
  },
1034
1053
  {
1035
1054
  "name": "zob_context_readiness",
@@ -1850,6 +1869,35 @@
1850
1869
  "AGENTS.md"
1851
1870
  ],
1852
1871
  "noShipNotes": "Lists specialist agents only."
1872
+ },
1873
+ {
1874
+ "name": "plan",
1875
+ "family": "plan",
1876
+ "modes": [
1877
+ "all"
1878
+ ],
1879
+ "skillRefs": [
1880
+ ".pi/skills/zob-harness/SKILL.md",
1881
+ ".pi/skills/zob-goal-todo-tree/SKILL.md"
1882
+ ],
1883
+ "docRefs": [
1884
+ "AGENTS.md"
1885
+ ],
1886
+ "noShipNotes": "Inspects or launches saved plan TODO sidecars; launch mutates runtime goal/TODO metadata only."
1887
+ },
1888
+ {
1889
+ "name": "plans",
1890
+ "family": "plan",
1891
+ "modes": [
1892
+ "all"
1893
+ ],
1894
+ "skillRefs": [
1895
+ ".pi/skills/zob-harness/SKILL.md"
1896
+ ],
1897
+ "docRefs": [
1898
+ "AGENTS.md"
1899
+ ],
1900
+ "noShipNotes": "Lists captured plan artifacts and launchability metadata only."
1853
1901
  }
1854
1902
  ]
1855
1903
  }
@@ -43,11 +43,17 @@ export { DEFAULT_GOAL_ACTIVATION_MODE, clearRuntimeGoalContinuationState, clearR
43
43
  export type { GoalActivationMode, RuntimeGoal, RuntimeGoalStatus, RuntimeGoalOracleStatus, RuntimeGoalOracleVerdict } from "./src/runtime/goal-runtime.js";
44
44
  export { extractModeIntent, looksLikeCompletePlanResponse, stripModeIntentMarkup, validateModeIntent } from "./src/runtime/mode-intent.js";
45
45
  export type { ZobModeIntent, ZobModeIntentConfidence, ZobModeIntentRisk, ZobModeIntentValidation } from "./src/runtime/mode-intent.js";
46
- export { capturePlanArtifact, extractPlanTitle, shouldCapturePlanResponse } from "./src/runtime/plan-capture.js";
46
+ export { capturePlanArtifact, extractPlanTitle, listCapturedPlanEntries, shouldCapturePlanResponse, updateCapturedPlanEntry } from "./src/runtime/plan-capture.js";
47
47
  export type { PlanCaptureInput, PlanCaptureResult, PlanIndexEntry } from "./src/runtime/plan-capture.js";
48
+ export { launchCapturedPlan, previewCapturedPlanLaunch, resolveCapturedPlanForLaunch } from "./src/runtime/plan-launch.js";
49
+ export type { PlanLaunchInput, PlanLaunchResult, PlanLaunchSelector } from "./src/runtime/plan-launch.js";
50
+ export { PLAN_TODOS_BLOCK_END, PLAN_TODOS_BLOCK_START, PLAN_TODOS_CANONICAL_SCHEMA, PLAN_TODOS_DISPLAY_CARD_END, PLAN_TODOS_DISPLAY_CARD_START, PLAN_TODOS_INPUT_SCHEMA, PLAN_TODOS_SIDECAR_SCHEMA, buildPlanTodoSidecar, canonicalManifestHash, compileMarkdownPlanTodoManifest, extractAndNormalizePlanTodoManifest, extractPlanTodosJson, formatPlanTodoManifestDisplayCard, formatPlanTodoManifestTree, normalizePlanTodoManifest, planTodoSidecarRelativePath, readPlanTodoSidecar, redactPlanTodosBlockForDisplay, validatePlanTodoSidecar, writePlanTodoSidecar } from "./src/domains/plan/plan-todos.js";
51
+ export type { PlanLaunchStatus, PlanTodoCanonicalItem, PlanTodoCanonicalManifest, PlanTodoDisplayCardOptions, PlanTodoDisplayRedactionResult, PlanTodoManifestQuality, PlanTodoManifestResult, PlanTodoManifestSource, PlanTodoSidecar } from "./src/domains/plan/plan-todos.js";
48
52
  export { ZOB_COMPACTION_CONTINUITY_CONTRACT, ZOB_TOOL_ROUTING_CONTRACT } from "./src/core/constants.js";
49
53
  export { ZOB_COMPACTION_DETAILS_SCHEMA, ZOB_COMPACTION_ENTRY_TYPE, ZOB_COMPACTION_HARD_CAP_TOKENS, ZOB_COMPACTION_LEDGER_SCHEMA, ZOB_COMPACTION_SUMMARY_SCHEMA, ZOB_COMPACTION_TARGET_TOKENS, buildDeterministicZobCompactionResult, buildDeterministicZobCompactionSummary, buildZobCompactionDetails, buildZobCompactionInstructions, buildZobCompactionLedgerEntry, buildZobCompactionStateCapsule, withZobCompactionDetails, zobCompactionBodyFreeViolations } from "./src/runtime/compaction-policy.js";
50
54
  export type { ZobCompactionDetails, ZobCompactionFileRefsInput, ZobCompactionInstructionInput, ZobCompactionLedgerEntry, ZobCompactionStateCapsule } from "./src/runtime/compaction-policy.js";
55
+ export { assistantMessageHasVisibleOutput, createStopRestoreCandidate, findStopRestoreUserEntryId, markStopRestoreAssistantMessage, markStopRestoreRestored, markStopRestoreToolVisible, shouldRestoreStopPrompt } from "./src/runtime/stop-restore.js";
56
+ export type { StopRestoreCandidate, StopRestoreCandidateInput, StopRestoreDecision, StopRestoreDecisionInput, StopRestoreRewindResult } from "./src/runtime/stop-restore.js";
51
57
  export { isAdaptiveZmodeAlias, renderAdaptiveZmodeTemplate, resolveAdaptiveZmodeEntrypoint, validateAdaptiveZmodeEntrypoint } from "./src/runtime/adaptive-zmode.js";
52
58
  export type { AdaptiveZmodeAlias, AdaptiveZmodeEntrypoint } from "./src/runtime/adaptive-zmode.js";
53
59
  export {
@@ -63,17 +63,18 @@ export const ZOB_COMPUTE_READ_TOOLS = ["zob_compute_preview", "zob_compute_resol
63
63
  export const ZOB_COMPUTE_REPORT_TOOLS = ["zob_compute_write_profile_reports"] as const;
64
64
  export const ZOB_PROJECT_DNA_READ_TOOLS = ["zob_project_dna_readiness", "zob_project_dna_plan_workflow", "zob_project_dna_query", "zob_project_dna_federated_query"] as const;
65
65
  export const ZOB_PROJECT_DNA_PROPOSAL_TOOLS = ["zob_project_dna_writeback_proposal"] as const;
66
+ export const ZOB_PLAN_LAUNCH_TOOLS = ["zob_plan_launch"] as const;
66
67
  export const ZOB_RUNTIME_GOAL_TOOLS = ["get_goal", "get_goal_todos", "add_goal_todo", "add_goal_todos", "update_goal_todo", "resolve_goal_todo", "complete_goal_todo", "block_goal_todo", "split_goal_todo", "validate_goal_todo_claim", "accept_goal_todo_claim", "reject_goal_todo_claim", "import_factory_todos", "import_orchestration_todos", "import_chain_todos", "create_goal", "resume_goal", "propose_goal_completion", "record_goal_oracle", "update_goal"] as const;
67
68
  export const ZOB_AUTONOMOUS_READ_TOOLS = ["zob_autonomous_validate_run", "zob_autonomous_validate_smoke"] as const;
68
69
  export const ZOB_AUTONOMOUS_FACTORY_TOOLS = ["zob_autonomous_dry_run", "zob_autonomous_readonly_smoke"] as const;
69
70
 
70
71
  export const MODE_TOOLS: Record<ModeName, string[]> = {
71
72
  explore: ["read", "grep", "find", "ls", "bash", "delegate_agent", "delegate_task", "zob_coms_list", "zob_coms_get", "zob_coms_await", "zpeer_ask", "zob_goal_room_list", "zob_workspace_claims_list", "zob_worker_pool_status", "zob_merge_queue_list", ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_AUTONOMOUS_READ_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS],
72
- plan: ["read", "grep", "find", "ls", "delegate_agent", "delegate_task", "orchestrate_run", "chain_run", ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS, ...ZOB_PROJECT_DNA_PROPOSAL_TOOLS],
73
- implement: ["read", "bash", "edit", "write", "grep", "find", "ls", "delegate_agent", "delegate_task", ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS, ...ZOB_PROJECT_DNA_PROPOSAL_TOOLS],
73
+ plan: ["read", "grep", "find", "ls", "delegate_agent", "delegate_task", "orchestrate_run", "chain_run", ...ZOB_PLAN_LAUNCH_TOOLS, ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS, ...ZOB_PROJECT_DNA_PROPOSAL_TOOLS],
74
+ implement: ["read", "bash", "edit", "write", "grep", "find", "ls", "delegate_agent", "delegate_task", ...ZOB_PLAN_LAUNCH_TOOLS, ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS, ...ZOB_PROJECT_DNA_PROPOSAL_TOOLS],
74
75
  oracle: ["read", "grep", "find", "ls", "bash", "delegate_agent", "delegate_task", "zob_coms_list", "zob_coms_get", "zob_coms_await", "zpeer_ask", "zob_goal_room_list", "zob_workspace_claims_list", "zob_worker_pool_status", "zob_merge_queue_list", ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_AUTONOMOUS_READ_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS],
75
- orchestrator: ["read", "grep", "find", "ls", "delegate_agent", "delegate_task", "orchestrate_run", "chain_run", ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS],
76
- factory: ["read", "bash", "edit", "write", "grep", "find", "ls", "delegate_agent", "delegate_task", "orchestrate_run", "factory_run", "factory_quarantine_review", "factory_quarantine_activate", "factory_quarantine_verify_activation", "chain_run", ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_AUTONOMOUS_READ_TOOLS, ...ZOB_AUTONOMOUS_FACTORY_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS, ...ZOB_PROJECT_DNA_PROPOSAL_TOOLS],
76
+ orchestrator: ["read", "grep", "find", "ls", "delegate_agent", "delegate_task", "orchestrate_run", "chain_run", ...ZOB_PLAN_LAUNCH_TOOLS, ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS],
77
+ factory: ["read", "bash", "edit", "write", "grep", "find", "ls", "delegate_agent", "delegate_task", "orchestrate_run", "factory_run", "factory_quarantine_review", "factory_quarantine_activate", "factory_quarantine_verify_activation", "chain_run", ...ZOB_PLAN_LAUNCH_TOOLS, ...ZOB_RUNTIME_GOAL_TOOLS, ...ZOB_DELEGATION_READ_TOOLS, ...ZOB_ZCOMMIT_TOOLS, ...ZOB_ZAGENT_TOOLS, ...ZOB_AUTONOMOUS_READ_TOOLS, ...ZOB_AUTONOMOUS_FACTORY_TOOLS, ...ZOB_COMS_TOOLS, ...ZOB_GOAL_ROOM_TOOLS, ...ZOB_GOVERNED_REQUEST_TOOLS, ...ZOB_WORKSPACE_CLAIM_TOOLS, ...ZOB_WORKER_POOL_TOOLS, ...ZOB_MERGE_QUEUE_TOOLS, ...ZOB_MISSION_CONTROL_READ_TOOLS, ...ZOB_MISSION_CONTROL_PROPOSAL_TOOLS, ...ZOB_CONTEXT_READ_TOOLS, ...ZOB_CONTEXT_PROPOSAL_TOOLS, ...ZOB_COMPUTE_READ_TOOLS, ...ZOB_COMPUTE_REPORT_TOOLS, ...ZOB_PROJECT_DNA_READ_TOOLS, ...ZOB_PROJECT_DNA_PROPOSAL_TOOLS],
77
78
  // Vanilla is handled specially by applyMode: all currently available Pi tools are enabled.
78
79
  vanilla: [],
79
80
  };
@@ -88,6 +89,8 @@ export const MODE_PROMPTS: Record<ModeName, string> = {
88
89
  - No edits, no commits, no broad test runs.
89
90
  - For non-trivial or tool-ambiguous plans, apply the ZOB tool-routing contract and state which families are in/out before detailed steps.
90
91
  - Produce scope table, likely files, TDD sequence, validation ladder, atomic commit plan, risks, and stop conditions.
92
+ - For complete implementation plans, include exactly one machine-readable TODO manifest block at the end: <!-- ZOB_PLAN_TODOS_START --> + fenced JSON schema zob.plan-todos.v1 + <!-- ZOB_PLAN_TODOS_END -->; keep it simple (objective, todos, key, title, done_when, checks, children) so zob_plan_launch can launch without the LLM recreating TODOs from prose.
93
+ - Treat launchability as part of plan quality: if the explicit TODO block is omitted, capture falls back to deterministic Markdown list parsing and may record fallback warnings or needs_manifest; do not rely on prose-only plans when you can emit the block.
91
94
  - Consume prior explore outputs before re-reading. Avoid duplicate discovery.
92
95
  - Single-plan rule: if the immediately previous assistant response already provided a complete plan for the same request, do not restate it; summarize that the plan is already provided and offer to refine, save, or implement it.
93
96
  - If auto-mode switched to plan after a short handoff, produce the deferred plan once and do not request plan mode again.`,
@@ -14,6 +14,7 @@ export interface ContextDiscoveryConfig {
14
14
  maxResults: number;
15
15
  maxContextLines: number;
16
16
  maxFileBytes: number;
17
+ colgrepTimeoutMs: number;
17
18
  };
18
19
  promptInjection: {
19
20
  enabled: boolean;
@@ -48,11 +49,14 @@ const DEFAULT_CONFIG: ContextDiscoveryConfig = {
48
49
  fallbackProvider: "grep",
49
50
  includePaths: [".pi/extensions", ".pi/skills", ".pi/capabilities", "scripts", "docs", "README.md", "AGENTS.md"],
50
51
  excludePaths: [".env", "**/.env", ".env.*", "**/*secret*", "**/*key*", "*.pem", ".pi/sessions", ".pi/agent-sessions", "node_modules", "dist", "build"],
51
- limits: { maxResults: 6, maxContextLines: 1, maxFileBytes: 1024 * 1024 },
52
+ limits: { maxResults: 6, maxContextLines: 1, maxFileBytes: 1024 * 1024, colgrepTimeoutMs: 8_000 },
52
53
  promptInjection: { enabled: true, includeInstallHint: true },
53
54
  loadedFrom: "defaults",
54
55
  };
55
56
  const TEXT_EXTENSIONS = new Set(["", ".cjs", ".css", ".js", ".json", ".md", ".mjs", ".sh", ".ts", ".tsx", ".txt", ".yaml", ".yml"]);
57
+ const COLGREP_DEFAULT_TIMEOUT_MS = 8_000;
58
+ const COLGREP_MAX_OUTPUT_BYTES = 1024 * 1024;
59
+ const FALLBACK_CANDIDATE_BUDGET = 500;
56
60
 
57
61
  function clampInteger(value: unknown, fallback: number, min: number, max: number): number {
58
62
  const numberValue = typeof value === "number" ? value : Number(value);
@@ -64,6 +68,35 @@ function shellQuote(value: string): string {
64
68
  return `'${String(value).replaceAll("'", "'\\''")}'`;
65
69
  }
66
70
 
71
+ function escapeRegExpLiteral(value: string): string {
72
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
73
+ }
74
+
75
+ function extractFallbackSearchTerms(query: string): string[] {
76
+ const rawTerms = query.toLowerCase().match(/[\p{L}\p{N}_-]+/gu) ?? [];
77
+ const usefulTerms = rawTerms.filter((term) => term.length >= 3 || /\d/u.test(term) || term.includes("_") || term.includes("-"));
78
+ return [...new Set(usefulTerms)].sort((left, right) => right.length - left.length || left.localeCompare(right)).slice(0, 12);
79
+ }
80
+
81
+ function fallbackLineScore(lineLower: string, queryLower: string, terms: string[]): number {
82
+ let score = queryLower.length > 0 && lineLower.includes(queryLower) ? 100 : 0;
83
+ for (const term of terms) {
84
+ if (lineLower.includes(term)) score += Math.min(10, term.length);
85
+ }
86
+ return score;
87
+ }
88
+
89
+ function getColgrepTimeoutMs(config: ContextDiscoveryConfig): number {
90
+ return clampInteger(process.env.ZOB_CONTEXT_COLGREP_TIMEOUT_MS ?? config.limits.colgrepTimeoutMs, COLGREP_DEFAULT_TIMEOUT_MS, 500, 30_000);
91
+ }
92
+
93
+ function buildFallbackVerificationCommand(query: string, roots: string[], searchTerms: string[], mode: ContextSearchMode, pattern?: string): string {
94
+ const rootArgs = roots.map(shellQuote).join(" ") || ".";
95
+ if (mode === "regex") return `grep -R -n -E ${shellQuote(pattern ?? query)} ${rootArgs}`;
96
+ const grepPattern = searchTerms.length > 0 ? searchTerms.slice(0, 10).map(escapeRegExpLiteral).join("|") : escapeRegExpLiteral(query);
97
+ return `grep -R -n -E ${shellQuote(grepPattern)} ${rootArgs}`;
98
+ }
99
+
67
100
  function globToRegExp(pattern: string): RegExp {
68
101
  const escaped = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
69
102
  return new RegExp(`^${escaped}$`, "iu");
@@ -144,10 +177,10 @@ export function buildActiveSearchBackendPromptSnippet(repoRoot: string): string
144
177
  const detection = detectColgrep(repoRoot);
145
178
  const scope = `${config.loadedFrom}; roots=${config.includePaths.slice(0, 6).join(",")}; excludes=${config.excludePaths.length}`;
146
179
  if (detection.ready) {
147
- return `\n\nZOB ACTIVE SEARCH BACKEND\n- active search backend: colgrep\n- prompt injection: enabled by ${scope}; bounded per turn from current repo config, not a global/stale context pack.\n- For exploratory, natural-language, or "where is this mechanism?" repo discovery, start with zob_context_search/ColGREP before grep/find.\n- If zob_context_search is not listed in your available tools but bash is available, run npm run --silent zob:context:query -- --query "<natural language query>" --max-results 6 --max-context-lines 1 before rg/grep; the wrapper returns compact refs by default.\n- Do not conclude the native tool is unavailable and immediately use broad rg/grep; use the wrapper above as the ColGREP path.\n- Run one exploratory context search, then read the returned refs; retry only if results=0 or clearly irrelevant.\n- Use grep/read after semantic discovery for exact proof, known identifiers, final citations, and line refs.\n- Never run broad grep/find over .pi unless .pi/sessions and .pi/agent-sessions are explicitly excluded/pruned.\n- Search output must stay bounded and avoid forbidden paths/secrets.`;
180
+ return `\n\nZOB ACTIVE SEARCH BACKEND\n- active search backend: colgrep\n- prompt injection: enabled by ${scope}; bounded per turn from current repo config, not a global/stale context pack.\n- For exploratory, natural-language, or "where is this mechanism?" repo discovery, start with zob_context_search/ColGREP before grep/find.\n- If zob_context_search is not listed in your available tools but bash is available, run npm run --silent zob:context:query -- --query "<natural language query>" --max-results 6 --max-context-lines 1 before rg/grep; the wrapper returns compact refs by default.\n- Do not conclude the native tool is unavailable and immediately use broad rg/grep; use the wrapper above as the ColGREP path.\n- Reuse zob_context_search/ColGREP at context pivot points: new subsystem/domain, ambiguous or broad file area, fallback_status suggesting narrower paths, repeated low-signal grep/find, unfamiliar code before edits, or unknown validation/test failure.\n- Use grep/read after semantic discovery for exact proof, known identifiers, final citations, and line refs; use grep/read directly when exact identifiers or paths are already known.\n- Never run broad grep/find over .pi unless .pi/sessions and .pi/agent-sessions are explicitly excluded/pruned.\n- Search output must stay bounded and avoid forbidden paths/secrets.`;
148
181
  }
149
182
  const installHint = config.promptInjection.includeInstallHint ? `\n- Optional ColGREP setup hint: ${detection.guidance}` : "";
150
- return `\n\nZOB ACTIVE SEARCH BACKEND\n- active search backend: grep fallback\n- prompt injection: enabled by ${scope}; bounded per turn from current repo config, not a global/stale context pack.\n- Prefer zob_context_search when listed; if not listed and bash is available, npm run --silent zob:context:query -- --query "<query>" still gives the same bounded compact fallback path.${installHint}\n- Missing ColGREP is not a blocker; do not auto-install it.\n- Never run broad grep/find over .pi unless .pi/sessions and .pi/agent-sessions are explicitly excluded/pruned.`;
183
+ return `\n\nZOB ACTIVE SEARCH BACKEND\n- active search backend: grep fallback\n- prompt injection: enabled by ${scope}; bounded per turn from current repo config, not a global/stale context pack.\n- Prefer zob_context_search when listed; if not listed and bash is available, npm run --silent zob:context:query -- --query "<query>" still gives the same bounded compact fallback path.${installHint}\n- Reuse the active context path at pivots such as new subsystem/domain, ambiguous file area, fallback_status suggesting narrower paths, repeated low-signal grep/find, unfamiliar code before edits, or unknown validation/test failure.\n- Missing ColGREP is not a blocker; do not auto-install it.\n- Use grep/read for exact proof and when exact identifiers or paths are already known; never run broad grep/find over .pi unless .pi/sessions and .pi/agent-sessions are explicitly excluded/pruned.`;
151
184
  }
152
185
 
153
186
  function safeSearchRoots(repoRoot: string, config: ContextDiscoveryConfig, requestedPaths?: string[]): { roots: string[]; rejected: string[] } {
@@ -211,12 +244,9 @@ function extractJsonResults(repoRoot: string, stdout: string, config: ContextDis
211
244
  }
212
245
  }
213
246
 
214
- const COLGREP_TIMEOUT_MS = 30_000;
215
- const COLGREP_MAX_OUTPUT_BYTES = 1024 * 1024;
216
-
217
247
  type ColgrepRunResult = { ok: boolean; results: NormalizedContextResult[]; error?: string };
218
248
 
219
- async function runColgrep(repoRoot: string, query: string, roots: string[], config: ContextDiscoveryConfig, maxResults: number, maxContextLines: number): Promise<ColgrepRunResult> {
249
+ async function runColgrep(repoRoot: string, query: string, roots: string[], config: ContextDiscoveryConfig, maxResults: number, maxContextLines: number, timeoutMs: number): Promise<ColgrepRunResult> {
220
250
  const args = ["--json", "-k", String(maxResults), "-n", String(maxContextLines), query, ...roots];
221
251
  return await new Promise<ColgrepRunResult>((resolve) => {
222
252
  let stdout = "";
@@ -231,8 +261,8 @@ async function runColgrep(repoRoot: string, query: string, roots: string[], conf
231
261
  };
232
262
  const timer = setTimeout(() => {
233
263
  child.kill("SIGTERM");
234
- finish({ ok: false, results: [], error: `colgrep timed out after ${String(COLGREP_TIMEOUT_MS)}ms; retry with narrower paths or max_results.` });
235
- }, COLGREP_TIMEOUT_MS);
264
+ finish({ ok: false, results: [], error: `ColGREP exceeded ${String(timeoutMs)}ms; bounded grep fallback used. Retry with narrower paths for semantic ranking.` });
265
+ }, timeoutMs);
236
266
  child.stdout.setEncoding("utf8");
237
267
  child.stderr.setEncoding("utf8");
238
268
  child.stdout.on("data", (chunk: string) => {
@@ -254,40 +284,62 @@ async function runColgrep(repoRoot: string, query: string, roots: string[], conf
254
284
  });
255
285
  }
256
286
 
257
- function fallbackSearch(repoRoot: string, params: Required<Pick<ContextSearchParams, "query" | "mode">> & Pick<ContextSearchParams, "pattern" | "paths">, config: ContextDiscoveryConfig, maxResults: number, maxContextLines: number): { results: NormalizedContextResult[]; rejectedPaths: string[]; roots: string[] } {
287
+ function fallbackSearch(repoRoot: string, params: Required<Pick<ContextSearchParams, "query" | "mode">> & Pick<ContextSearchParams, "pattern" | "paths">, config: ContextDiscoveryConfig, maxResults: number, maxContextLines: number): { results: NormalizedContextResult[]; rejectedPaths: string[]; roots: string[]; searchTerms: string[] } {
258
288
  const { roots, rejected } = safeSearchRoots(repoRoot, config, params.paths);
259
289
  const files: string[] = [];
260
290
  for (const root of roots) collectFiles(repoRoot, root, config, files);
261
291
  const needle = params.mode === "regex" ? params.pattern ?? params.query : params.query;
292
+ const queryLower = params.query.toLowerCase();
293
+ const searchTerms = params.mode === "regex" ? [] : extractFallbackSearchTerms(params.query);
262
294
  let regex: RegExp | undefined;
263
295
  if (params.mode === "regex") {
264
296
  try { regex = new RegExp(needle, "iu"); } catch { regex = undefined; }
265
297
  }
266
298
  const results: NormalizedContextResult[] = [];
299
+ const rankedResults: NormalizedContextResult[] = [];
300
+ const pushRankedResult = (result: NormalizedContextResult): void => {
301
+ rankedResults.push(result);
302
+ if (rankedResults.length > FALLBACK_CANDIDATE_BUDGET) {
303
+ rankedResults.sort((left, right) => (right.score ?? 0) - (left.score ?? 0) || left.ref.localeCompare(right.ref));
304
+ rankedResults.length = FALLBACK_CANDIDATE_BUDGET;
305
+ }
306
+ };
267
307
  for (const path of [...new Set(files)].sort()) {
268
- if (results.length >= maxResults) break;
308
+ if ((params.mode === "files" || params.mode === "regex") && results.length >= maxResults) break;
269
309
  if (params.mode === "files") {
270
- if (!path.toLowerCase().includes(params.query.toLowerCase())) continue;
310
+ if (!path.toLowerCase().includes(queryLower)) continue;
271
311
  results.push({ path, ref: path, preview: path });
272
312
  continue;
273
313
  }
274
314
  const lines = readFileSync(join(repoRoot, path), "utf8").split(/\r?\n/u);
275
315
  for (let index = 0; index < lines.length; index += 1) {
276
- const matched = regex ? regex.test(lines[index]) : lines[index].toLowerCase().includes(params.query.toLowerCase());
316
+ const lineText = lines[index];
317
+ const score = regex ? undefined : fallbackLineScore(lineText.toLowerCase(), queryLower, searchTerms);
318
+ const matched = regex ? regex.test(lineText) : (score ?? 0) > 0;
277
319
  if (!matched) continue;
278
320
  const start = Math.max(0, index - maxContextLines);
279
321
  const end = Math.min(lines.length, index + maxContextLines + 1);
280
- results.push({
322
+ const result = {
281
323
  path,
282
324
  line: index + 1,
283
325
  ref: `${path}:${index + 1}`,
284
- preview: lines[index].trim().slice(0, 240),
326
+ preview: lineText.trim().slice(0, 240),
285
327
  context: lines.slice(start, end).map((text, offset) => ({ line: start + offset + 1, text: text.slice(0, 240) })),
286
- });
287
- if (results.length >= maxResults) break;
328
+ score,
329
+ } satisfies NormalizedContextResult;
330
+ if (regex) {
331
+ results.push(result);
332
+ if (results.length >= maxResults) break;
333
+ } else {
334
+ pushRankedResult(result);
335
+ }
288
336
  }
289
337
  }
290
- return { results, rejectedPaths: rejected, roots };
338
+ if (params.mode !== "files" && params.mode !== "regex") {
339
+ rankedResults.sort((left, right) => (right.score ?? 0) - (left.score ?? 0) || left.ref.localeCompare(right.ref));
340
+ results.push(...rankedResults.slice(0, maxResults));
341
+ }
342
+ return { results, rejectedPaths: rejected, roots, searchTerms };
291
343
  }
292
344
 
293
345
  export async function runContextSearch(repoRoot: string, input: ContextSearchParams): Promise<Record<string, unknown>> {
@@ -296,14 +348,16 @@ export async function runContextSearch(repoRoot: string, input: ContextSearchPar
296
348
  const mode = input.mode ?? "auto";
297
349
  const maxResults = clampInteger(input.max_results, config.limits.maxResults, 1, 50);
298
350
  const maxContextLines = clampInteger(input.max_context_lines, config.limits.maxContextLines, 0, 5);
351
+ const colgrepTimeoutMs = getColgrepTimeoutMs(config);
299
352
  const detection = detectColgrep(repoRoot);
300
353
  const { roots, rejected } = safeSearchRoots(repoRoot, config, input.paths);
301
354
  let provider = detection.ready ? "colgrep" : "grep-fallback";
302
355
  let fallback = !detection.ready;
303
356
  let fallbackReason = detection.ready ? undefined : detection.guidance;
357
+ let fallbackSearchTerms: string[] = [];
304
358
  let results: NormalizedContextResult[] = [];
305
359
  if (detection.ready && mode !== "regex" && mode !== "files") {
306
- const colgrep = await runColgrep(repoRoot, query, roots, config, maxResults, maxContextLines);
360
+ const colgrep = await runColgrep(repoRoot, query, roots, config, maxResults, maxContextLines, colgrepTimeoutMs);
307
361
  if (colgrep.ok) results = colgrep.results;
308
362
  else {
309
363
  provider = "grep-fallback";
@@ -316,8 +370,14 @@ export async function runContextSearch(repoRoot: string, input: ContextSearchPar
316
370
  fallbackReason = `${mode} mode uses exact grep/find fallback for deterministic results.`;
317
371
  }
318
372
  if (fallback || results.length === 0) {
373
+ if (!fallback) {
374
+ provider = "grep-fallback";
375
+ fallback = true;
376
+ fallbackReason = "ColGREP returned no compact refs; tokenized grep fallback used.";
377
+ }
319
378
  const fallbackResult = fallbackSearch(repoRoot, { query, mode, pattern: input.pattern, paths: input.paths }, config, maxResults, maxContextLines);
320
379
  results = fallbackResult.results;
380
+ fallbackSearchTerms = fallbackResult.searchTerms;
321
381
  }
322
382
  const refs = results.map((item) => item.ref);
323
383
  return {
@@ -334,10 +394,11 @@ export async function runContextSearch(repoRoot: string, input: ContextSearchPar
334
394
  results,
335
395
  searchedRoots: roots,
336
396
  rejectedPaths: rejected,
337
- limits: { maxResults, maxContextLines },
397
+ limits: { maxResults, maxContextLines, colgrepTimeoutMs },
398
+ fallbackSearchTerms,
338
399
  recommendedVerification: refs.length > 0
339
- ? [`read ${results[0]?.path ?? roots[0] ?? "."}`, "After reading, grep exact identifiers/strings found in returned refs for final proof."]
340
- : [`grep -R -n ${shellQuote(query)} ${roots.map(shellQuote).join(" ") || "."}`, "find relevant safe paths, then read exact files"],
400
+ ? [`read ${results[0]?.path ?? roots[0] ?? "."}`, "Then grep exact identifiers/strings for proof; rerun zob_context_search at context pivots or when fallback_status suggests narrower paths."]
401
+ : [buildFallbackVerificationCommand(query, roots, fallbackSearchTerms, mode, input.pattern), "If low-signal or ambiguous, rerun zob_context_search with narrower paths/query, then read exact files."],
341
402
  safety: { repoRelativeOnly: true, forbiddenPathsExcluded: config.excludePaths, rawPromptOrConversationPersisted: false, autoInstall: false },
342
403
  };
343
404
  }
@@ -19,8 +19,11 @@ export function summarizeGoalTodos(todoState: GoalTodoState, goalId?: string): G
19
19
  const validationFailed = nodes.filter((node) => node.validation?.status === "failed" || node.validation?.status === "blocked" || node.validation?.status === "warn").length;
20
20
  const needsUser = nodes.filter((node) => node.status === "needs_user").length;
21
21
  const needsOracle = nodes.filter((node) => node.status === "needs_oracle").length;
22
- const nextAgent = nodes.find((node) => node.owner === "agent" && (node.status === "ready" || node.status === "planned" || node.status === "in_progress"));
23
- const nextUser = nodes.find((node) => ACTIONABLE_STATUSES.has(node.status) && (node.owner === "user" || node.status === "needs_user"));
22
+ const hasOpenChildren = (node: GoalTodoNode): boolean => nodes.some((candidate) => candidate.parentId === node.id && OPEN_REQUIRED_STATUSES.has(candidate.status));
23
+ const agentCandidates = nodes.filter((node) => node.owner === "agent" && (node.status === "ready" || node.status === "planned" || node.status === "in_progress"));
24
+ const nextAgent = agentCandidates.find((node) => !hasOpenChildren(node)) ?? agentCandidates[0];
25
+ const userCandidates = nodes.filter((node) => ACTIONABLE_STATUSES.has(node.status) && (node.owner === "user" || node.status === "needs_user"));
26
+ const nextUser = userCandidates.find((node) => !hasOpenChildren(node)) ?? userCandidates[0];
24
27
  return {
25
28
  goalId,
26
29
  total: nodes.length,