zob-harness 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/capabilities/zob-public-runtime-capabilities.json +1 -1
- package/.pi/context-discovery.json +2 -2
- package/.pi/extensions/zob-harness/src/core/constants.ts +1 -1
- package/.pi/extensions/zob-harness/src/domains/context/context-discovery.ts +61 -17
- package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +6 -3
- package/.pi/skills/zob-context-discovery/SKILL.md +17 -8
- package/.pi/skills/zob-harness/SKILL.md +1 -1
- package/.pi/skills/zob-tool-router/SKILL.md +1 -1
- package/AGENTS.md +1 -1
- package/README.md +2 -2
- package/package.json +1 -1
- package/scripts/README.md +1 -1
- package/scripts/context-discovery/query.mjs +4 -13
- package/scripts/context-discovery/shared.mjs +76 -5
- package/scripts/context-discovery/smoke.mjs +57 -0
|
@@ -1030,7 +1030,7 @@
|
|
|
1030
1030
|
"README.md",
|
|
1031
1031
|
"scripts/README.md"
|
|
1032
1032
|
],
|
|
1033
|
-
"noShipNotes": "Bounded repo-local context discovery only;
|
|
1033
|
+
"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."
|
|
1034
1034
|
},
|
|
1035
1035
|
{
|
|
1036
1036
|
"name": "zob_context_readiness",
|
|
@@ -56,7 +56,7 @@ export const ZOB_ZCOMMIT_TOOLS = ["zob_zcommit_run"] as const;
|
|
|
56
56
|
export const ZOB_DELEGATION_READ_TOOLS = ["zob_delegation_catalog", "get_delegation_run", "await_delegation_run"] as const;
|
|
57
57
|
export const ZOB_MISSION_CONTROL_READ_TOOLS = ["zob_coms_readiness", "zob_mission_control_snapshot"] as const;
|
|
58
58
|
export const ZOB_MISSION_CONTROL_PROPOSAL_TOOLS = ["zob_mission_control_propose_command"] as const;
|
|
59
|
-
export const ZOB_CONTEXT_READ_TOOLS = ["zob_context_readiness", "zob_context_validate_scope"] as const;
|
|
59
|
+
export const ZOB_CONTEXT_READ_TOOLS = ["zob_context_search", "zob_context_readiness", "zob_context_validate_scope"] as const;
|
|
60
60
|
export const ZOB_CONTEXT_PROPOSAL_TOOLS = ["zob_context_writeback_proposal"] as const;
|
|
61
61
|
export const ZOB_COMPUTE_READ_TOOLS = ["zob_compute_preview", "zob_compute_resolve_profile", "zob_compute_plan_workflow", "zob_compute_validate_profile"] as const;
|
|
62
62
|
export const ZOB_COMPUTE_REPORT_TOOLS = ["zob_compute_write_profile_reports"] as const;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { basename, extname, isAbsolute, join, normalize, sep } from "node:path";
|
|
3
|
+
import { basename, extname, isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
4
4
|
|
|
5
5
|
export type ContextSearchMode = "auto" | "semantic" | "hybrid" | "regex" | "files";
|
|
6
6
|
|
|
@@ -48,7 +48,7 @@ const DEFAULT_CONFIG: ContextDiscoveryConfig = {
|
|
|
48
48
|
fallbackProvider: "grep",
|
|
49
49
|
includePaths: [".pi/extensions", ".pi/skills", ".pi/capabilities", "scripts", "docs", "README.md", "AGENTS.md"],
|
|
50
50
|
excludePaths: [".env", "**/.env", ".env.*", "**/*secret*", "**/*key*", "*.pem", ".pi/sessions", ".pi/agent-sessions", "node_modules", "dist", "build"],
|
|
51
|
-
limits: { maxResults:
|
|
51
|
+
limits: { maxResults: 6, maxContextLines: 1, maxFileBytes: 1024 * 1024 },
|
|
52
52
|
promptInjection: { enabled: true, includeInstallHint: true },
|
|
53
53
|
loadedFrom: "defaults",
|
|
54
54
|
};
|
|
@@ -78,6 +78,14 @@ export function normalizeRepoPath(raw: unknown): string | undefined {
|
|
|
78
78
|
return normalized.split(sep).join("/");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function normalizeBackendPath(repoRoot: string, raw: unknown): string | undefined {
|
|
82
|
+
if (typeof raw !== "string" || raw.trim().length === 0 || raw.includes("\0")) return undefined;
|
|
83
|
+
if (!isAbsolute(raw)) return normalizeRepoPath(raw);
|
|
84
|
+
const relPath = relative(repoRoot, raw);
|
|
85
|
+
if (!relPath || relPath === ".." || relPath.startsWith(`..${sep}`) || isAbsolute(relPath)) return undefined;
|
|
86
|
+
return normalizeRepoPath(relPath);
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
function pathIsExcluded(relPath: string, excludePaths: string[]): boolean {
|
|
82
90
|
const normalized = normalizeRepoPath(relPath);
|
|
83
91
|
if (!normalized) return true;
|
|
@@ -136,10 +144,10 @@ export function buildActiveSearchBackendPromptSnippet(repoRoot: string): string
|
|
|
136
144
|
const detection = detectColgrep(repoRoot);
|
|
137
145
|
const scope = `${config.loadedFrom}; roots=${config.includePaths.slice(0, 6).join(",")}; excludes=${config.excludePaths.length}`;
|
|
138
146
|
if (detection.ready) {
|
|
139
|
-
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-
|
|
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.`;
|
|
140
148
|
}
|
|
141
149
|
const installHint = config.promptInjection.includeInstallHint ? `\n- Optional ColGREP setup hint: ${detection.guidance}` : "";
|
|
142
|
-
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
|
|
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.`;
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
function safeSearchRoots(repoRoot: string, config: ContextDiscoveryConfig, requestedPaths?: string[]): { roots: string[]; rejected: string[] } {
|
|
@@ -180,12 +188,13 @@ function collectFiles(repoRoot: string, relPath: string, config: ContextDiscover
|
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
function normalizeLineResult(repoRoot: string, item: Record<string, unknown>, config: ContextDiscoveryConfig): NormalizedContextResult | undefined {
|
|
183
|
-
const
|
|
184
|
-
const
|
|
191
|
+
const unit = typeof item.unit === "object" && item.unit !== null ? item.unit as Record<string, unknown> : undefined;
|
|
192
|
+
const rawPath = item.path ?? item.file ?? item.filename ?? item.source_path ?? unit?.path ?? unit?.file ?? unit?.filename ?? unit?.source_path;
|
|
193
|
+
const path = normalizeBackendPath(repoRoot, rawPath);
|
|
185
194
|
if (!path || pathIsExcluded(path, config.excludePaths) || !existsSync(join(repoRoot, path))) return undefined;
|
|
186
|
-
const lineValue = item.line ?? item.lineNumber ?? item.line_number ?? item.start_line;
|
|
195
|
+
const lineValue = item.line ?? item.lineNumber ?? item.line_number ?? item.start_line ?? unit?.line ?? unit?.lineNumber ?? unit?.line_number ?? unit?.start_line;
|
|
187
196
|
const line = typeof lineValue === "number" ? Math.max(1, Math.floor(lineValue)) : undefined;
|
|
188
|
-
const rawPreview = item.preview ?? item.text ?? item.lineText ?? item.content ?? item.match ?? "";
|
|
197
|
+
const rawPreview = item.preview ?? item.text ?? item.lineText ?? item.content ?? item.match ?? unit?.preview ?? unit?.text ?? unit?.lineText ?? unit?.content ?? unit?.code ?? unit?.docstring ?? unit?.signature ?? unit?.qualified_name ?? unit?.name ?? "";
|
|
189
198
|
const preview = String(rawPreview).replace(/\s+/gu, " ").trim().slice(0, 240);
|
|
190
199
|
const score = typeof item.score === "number" ? item.score : undefined;
|
|
191
200
|
return { path, line, ref: line ? `${path}:${line}` : path, preview, score };
|
|
@@ -202,12 +211,47 @@ function extractJsonResults(repoRoot: string, stdout: string, config: ContextDis
|
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
213
|
|
|
205
|
-
|
|
214
|
+
const COLGREP_TIMEOUT_MS = 30_000;
|
|
215
|
+
const COLGREP_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
216
|
+
|
|
217
|
+
type ColgrepRunResult = { ok: boolean; results: NormalizedContextResult[]; error?: string };
|
|
218
|
+
|
|
219
|
+
async function runColgrep(repoRoot: string, query: string, roots: string[], config: ContextDiscoveryConfig, maxResults: number, maxContextLines: number): Promise<ColgrepRunResult> {
|
|
206
220
|
const args = ["--json", "-k", String(maxResults), "-n", String(maxContextLines), query, ...roots];
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
return await new Promise<ColgrepRunResult>((resolve) => {
|
|
222
|
+
let stdout = "";
|
|
223
|
+
let stderr = "";
|
|
224
|
+
let settled = false;
|
|
225
|
+
const child = spawn("colgrep", args, { cwd: repoRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
226
|
+
const finish = (value: ColgrepRunResult): void => {
|
|
227
|
+
if (settled) return;
|
|
228
|
+
settled = true;
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
resolve(value);
|
|
231
|
+
};
|
|
232
|
+
const timer = setTimeout(() => {
|
|
233
|
+
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);
|
|
236
|
+
child.stdout.setEncoding("utf8");
|
|
237
|
+
child.stderr.setEncoding("utf8");
|
|
238
|
+
child.stdout.on("data", (chunk: string) => {
|
|
239
|
+
stdout += chunk;
|
|
240
|
+
if (stdout.length > COLGREP_MAX_OUTPUT_BYTES) {
|
|
241
|
+
child.kill("SIGTERM");
|
|
242
|
+
finish({ ok: false, results: [], error: "colgrep output exceeded compact result budget; retry with lower max_results." });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
child.stderr.on("data", (chunk: string) => { stderr += chunk; });
|
|
246
|
+
child.on("error", (error) => finish({ ok: false, results: [], error: error.message.slice(0, 240) }));
|
|
247
|
+
child.on("close", (code) => {
|
|
248
|
+
if (code !== 0) {
|
|
249
|
+
finish({ ok: false, results: [], error: stderr.trim().slice(0, 240) || `colgrep exited ${String(code)}` });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
finish({ ok: true, results: extractJsonResults(repoRoot, stdout, config, maxResults) });
|
|
253
|
+
});
|
|
254
|
+
});
|
|
211
255
|
}
|
|
212
256
|
|
|
213
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[] } {
|
|
@@ -246,7 +290,7 @@ function fallbackSearch(repoRoot: string, params: Required<Pick<ContextSearchPar
|
|
|
246
290
|
return { results, rejectedPaths: rejected, roots };
|
|
247
291
|
}
|
|
248
292
|
|
|
249
|
-
export function runContextSearch(repoRoot: string, input: ContextSearchParams): Record<string, unknown
|
|
293
|
+
export async function runContextSearch(repoRoot: string, input: ContextSearchParams): Promise<Record<string, unknown>> {
|
|
250
294
|
const config = loadContextDiscoveryConfig(repoRoot);
|
|
251
295
|
const query = String(input.query ?? "").trim();
|
|
252
296
|
const mode = input.mode ?? "auto";
|
|
@@ -259,7 +303,7 @@ export function runContextSearch(repoRoot: string, input: ContextSearchParams):
|
|
|
259
303
|
let fallbackReason = detection.ready ? undefined : detection.guidance;
|
|
260
304
|
let results: NormalizedContextResult[] = [];
|
|
261
305
|
if (detection.ready && mode !== "regex" && mode !== "files") {
|
|
262
|
-
const colgrep = runColgrep(repoRoot, query, roots, config, maxResults, maxContextLines);
|
|
306
|
+
const colgrep = await runColgrep(repoRoot, query, roots, config, maxResults, maxContextLines);
|
|
263
307
|
if (colgrep.ok) results = colgrep.results;
|
|
264
308
|
else {
|
|
265
309
|
provider = "grep-fallback";
|
|
@@ -292,7 +336,7 @@ export function runContextSearch(repoRoot: string, input: ContextSearchParams):
|
|
|
292
336
|
rejectedPaths: rejected,
|
|
293
337
|
limits: { maxResults, maxContextLines },
|
|
294
338
|
recommendedVerification: refs.length > 0
|
|
295
|
-
? [`
|
|
339
|
+
? [`read ${results[0]?.path ?? roots[0] ?? "."}`, "After reading, grep exact identifiers/strings found in returned refs for final proof."]
|
|
296
340
|
: [`grep -R -n ${shellQuote(query)} ${roots.map(shellQuote).join(" ") || "."}`, "find relevant safe paths, then read exact files"],
|
|
297
341
|
safety: { repoRelativeOnly: true, forbiddenPathsExcluded: config.excludePaths, rawPromptOrConversationPersisted: false, autoInstall: false },
|
|
298
342
|
};
|
|
@@ -30,16 +30,19 @@ export function registerContextTools(pi: ExtensionAPI): void {
|
|
|
30
30
|
name: "zob_context_search",
|
|
31
31
|
label: "ZOB Context Search",
|
|
32
32
|
description: "Search bounded repo-local context through the active context backend. Prefers ColGREP when installed and ready, falls back to safe grep/find-style search, never auto-installs ColGREP, and excludes forbidden/session/vendor/build paths.",
|
|
33
|
-
promptSnippet: "
|
|
33
|
+
promptSnippet: "For exploratory/natural-language repo discovery, call zob_context_search before broad grep/find; if this tool is not exposed but bash is available, use compact npm run --silent zob:context:query before rg/grep; verify exact proof with grep/read on returned refs.",
|
|
34
34
|
promptGuidelines: [
|
|
35
|
-
"Call zob_context_search for
|
|
35
|
+
"Call zob_context_search first for exploratory/natural-language codebase discovery when semantic or broad search is useful.",
|
|
36
36
|
"zob_context_search prefers ColGREP when ready and falls back to bounded grep when ColGREP is missing, not ready, or a query fails.",
|
|
37
|
+
"If zob_context_search is not available in the current toolset, use the compact local wrapper: npm run --silent zob:context:query -- --query \"<query>\" --max-results 6 --max-context-lines 1 before broad rg/grep.",
|
|
37
38
|
"Never install ColGREP from this tool path; missing ColGREP is not a blocker.",
|
|
39
|
+
"Run one exploratory context search, then read returned refs; retry only when results are empty or clearly irrelevant.",
|
|
40
|
+
"Never run broad grep/find over .pi unless .pi/sessions and .pi/agent-sessions are explicitly excluded/pruned.",
|
|
38
41
|
"Use returned refs as leads and verify final claims with exact read/grep evidence.",
|
|
39
42
|
],
|
|
40
43
|
parameters: ContextSearchParams,
|
|
41
44
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
42
|
-
const result = runContextSearch(ctx.cwd, params);
|
|
45
|
+
const result = await runContextSearch(ctx.cwd, params);
|
|
43
46
|
return { content: [{ type: "text", text: formatContextSearchResult(result) }], details: result };
|
|
44
47
|
},
|
|
45
48
|
});
|
|
@@ -15,13 +15,17 @@ Use this skill for:
|
|
|
15
15
|
## Active backend rules
|
|
16
16
|
|
|
17
17
|
1. Prefer `zob_context_search` for repo-local discovery when the runtime tool is available.
|
|
18
|
-
2. When ColGREP is installed and ready,
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
21
|
-
5.
|
|
22
|
-
6.
|
|
23
|
-
7.
|
|
24
|
-
8.
|
|
18
|
+
2. When ColGREP is installed and ready, exploratory or natural-language repo discovery MUST start with `zob_context_search`/ColGREP before broad grep/find. This includes prompts like “explore this repo”, “where is this mechanism?”, or “find the design/flow”.
|
|
19
|
+
3. If `zob_context_search` is not exposed in the current session/toolset but `bash` is available, use the compact local wrapper before broad grep: `npm run --silent zob:context:query -- --query "<natural language query>" --max-results 6 --max-context-lines 1`.
|
|
20
|
+
4. Do not treat missing native-tool exposure as permission to immediately use broad `rg`/`grep`; the wrapper is the ColGREP path for those sessions.
|
|
21
|
+
5. Run one exploratory context search, then read returned refs; retry only when results are empty or clearly irrelevant.
|
|
22
|
+
6. Use grep/find/read after semantic discovery for exact proof, known identifiers/strings, final citations, and line refs. If the exact identifier/path/string is already known, grep/read may be used directly.
|
|
23
|
+
7. When ColGREP is missing, unavailable, or not indexed, fall back to grep/find/read. Missing ColGREP is not a blocker for normal ZOB work.
|
|
24
|
+
8. Do not auto-install ColGREP, run network/package-manager installer commands, or mutate user tooling without explicit owner approval.
|
|
25
|
+
9. Keep search bounded to repo-local allowed paths and task-relevant globs.
|
|
26
|
+
10. Never run broad grep/find over `.pi` unless `.pi/sessions` and `.pi/agent-sessions` are explicitly excluded/pruned.
|
|
27
|
+
11. Never read forbidden paths or secret-like files, including `.env`, `**/.env`, `**/*secret*`, `**/*key*`, private keys, `.pi/sessions`, `.pi/agent-sessions`, `node_modules`, `dist`, or `build`.
|
|
28
|
+
12. Persist only safe metadata/artifact refs for context packs. Do not persist raw secret/session bodies.
|
|
25
29
|
|
|
26
30
|
## User setup and scripts
|
|
27
31
|
|
|
@@ -34,7 +38,12 @@ Use this skill for:
|
|
|
34
38
|
|
|
35
39
|
- Active-backend prompt injection is controlled by `.pi/context-discovery.json` under `promptInjection.enabled`.
|
|
36
40
|
- The injected block must stay concise, current-repo scoped, and bounded by the configured include/exclude roots; it is a discovery hint, not a context pack or evidence source.
|
|
37
|
-
- Do not inject stale/global context or raw search results into the prompt. Use `zob_context_search`
|
|
41
|
+
- Do not inject stale/global context or raw search results into the prompt. Use `zob_context_search` or the compact `npm run --silent zob:context:query` wrapper, then read exact files when details are needed.
|
|
42
|
+
|
|
43
|
+
## UX / performance posture
|
|
44
|
+
|
|
45
|
+
- Native `zob_context_search` execution must remain async/non-blocking from the Pi extension perspective; avoid synchronous long-running ColGREP calls in tool handlers.
|
|
46
|
+
- Default discovery output should be compact: a small set of refs/previews, then read exact files. Do not dump raw ColGREP JSON or code bodies into the chat by default.
|
|
38
47
|
|
|
39
48
|
## Evidence expectations
|
|
40
49
|
|
|
@@ -34,7 +34,7 @@ Communication is a core deliverable. Prefer one parent-visible control room by d
|
|
|
34
34
|
1. Classify the task as one of: `explore`, `plan`, `implement`, `oracle`, `factory`, `orchestrator`. When `.pi/routing/intent-classifier.json` enables an optional model classifier, treat it as advisory intent routing only; regex fallback and deterministic safety hard-blocks remain authoritative. `autoSwitchIntents` controls which detected intents switch mode directly; this project enables all ZOB modes by default. Use `/intent-classifier status|regex|model-strict|model-fallback|test` (alias `/intent`) to switch/test routing without editing JSON by hand.
|
|
35
35
|
2. For non-trivial or tool-ambiguous work, apply `zob-tool-router`: classify applicable families, then use/delegate/skip each with a reason.
|
|
36
36
|
3. Use `orchestrator` when the task needs Chief Vision coordination, multi-agent decomposition, Lead/Worker routing, goal/TODO graph governance, or parent-owned dispatch; the root should delegate substantive work rather than do it directly.
|
|
37
|
-
4. Check `.pi/capabilities/zob-public-runtime-capabilities.json` for the relevant tool/command family, mode allowlist, skill refs, and no-ship notes. For context tasks,
|
|
37
|
+
4. Check `.pi/capabilities/zob-public-runtime-capabilities.json` for the relevant tool/command family, mode allowlist, skill refs, and no-ship notes. For exploratory/natural-language context tasks, start with the active backend via `zob_context_search`; use ColGREP when ready before broad grep/find. If `zob_context_search` is not exposed but bash is available, run `npm run --silent zob:context:query -- --query "<query>" --max-results 6 --max-context-lines 1` before `rg`/`grep`. Use grep/find/read for fallback plus exact verification. Never broad-grep `.pi` without pruning `.pi/sessions` and `.pi/agent-sessions`.
|
|
38
38
|
5. If broad or risky, use the `delegate_agent` tool before editing.
|
|
39
39
|
6. For delegated work, use the six-part contract:
|
|
40
40
|
- TASK
|
|
@@ -53,7 +53,7 @@ Do not use this skill for a small factual answer when no tools are needed.
|
|
|
53
53
|
| delegation | Specialist review, parallel discovery, implementation handoff, independent QA, or uncertain broad work | `zob_delegation_catalog`, `delegate_task`, `delegate_agent`; skill `zob-delegation-routing` |
|
|
54
54
|
| orchestration | Lead/Worker lanes, Chief Vision coordination, parent-owned dispatch, multi-agent workgraph | `/zmode orchestrator`, `orchestrate_run`, `chain_run`, goal/TODO tools |
|
|
55
55
|
| compute | Complexity, budget, model class, max profile, or multi-agent sizing matters | `zob_compute_preview`, `zob_compute_resolve_profile`, `zob_compute_plan_workflow`, `zob_compute_validate_profile`; skill `zob-compute-profile` |
|
|
56
|
-
| context / ProjectDNA | Need bounded repo/reference context, scan artifacts, cited context packs, or writeback proposals | `zob_context_validate_scope`, `zob_context_readiness`, `zob_project_dna_query`, `zob_project_dna_federated_query`, `zob_project_dna_readiness`;
|
|
56
|
+
| context / ProjectDNA | Need bounded repo/reference context, scan artifacts, cited context packs, or writeback proposals | `zob_context_search`, `zob_context_validate_scope`, `zob_context_readiness`, `zob_project_dna_query`, `zob_project_dna_federated_query`, `zob_project_dna_readiness`; skills `zob-context-discovery`, `zob-project-dna` |
|
|
57
57
|
| factory | Repeated workflow, smoke/pilot/batch gates, manifests, checkpoints, sentinels | `factory_run`, factory quarantine tools, autonomous factory read-only smokes; skill `zob-factory` |
|
|
58
58
|
| coms / goal-room | Parent-visible coordination, live required-local handoff, blockers, TODO claims, status refs | `zob_goal_room_*`, `zob_coms_*`; skills `zob-coms-v2-live`, `zob-coms-safety` |
|
|
59
59
|
| workspace / merge queue | Parallel write intent, sandbox diff review, parent-owned manual apply decisions | `zob_workspace_claim`, `zob_workspace_release`, `zob_merge_candidate_submit`, `zob_merge_queue_decide`; skill `zob-sandbox` |
|
package/AGENTS.md
CHANGED
|
@@ -49,7 +49,7 @@ Use the registry plus the domain skill instead of copying tool docs into prompts
|
|
|
49
49
|
- Delegation/catalog: `zob_delegation_catalog`, `delegate_agent`, `delegate_task` -> `.pi/skills/zob-delegation-routing/SKILL.md` and `.pi/skills/zob-harness/SKILL.md`; call the catalog before first delegation when agent/contract/tool routing is uncertain, normally omit `delegate_task.output_contract` and `delegate_task.required_tools`, and never invent contract IDs or agent tools.
|
|
50
50
|
- Live coms: `zob_coms_*` -> `.pi/skills/zob-coms-v2-live/SKILL.md` and `.pi/skills/zob-coms-safety/SKILL.md`; required-local delivery must be live, never append-only success.
|
|
51
51
|
- Mission Control: `zob_mission_control_*` / readiness -> `.pi/skills/zob-mission-control-coms/SKILL.md`; command writes are proposals only.
|
|
52
|
-
- Context: `zob_context_search`, `zob_context_*` -> registry no-ship notes plus `.pi/skills/zob-context-discovery/SKILL.md`, `.pi/skills/zob-harness/SKILL.md`, and `.pi/skills/zob-spec/SKILL.md` as applicable;
|
|
52
|
+
- Context: `zob_context_search`, `zob_context_*` -> registry no-ship notes plus `.pi/skills/zob-context-discovery/SKILL.md`, `.pi/skills/zob-harness/SKILL.md`, and `.pi/skills/zob-spec/SKILL.md` as applicable; for exploratory/natural-language repo discovery, start with the active search backend (`zob_context_search`/ColGREP when ready, grep/find/read fallback) before broad grep/find; if the native tool is not exposed but bash is available, run the compact wrapper `npm run --silent zob:context:query -- --query "<query>" --max-results 6 --max-context-lines 1` before `rg`/`grep`; then verify exact claims with grep/read/file refs; never broad-grep `.pi` without pruning `.pi/sessions` and `.pi/agent-sessions`.
|
|
53
53
|
- Compute profile / effort routing: `zob_compute_*`, `npm run preview:compute-profile:project-dna-smoke`, `npm run validate:compute-profile:project-dna-smoke` -> `.pi/skills/zob-compute-profile/SKILL.md`; preview/resolve/report tools are metadata-only and never bypass safety, budget, oracle, sandbox, or parent-owned dispatch gates.
|
|
54
54
|
- Autonomy: `zob_autonomous_*` -> `.pi/skills/zob-autonomous-runtime/SKILL.md`; dry-run/readonly smoke/validation are supervised evidence only, not global autonomy completion.
|
|
55
55
|
- Factory quarantine/run: `factory_quarantine_*`, `factory_run` -> `.pi/skills/zob-factory/SKILL.md` and `.pi/skills/zob-sandbox/SKILL.md`.
|
package/README.md
CHANGED
|
@@ -332,7 +332,7 @@ This release adds the handoff runtime/docs and `npm run smoke:goal-todo-handoff`
|
|
|
332
332
|
|
|
333
333
|
### Use active context discovery
|
|
334
334
|
|
|
335
|
-
ZOB can adapt context search to the active backend. Use `zob_context_search` inside Pi when available: it prefers ColGREP for broad/semantic repo discovery when ColGREP is installed and indexed, and falls back to grep/find/read when it is missing or unavailable. Treat broad search hits as leads and verify exact claims with file refs before editing or reporting readiness.
|
|
335
|
+
ZOB can adapt context search to the active backend. Use `zob_context_search` inside Pi when available: it prefers ColGREP for broad/semantic repo discovery when ColGREP is installed and indexed, runs native search asynchronously from the extension handler, and falls back to grep/find/read when it is missing or unavailable. Treat broad search hits as leads and verify exact claims with file refs before editing or reporting readiness.
|
|
336
336
|
|
|
337
337
|
ColGREP setup is optional and owner-driven; ZOB must not auto-install it or run installer/package-manager commands. Local helpers:
|
|
338
338
|
|
|
@@ -343,7 +343,7 @@ npm run zob:context:query -- "goal todo routing" # one-shot query with grep fal
|
|
|
343
343
|
npm run smoke:context-discovery # deterministic fallback smoke; passes without ColGREP
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
-
Forbidden/secret/session/vendor/build paths remain excluded from discovery. Active-backend prompt injection is bounded and configurable through `.pi/context-discovery.json` (`promptInjection.enabled`); it should never inject stale/global context or raw search results. Oracle/no-ship review for this feature checks context freshness, citation coverage, exact grep/read verification, forbidden-source violations, and no unapproved installer/network behavior.
|
|
346
|
+
Forbidden/secret/session/vendor/build paths remain excluded from discovery. Active-backend prompt injection is bounded and configurable through `.pi/context-discovery.json` (`promptInjection.enabled`); it should never inject stale/global context or raw search results. When ColGREP is ready, exploratory/natural-language repo discovery should start with `zob_context_search`/ColGREP before broad grep/find; if the native tool is not exposed but `bash` is available, use the compact wrapper `npm run --silent zob:context:query -- --query "<query>" --max-results 6 --max-context-lines 1` before `rg`/`grep`. Then use grep/read for exact proof and citations. Broad grep/find over `.pi` must explicitly prune `.pi/sessions` and `.pi/agent-sessions`. Oracle/no-ship review for this feature checks context freshness, citation coverage, exact grep/read verification, forbidden-source violations, and no unapproved installer/network behavior.
|
|
347
347
|
|
|
348
348
|
See [`reports/context-discovery/design.md`](reports/context-discovery/design.md), [`.pi/skills/zob-context-discovery/SKILL.md`](.pi/skills/zob-context-discovery/SKILL.md), and [scripts/README.md](scripts/README.md) for the operating rules and script map.
|
|
349
349
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zob-harness",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A governed Agent Factory for Pi: launch communicating agent teams, run tmux-backed factories, validate artifacts, and package repeatable workflows.",
|
|
6
6
|
"license": "MIT",
|
package/scripts/README.md
CHANGED
|
@@ -8,7 +8,7 @@ These script families are intended to be part of the normal tracked repo workflo
|
|
|
8
8
|
|
|
9
9
|
- `scripts/autonomy/` — static/read-only autonomy readiness smokes.
|
|
10
10
|
- `scripts/compute-profile/` — compute profile policy and regression checks.
|
|
11
|
-
- `scripts/context-discovery/` — adaptive active search backend helpers: `npm run zob:context:doctor`, `npm run zob:context:init`, `npm run zob:context:query`, and `npm run smoke:context-discovery`;
|
|
11
|
+
- `scripts/context-discovery/` — adaptive active search backend helpers: `npm run zob:context:doctor`, `npm run zob:context:init`, `npm run zob:context:query`, and `npm run smoke:context-discovery`; starts exploratory/natural-language discovery with ColGREP when installed/ready, gives sessions without native `zob_context_search` a compact wrapper command before `rg`/`grep`, validates grep fallback when absent, and keeps broad grep/find away from `.pi/sessions` / `.pi/agent-sessions`.
|
|
12
12
|
- `scripts/git-ops/` — governed `/zcommit` policy smokes.
|
|
13
13
|
- `scripts/goal-todo/` — Goal/TODO tree compatibility smokes, including `scripts/goal-todo/handoff-static-smoke.mjs` coverage for the Goal TODO ZPeer/ZTeam handoff script (`npm run smoke:goal-todo-handoff`).
|
|
14
14
|
- `scripts/harness-intake/` — natural-language harness setup/session analyzer that produces quarantined ZOB team/factory proposals plus tmux launch support.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { detectColgrep, fallbackSearch, loadConfig, parseArgs, printHumanSearch, printJson } from "./shared.mjs";
|
|
3
|
+
import { detectColgrep, fallbackSearch, loadConfig, normalizeColgrepResults, parseArgs, printHumanSearch, printJson } from "./shared.mjs";
|
|
4
4
|
|
|
5
5
|
const args = parseArgs(process.argv.slice(2));
|
|
6
6
|
const query = args.query ?? args.q ?? args._.join(" ");
|
|
@@ -32,15 +32,9 @@ if (colgrep.ready) {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
if (colgrepResult.status === 0) {
|
|
35
|
-
result = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
query,
|
|
39
|
-
resultCount: undefined,
|
|
40
|
-
raw: colgrepResult.stdout.trim(),
|
|
41
|
-
stderr: colgrepResult.stderr.trim(),
|
|
42
|
-
recommendedVerification: ["Use grep/read on returned repo-relative refs for exact proof."],
|
|
43
|
-
};
|
|
35
|
+
result = normalizeColgrepResults(colgrepResult.stdout, { query, config, maxResults, maxContextLines });
|
|
36
|
+
result.stderr = colgrepResult.stderr.trim().slice(0, 240);
|
|
37
|
+
result.colgrepArgs = colgrepArgs;
|
|
44
38
|
} else {
|
|
45
39
|
result = runFallback("colgrep-query-failed");
|
|
46
40
|
result.colgrepQueryStatus = colgrepResult.status;
|
|
@@ -53,9 +47,6 @@ if (colgrep.ready) {
|
|
|
53
47
|
|
|
54
48
|
if (args.json) {
|
|
55
49
|
printJson(result);
|
|
56
|
-
} else if (result.provider === "colgrep") {
|
|
57
|
-
console.log("provider: colgrep");
|
|
58
|
-
console.log(result.raw);
|
|
59
50
|
} else {
|
|
60
51
|
printHumanSearch(result);
|
|
61
52
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
4
|
-
import { basename,
|
|
4
|
+
import { basename, extname, isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
5
5
|
|
|
6
6
|
export const repoRoot = process.cwd();
|
|
7
7
|
export const configPath = ".pi/context-discovery.json";
|
|
@@ -33,8 +33,8 @@ export const defaultConfig = {
|
|
|
33
33
|
"build",
|
|
34
34
|
],
|
|
35
35
|
limits: {
|
|
36
|
-
maxResults:
|
|
37
|
-
maxContextLines:
|
|
36
|
+
maxResults: 6,
|
|
37
|
+
maxContextLines: 1,
|
|
38
38
|
maxFileBytes: 1024 * 1024,
|
|
39
39
|
},
|
|
40
40
|
promptInjection: {
|
|
@@ -126,13 +126,35 @@ export function parseArgs(argv) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export function normalizeRepoPath(raw) {
|
|
129
|
-
|
|
129
|
+
if (typeof raw !== "string" || raw.trim().length === 0 || raw.includes("\0") || raw.includes("\\")) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const trimmed = raw.trim().replace(/^\.\//u, "");
|
|
133
|
+
if (isAbsolute(trimmed)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const normalized = normalize(trimmed);
|
|
130
137
|
if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith(`..${sep}`)) {
|
|
131
138
|
return null;
|
|
132
139
|
}
|
|
133
140
|
return normalized.split(sep).join("/");
|
|
134
141
|
}
|
|
135
142
|
|
|
143
|
+
export function normalizeBackendPath(raw) {
|
|
144
|
+
if (typeof raw !== "string" || raw.trim().length === 0 || raw.includes("\0")) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const trimmed = raw.trim();
|
|
148
|
+
if (!isAbsolute(trimmed)) {
|
|
149
|
+
return normalizeRepoPath(trimmed);
|
|
150
|
+
}
|
|
151
|
+
const relPath = relative(repoRoot, trimmed);
|
|
152
|
+
if (!relPath || relPath === ".." || relPath.startsWith(`..${sep}`) || isAbsolute(relPath)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return normalizeRepoPath(relPath);
|
|
156
|
+
}
|
|
157
|
+
|
|
136
158
|
function globToRegExp(pattern) {
|
|
137
159
|
const escaped = pattern
|
|
138
160
|
.split("*")
|
|
@@ -141,7 +163,7 @@ function globToRegExp(pattern) {
|
|
|
141
163
|
return new RegExp(`^${escaped}$`, "iu");
|
|
142
164
|
}
|
|
143
165
|
|
|
144
|
-
function isExcluded(relPath, excludePaths) {
|
|
166
|
+
export function isExcluded(relPath, excludePaths) {
|
|
145
167
|
const normalized = normalizeRepoPath(relPath);
|
|
146
168
|
if (!normalized) {
|
|
147
169
|
return true;
|
|
@@ -231,6 +253,51 @@ export function fallbackSearch({ query, config, maxResults, maxContextLines }) {
|
|
|
231
253
|
};
|
|
232
254
|
}
|
|
233
255
|
|
|
256
|
+
export function normalizeColgrepResults(stdout, { query, config, maxResults, maxContextLines }) {
|
|
257
|
+
let parsed;
|
|
258
|
+
try {
|
|
259
|
+
parsed = JSON.parse(stdout);
|
|
260
|
+
} catch {
|
|
261
|
+
parsed = [];
|
|
262
|
+
}
|
|
263
|
+
const candidates = Array.isArray(parsed)
|
|
264
|
+
? parsed
|
|
265
|
+
: parsed && typeof parsed === "object"
|
|
266
|
+
? [parsed.results, parsed.matches, parsed.items].find(Array.isArray) ?? []
|
|
267
|
+
: [];
|
|
268
|
+
const results = [];
|
|
269
|
+
for (const item of candidates) {
|
|
270
|
+
if (!item || typeof item !== "object") continue;
|
|
271
|
+
const unit = item.unit && typeof item.unit === "object" ? item.unit : {};
|
|
272
|
+
const path = normalizeBackendPath(item.path ?? item.file ?? item.filename ?? item.source_path ?? unit.path ?? unit.file ?? unit.filename ?? unit.source_path);
|
|
273
|
+
if (!path || isExcluded(path, config.excludePaths) || !existsSync(join(repoRoot, path))) continue;
|
|
274
|
+
const lineValue = item.line ?? item.lineNumber ?? item.line_number ?? item.start_line ?? unit.line ?? unit.lineNumber ?? unit.line_number ?? unit.start_line;
|
|
275
|
+
const line = Number.isFinite(Number(lineValue)) ? Math.max(1, Math.floor(Number(lineValue))) : undefined;
|
|
276
|
+
const previewSource = item.preview ?? item.text ?? item.lineText ?? item.match ?? unit.docstring ?? unit.signature ?? unit.qualified_name ?? unit.name ?? path;
|
|
277
|
+
const preview = String(previewSource).replace(/\s+/gu, " ").trim().slice(0, 240);
|
|
278
|
+
results.push({
|
|
279
|
+
path,
|
|
280
|
+
line,
|
|
281
|
+
ref: line ? `${path}:${line}` : path,
|
|
282
|
+
preview,
|
|
283
|
+
score: Number.isFinite(Number(item.score)) ? Number(item.score) : undefined,
|
|
284
|
+
});
|
|
285
|
+
if (results.length >= maxResults) break;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
provider: "colgrep",
|
|
289
|
+
fallback: false,
|
|
290
|
+
query,
|
|
291
|
+
maxResults,
|
|
292
|
+
maxContextLines,
|
|
293
|
+
resultCount: results.length,
|
|
294
|
+
results,
|
|
295
|
+
recommendedVerification: results.length
|
|
296
|
+
? [`read ${results[0].path}`, "After reading, grep exact identifiers/strings found in returned refs for final proof."]
|
|
297
|
+
: ["No compact ColGREP refs parsed; retry with a narrower query or inspect ColGREP status."],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
234
301
|
export function printJson(value) {
|
|
235
302
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
236
303
|
}
|
|
@@ -242,6 +309,10 @@ export function printHumanSearch(result) {
|
|
|
242
309
|
for (const item of result.results ?? []) {
|
|
243
310
|
console.log(`- ${item.ref}: ${item.preview}`);
|
|
244
311
|
}
|
|
312
|
+
const verification = Array.isArray(result.recommendedVerification) ? result.recommendedVerification.slice(0, 2) : [];
|
|
313
|
+
if (verification.length > 0) {
|
|
314
|
+
console.log(`verify: ${verification.join(" ; ")}`);
|
|
315
|
+
}
|
|
245
316
|
}
|
|
246
317
|
|
|
247
318
|
export function repoRelative(path) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
3
4
|
|
|
4
5
|
const env = { ...process.env, ZOB_CONTEXT_FORCE_FALLBACK: "1" };
|
|
5
6
|
const result = spawnSync(process.execPath, ["scripts/context-discovery/query.mjs", "--query", "ZOB Harness", "--json", "--max-results", "5"], {
|
|
@@ -31,6 +32,62 @@ if (parsed.provider !== "grep-fallback" || parsed.fallback !== true || parsed.re
|
|
|
31
32
|
process.exit(1);
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const constantsSource = readFileSync(".pi/extensions/zob-harness/src/core/constants.ts", "utf8");
|
|
36
|
+
const runtimeSource = readFileSync(".pi/extensions/zob-harness/src/domains/context/context-discovery.ts", "utf8");
|
|
37
|
+
const querySource = readFileSync("scripts/context-discovery/query.mjs", "utf8");
|
|
38
|
+
const sharedSource = readFileSync("scripts/context-discovery/shared.mjs", "utf8");
|
|
39
|
+
const requiredPromptFragments = [
|
|
40
|
+
"For exploratory, natural-language",
|
|
41
|
+
"start with zob_context_search/ColGREP before grep/find",
|
|
42
|
+
"npm run --silent zob:context:query -- --query",
|
|
43
|
+
"Do not conclude the native tool is unavailable and immediately use broad rg/grep",
|
|
44
|
+
"Run one exploratory context search, then read the returned refs",
|
|
45
|
+
"Use grep/read after semantic discovery",
|
|
46
|
+
"Never run broad grep/find over .pi unless .pi/sessions and .pi/agent-sessions are explicitly excluded/pruned",
|
|
47
|
+
"unit?.file",
|
|
48
|
+
"normalizeBackendPath(repoRoot, rawPath)",
|
|
49
|
+
];
|
|
50
|
+
const requiredWrapperFragments = [
|
|
51
|
+
"normalizeColgrepResults",
|
|
52
|
+
"printHumanSearch(result)",
|
|
53
|
+
];
|
|
54
|
+
const requiredAsyncRuntimeFragments = [
|
|
55
|
+
"async function runColgrep",
|
|
56
|
+
"spawn(\"colgrep\"",
|
|
57
|
+
"export async function runContextSearch",
|
|
58
|
+
"await runColgrep",
|
|
59
|
+
"COLGREP_TIMEOUT_MS = 30_000",
|
|
60
|
+
];
|
|
61
|
+
const forbiddenWrapperFragments = [
|
|
62
|
+
"console.log(result.raw)",
|
|
63
|
+
"raw: colgrepResult.stdout",
|
|
64
|
+
];
|
|
65
|
+
const missingWrapperFragments = requiredWrapperFragments.filter((fragment) => !querySource.includes(fragment) && !sharedSource.includes(fragment));
|
|
66
|
+
const missingAsyncRuntimeFragments = requiredAsyncRuntimeFragments.filter((fragment) => !runtimeSource.includes(fragment));
|
|
67
|
+
const presentForbiddenWrapperFragments = forbiddenWrapperFragments.filter((fragment) => querySource.includes(fragment));
|
|
68
|
+
const contextReadToolsLine = constantsSource.match(/ZOB_CONTEXT_READ_TOOLS = \[(?<tools>[^\]]+)\]/u)?.groups?.tools ?? "";
|
|
69
|
+
if (!contextReadToolsLine.includes('"zob_context_search"')) {
|
|
70
|
+
console.error("context-discovery smoke FAIL: zob_context_search missing from ZOB_CONTEXT_READ_TOOLS native mode allowlist");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const missingPromptFragments = requiredPromptFragments.filter((fragment) => !runtimeSource.includes(fragment));
|
|
75
|
+
if (missingPromptFragments.length > 0) {
|
|
76
|
+
console.error("context-discovery smoke FAIL: runtime prompt hardening fragments missing");
|
|
77
|
+
console.error(JSON.stringify({ missingPromptFragments }, null, 2));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (missingWrapperFragments.length > 0 || presentForbiddenWrapperFragments.length > 0) {
|
|
81
|
+
console.error("context-discovery smoke FAIL: ColGREP wrapper compact-output contract broken");
|
|
82
|
+
console.error(JSON.stringify({ missingWrapperFragments, presentForbiddenWrapperFragments }, null, 2));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
if (missingAsyncRuntimeFragments.length > 0) {
|
|
86
|
+
console.error("context-discovery smoke FAIL: native ColGREP execution must stay async/non-blocking");
|
|
87
|
+
console.error(JSON.stringify({ missingAsyncRuntimeFragments }, null, 2));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
34
91
|
console.log("context-discovery smoke PASS");
|
|
35
92
|
console.log(`provider=${parsed.provider} reason=${parsed.reason} results=${parsed.resultCount}`);
|
|
36
93
|
console.log(`evidence=${parsed.results.find((entry) => typeof entry.path === "string")?.ref}`);
|