scoutline 0.23.0 → 0.24.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 (47) hide show
  1. package/README.md +49 -1
  2. package/dist/capabilities/investigation.d.ts +134 -0
  3. package/dist/capabilities/investigation.d.ts.map +1 -0
  4. package/dist/capabilities/investigation.js +278 -0
  5. package/dist/capabilities/investigation.js.map +1 -0
  6. package/dist/commands/investigate.d.ts +232 -0
  7. package/dist/commands/investigate.d.ts.map +1 -0
  8. package/dist/commands/investigate.js +838 -0
  9. package/dist/commands/investigate.js.map +1 -0
  10. package/dist/commands/vision.d.ts.map +1 -1
  11. package/dist/commands/vision.js +15 -2
  12. package/dist/commands/vision.js.map +1 -1
  13. package/dist/index.d.ts +21 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +373 -6
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/investigate-claims.d.ts +90 -0
  18. package/dist/lib/investigate-claims.d.ts.map +1 -0
  19. package/dist/lib/investigate-claims.js +188 -0
  20. package/dist/lib/investigate-claims.js.map +1 -0
  21. package/dist/lib/investigate-extract.d.ts +35 -0
  22. package/dist/lib/investigate-extract.d.ts.map +1 -0
  23. package/dist/lib/investigate-extract.js +101 -0
  24. package/dist/lib/investigate-extract.js.map +1 -0
  25. package/dist/lib/investigate-planner.d.ts +58 -0
  26. package/dist/lib/investigate-planner.d.ts.map +1 -0
  27. package/dist/lib/investigate-planner.js +141 -0
  28. package/dist/lib/investigate-planner.js.map +1 -0
  29. package/dist/providers/registry.d.ts.map +1 -1
  30. package/dist/providers/registry.js +4 -1
  31. package/dist/providers/registry.js.map +1 -1
  32. package/dist/providers/types.d.ts +30 -1
  33. package/dist/providers/types.d.ts.map +1 -1
  34. package/dist/providers/types.js.map +1 -1
  35. package/dist/providers/zai/adapter.d.ts.map +1 -1
  36. package/dist/providers/zai/adapter.js +217 -3
  37. package/dist/providers/zai/adapter.js.map +1 -1
  38. package/dist/providers/zai/layout-parsing.d.ts +76 -0
  39. package/dist/providers/zai/layout-parsing.d.ts.map +1 -0
  40. package/dist/providers/zai/layout-parsing.js +151 -0
  41. package/dist/providers/zai/layout-parsing.js.map +1 -0
  42. package/dist/providers/zai/media.d.ts +19 -0
  43. package/dist/providers/zai/media.d.ts.map +1 -1
  44. package/dist/providers/zai/media.js +61 -0
  45. package/dist/providers/zai/media.js.map +1 -1
  46. package/package.json +1 -1
  47. package/skills/scoutline/SKILL.md +71 -1
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Deterministic claim splitting + claim↔evidence matching for
3
+ * `investigate --verify` (investigate-verify lane, Ticket T1; DESIGN
4
+ * D2, PRD AC-2/AC-4/AC-5).
5
+ *
6
+ * splitClaims: the extract grammar's terminator rules verbatim
7
+ * (lib/investigate-extract.ts splitWindows) — `[.!?]` followed by a
8
+ * space (or end of input), or a newline; the terminator char belongs
9
+ * to the window, the separator run (spaces/tabs) to neither. The
10
+ * splitter is PURE: the ≤ 8 claim cap is the CALLER's fail-loud
11
+ * validation (commands/investigate.ts, T3) — a 9-sentence statement
12
+ * returns 9 claims here, never a throw, never a truncation.
13
+ *
14
+ * matchClaimsToEvidence: zero-model, deterministic. Per claim —
15
+ * terms = normalizeTerms over the claim's stopword-filtered tokens
16
+ * (reused from lib/investigate-extract.ts, imports only); a passage
17
+ * matches when any term appears whole-word case-folded in
18
+ * passage.quote; the negation-cue scan runs over MATCHING passages
19
+ * only; evidence pointers land in first-encounter order (source
20
+ * order, then passage order); negationCues counts cue-bearing
21
+ * matching passages.
22
+ *
23
+ * Byte-stable: no clocks, no randomness, no locale-dependent casing
24
+ * beyond toLowerCase.
25
+ */
26
+ import type { EvidenceSource } from "../capabilities/investigation.js";
27
+ /**
28
+ * The verify-mode claim cap (PRD AC-1: > 8 sentence-claims is a
29
+ * fail-loud VALIDATION_ERROR, the R1 pipe-cap precedent — fail, never
30
+ * truncate). Exported so the command's error message names the cap
31
+ * from the single source of truth.
32
+ */
33
+ export declare const MAX_VERIFY_CLAIMS = 8;
34
+ /**
35
+ * Split a statement into sentence-claims: extract-grammar windows,
36
+ * whitespace-only sentences dropped, quotes trimmed. Deterministic —
37
+ * the same statement yields the identical claim array byte-stably.
38
+ * `|` needs no handling: claims split on sentences, never pipes.
39
+ */
40
+ export declare function splitClaims(statement: string): string[];
41
+ /**
42
+ * The negation-cue list behind `contradicted`. FROZEN and versioned:
43
+ * additions change verdicts and are release-visible (the parent-owned
44
+ * CHANGELOG bullet names them). Every cue is a LITERAL whole-word
45
+ * string (fix-round m1: the parenthesized inflection grammar is gone —
46
+ * variants spell out; a missing-absence family covers "without",
47
+ * "lacks", "untrue", "false", "absent", "rarely", "seldom").
48
+ *
49
+ * DISCLOSED as hint-grade: `contradicted` means "a term-matching
50
+ * passage carries a negation cue", NOT semantic contradiction —
51
+ * semantic judgment stays with the calling agent (agent-synthesis
52
+ * contract); the per-claim cue count exists for exactly that
53
+ * re-judgment.
54
+ */
55
+ export declare const NEGATION_CUES: readonly string[];
56
+ /** The deterministic verdict vocabulary (PRD AC-5). */
57
+ export type ClaimVerdict = "corroborated" | "contradicted" | "unresolved";
58
+ /** One evidence pointer into the pack's own sources array. */
59
+ export interface ClaimEvidencePointer {
60
+ readonly sourceIndex: number;
61
+ readonly passageIndex: number;
62
+ }
63
+ /** One claim row: text, verdict, cue count, evidence pointers. */
64
+ export interface ClaimRow {
65
+ readonly text: string;
66
+ readonly verdict: ClaimVerdict;
67
+ /** Number of cue-bearing MATCHING passages (agent re-judgment input). */
68
+ readonly negationCues: number;
69
+ readonly evidence: readonly ClaimEvidencePointer[];
70
+ }
71
+ /** The additive verify block (PRD AC-6): statement + per-claim rows. */
72
+ export interface VerifyBlock {
73
+ readonly statement: string;
74
+ readonly claims: readonly ClaimRow[];
75
+ }
76
+ export interface MatchClaimsArgs {
77
+ readonly statement: string;
78
+ readonly claims: readonly string[];
79
+ readonly sources: readonly EvidenceSource[];
80
+ }
81
+ /**
82
+ * Match claims to evidence: per claim, every (source, passage) whose
83
+ * quote contains ≥ 1 claim term whole-word case-folded becomes an
84
+ * evidence pointer in first-encounter order; the cue scan runs over
85
+ * matching passages only; the verdict follows PRD-5 (corroborated =
86
+ * matches + 0 cues; contradicted = ≥ 1 cue-bearing match; unresolved
87
+ * = no matches).
88
+ */
89
+ export declare function matchClaimsToEvidence({ statement, claims, sources }: MatchClaimsArgs): VerifyBlock;
90
+ //# sourceMappingURL=investigate-claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investigate-claims.d.ts","sourceRoot":"","sources":["../../src/lib/investigate-claims.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAQvE;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAgDnC;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CASvD;AAMD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAgCzC,CAAC;AAQH,uDAAuD;AACvD,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,cAAc,GAAG,YAAY,CAAC;AAE1E,8DAA8D;AAC9D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACpD;AAED,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C;AAuBD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,GAAG,WAAW,CAqBlG"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Deterministic claim splitting + claim↔evidence matching for
3
+ * `investigate --verify` (investigate-verify lane, Ticket T1; DESIGN
4
+ * D2, PRD AC-2/AC-4/AC-5).
5
+ *
6
+ * splitClaims: the extract grammar's terminator rules verbatim
7
+ * (lib/investigate-extract.ts splitWindows) — `[.!?]` followed by a
8
+ * space (or end of input), or a newline; the terminator char belongs
9
+ * to the window, the separator run (spaces/tabs) to neither. The
10
+ * splitter is PURE: the ≤ 8 claim cap is the CALLER's fail-loud
11
+ * validation (commands/investigate.ts, T3) — a 9-sentence statement
12
+ * returns 9 claims here, never a throw, never a truncation.
13
+ *
14
+ * matchClaimsToEvidence: zero-model, deterministic. Per claim —
15
+ * terms = normalizeTerms over the claim's stopword-filtered tokens
16
+ * (reused from lib/investigate-extract.ts, imports only); a passage
17
+ * matches when any term appears whole-word case-folded in
18
+ * passage.quote; the negation-cue scan runs over MATCHING passages
19
+ * only; evidence pointers land in first-encounter order (source
20
+ * order, then passage order); negationCues counts cue-bearing
21
+ * matching passages.
22
+ *
23
+ * Byte-stable: no clocks, no randomness, no locale-dependent casing
24
+ * beyond toLowerCase.
25
+ */
26
+ import { STOPWORDS } from "./context-file.js";
27
+ import { normalizeTerms } from "./investigate-extract.js";
28
+ // ---------------------------------------------------------------------------
29
+ // Claim splitting (extract-grammar terminators)
30
+ // ---------------------------------------------------------------------------
31
+ const STOPWORD_SET = new Set(STOPWORDS);
32
+ /**
33
+ * The verify-mode claim cap (PRD AC-1: > 8 sentence-claims is a
34
+ * fail-loud VALIDATION_ERROR, the R1 pipe-cap precedent — fail, never
35
+ * truncate). Exported so the command's error message names the cap
36
+ * from the single source of truth.
37
+ */
38
+ export const MAX_VERIFY_CLAIMS = 8;
39
+ /**
40
+ * Split input into sentence windows on the extract grammar: `[.!?]`
41
+ * followed by a space (or end of input), plus newline boundaries.
42
+ * Pinned to lib/investigate-extract.ts splitWindows — a decimal like
43
+ * "3.14" has no space after the dot, so it never terminates.
44
+ */
45
+ function splitWindows(content) {
46
+ const windows = [];
47
+ let start = 0;
48
+ for (let i = 0; i < content.length; i += 1) {
49
+ const ch = content.charAt(i);
50
+ const next = content.charAt(i + 1);
51
+ const isTerminator = (ch === "." || ch === "!" || ch === "?") &&
52
+ (i + 1 >= content.length || next === " ");
53
+ const isNewline = ch === "\n";
54
+ if (!isTerminator && !isNewline) {
55
+ continue;
56
+ }
57
+ const end = i + 1; // terminator char owned by the window
58
+ windows.push({ start, end });
59
+ // Skip the horizontal-whitespace separator run; the next window
60
+ // starts at the first character that is neither space nor tab.
61
+ let cursor = end;
62
+ while (cursor < content.length) {
63
+ const sep = content.charAt(cursor);
64
+ if (sep !== " " && sep !== "\t") {
65
+ break;
66
+ }
67
+ cursor += 1;
68
+ }
69
+ start = cursor;
70
+ i = cursor - 1; // loop increment lands on `cursor`
71
+ }
72
+ if (start < content.length) {
73
+ windows.push({ start, end: content.length });
74
+ }
75
+ return windows;
76
+ }
77
+ /**
78
+ * Split a statement into sentence-claims: extract-grammar windows,
79
+ * whitespace-only sentences dropped, quotes trimmed. Deterministic —
80
+ * the same statement yields the identical claim array byte-stably.
81
+ * `|` needs no handling: claims split on sentences, never pipes.
82
+ */
83
+ export function splitClaims(statement) {
84
+ const claims = [];
85
+ for (const window of splitWindows(statement)) {
86
+ const claim = statement.slice(window.start, window.end).trim();
87
+ if (claim.length > 0) {
88
+ claims.push(claim);
89
+ }
90
+ }
91
+ return claims;
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Negation cues (frozen, versioned contract — PRD AC-5)
95
+ // ---------------------------------------------------------------------------
96
+ /**
97
+ * The negation-cue list behind `contradicted`. FROZEN and versioned:
98
+ * additions change verdicts and are release-visible (the parent-owned
99
+ * CHANGELOG bullet names them). Every cue is a LITERAL whole-word
100
+ * string (fix-round m1: the parenthesized inflection grammar is gone —
101
+ * variants spell out; a missing-absence family covers "without",
102
+ * "lacks", "untrue", "false", "absent", "rarely", "seldom").
103
+ *
104
+ * DISCLOSED as hint-grade: `contradicted` means "a term-matching
105
+ * passage carries a negation cue", NOT semantic contradiction —
106
+ * semantic judgment stays with the calling agent (agent-synthesis
107
+ * contract); the per-claim cue count exists for exactly that
108
+ * re-judgment.
109
+ */
110
+ export const NEGATION_CUES = Object.freeze([
111
+ "not",
112
+ "no",
113
+ "never",
114
+ "none",
115
+ "cannot",
116
+ "can't",
117
+ "isn't",
118
+ "aren't",
119
+ "wasn't",
120
+ "weren't",
121
+ "doesn't",
122
+ "don't",
123
+ "didn't",
124
+ "won't",
125
+ "wouldn't",
126
+ "fail to",
127
+ "fails to",
128
+ "failed to",
129
+ "denies",
130
+ "denied",
131
+ "refutes",
132
+ "refuted",
133
+ "disputes",
134
+ "disputed",
135
+ "without",
136
+ "lacks",
137
+ "untrue",
138
+ "false",
139
+ "absent",
140
+ "rarely",
141
+ "seldom",
142
+ ]);
143
+ const CUE_LITERALS = NEGATION_CUES;
144
+ function escapeRegExp(s) {
145
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
146
+ }
147
+ function containsWholeWord(content, term) {
148
+ const re = new RegExp(`(^|[^\\p{L}\\p{N}_])${escapeRegExp(term)}(?:[^\\p{L}\\p{N}_]|$)`, "iu");
149
+ return re.test(content);
150
+ }
151
+ /** Claim terms: lowercase tokens, stopword-filtered, normalizeTerms-dedupe. */
152
+ function claimTerms(claim) {
153
+ const tokens = claim
154
+ .toLowerCase()
155
+ .split(/[^a-z0-9']+/)
156
+ .filter((t) => t.length > 0 && !STOPWORD_SET.has(t));
157
+ return normalizeTerms(tokens);
158
+ }
159
+ /**
160
+ * Match claims to evidence: per claim, every (source, passage) whose
161
+ * quote contains ≥ 1 claim term whole-word case-folded becomes an
162
+ * evidence pointer in first-encounter order; the cue scan runs over
163
+ * matching passages only; the verdict follows PRD-5 (corroborated =
164
+ * matches + 0 cues; contradicted = ≥ 1 cue-bearing match; unresolved
165
+ * = no matches).
166
+ */
167
+ export function matchClaimsToEvidence({ statement, claims, sources }) {
168
+ const rows = claims.map((text) => {
169
+ const terms = claimTerms(text);
170
+ const evidence = [];
171
+ let cueBearers = 0;
172
+ sources.forEach((source, sourceIndex) => {
173
+ source.passages.forEach((passage, passageIndex) => {
174
+ const matched = terms.some((term) => containsWholeWord(passage.quote, term));
175
+ if (!matched)
176
+ return;
177
+ evidence.push({ sourceIndex, passageIndex });
178
+ const carriesCue = CUE_LITERALS.some((cue) => containsWholeWord(passage.quote.toLowerCase(), cue));
179
+ if (carriesCue)
180
+ cueBearers += 1;
181
+ });
182
+ });
183
+ const verdict = evidence.length === 0 ? "unresolved" : cueBearers > 0 ? "contradicted" : "corroborated";
184
+ return { text, verdict, negationCues: cueBearers, evidence };
185
+ });
186
+ return { statement, claims: rows };
187
+ }
188
+ //# sourceMappingURL=investigate-claims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investigate-claims.js","sourceRoot":"","sources":["../../src/lib/investigate-claims.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Deterministic passage extraction for the investigate pipeline
3
+ * (T3, docs/plans/investigate-pipeline DESIGN D4, PRD AC-5).
4
+ *
5
+ * Splits content into sentence windows with character-index tracking,
6
+ * keeps the windows containing >= 1 term as a whole word (case-folded),
7
+ * dedupes by exact quote equality, caps at 5 first-encounter passages.
8
+ *
9
+ * Boundary-char ownership: the terminator that ended a window
10
+ * (`.`/`!`/`?`/`\n`) belongs to the window; the separator run
11
+ * (spaces/tabs after a terminator) belongs to neither window. The
12
+ * round-trip pin — content.slice(...charRange) === quote for every
13
+ * passage — holds byte-exactly under this rule, and is invariant to
14
+ * it (any slicing convention preserves the pin).
15
+ *
16
+ * Byte-stable: no clocks, no randomness, no locale-dependent casing
17
+ * beyond toLowerCase. Self-contained: no imports.
18
+ */
19
+ export interface Passage {
20
+ quote: string;
21
+ charRange: [number, number];
22
+ }
23
+ export interface ExtractPassagesArgs {
24
+ content: string;
25
+ terms: string[];
26
+ }
27
+ /** Normalize the term set: trim, case-fold, drop empties, dedupe. */
28
+ export declare function normalizeTerms(terms: string[]): string[];
29
+ /**
30
+ * Extract passages: sentence windows of `content` containing >= 1 of
31
+ * `terms` as a whole word (case-folded), deduped by exact quote,
32
+ * capped at 5 first-encounter. Empty term set -> [] (valid).
33
+ */
34
+ export declare function extractPassages({ content, terms }: ExtractPassagesArgs): Passage[];
35
+ //# sourceMappingURL=investigate-extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investigate-extract.d.ts","sourceRoot":"","sources":["../../src/lib/investigate-extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAID,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CASxD;AA4DD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,mBAAmB,GAAG,OAAO,EAAE,CAqBlF"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Deterministic passage extraction for the investigate pipeline
3
+ * (T3, docs/plans/investigate-pipeline DESIGN D4, PRD AC-5).
4
+ *
5
+ * Splits content into sentence windows with character-index tracking,
6
+ * keeps the windows containing >= 1 term as a whole word (case-folded),
7
+ * dedupes by exact quote equality, caps at 5 first-encounter passages.
8
+ *
9
+ * Boundary-char ownership: the terminator that ended a window
10
+ * (`.`/`!`/`?`/`\n`) belongs to the window; the separator run
11
+ * (spaces/tabs after a terminator) belongs to neither window. The
12
+ * round-trip pin — content.slice(...charRange) === quote for every
13
+ * passage — holds byte-exactly under this rule, and is invariant to
14
+ * it (any slicing convention preserves the pin).
15
+ *
16
+ * Byte-stable: no clocks, no randomness, no locale-dependent casing
17
+ * beyond toLowerCase. Self-contained: no imports.
18
+ */
19
+ const MAX_PASSAGES = 5;
20
+ /** Normalize the term set: trim, case-fold, drop empties, dedupe. */
21
+ export function normalizeTerms(terms) {
22
+ const seen = new Set();
23
+ for (const term of terms) {
24
+ const normalized = term.trim().toLowerCase();
25
+ if (normalized.length > 0) {
26
+ seen.add(normalized);
27
+ }
28
+ }
29
+ return [...seen];
30
+ }
31
+ /**
32
+ * Split content into sentence windows on `[.!?]` terminators followed
33
+ * by a space (the literal `[.!?] +` grammar), plus newline
34
+ * boundaries, tracking exact offsets into the original content.
35
+ * Newlines terminate but never separate: "here.\n" ends at the `\n`.
36
+ */
37
+ function splitWindows(content) {
38
+ const windows = [];
39
+ let start = 0;
40
+ for (let i = 0; i < content.length; i += 1) {
41
+ const ch = content.charAt(i);
42
+ const next = content.charAt(i + 1);
43
+ const isTerminator = (ch === "." || ch === "!" || ch === "?") &&
44
+ (i + 1 >= content.length || next === " ");
45
+ const isNewline = ch === "\n";
46
+ if (!isTerminator && !isNewline) {
47
+ continue;
48
+ }
49
+ const end = i + 1; // terminator char owned by the window
50
+ windows.push({ start, end });
51
+ // Skip the horizontal-whitespace separator run; the next window
52
+ // starts at the first character that is neither space nor tab.
53
+ let cursor = end;
54
+ while (cursor < content.length) {
55
+ const sep = content.charAt(cursor);
56
+ if (sep !== " " && sep !== "\t") {
57
+ break;
58
+ }
59
+ cursor += 1;
60
+ }
61
+ start = cursor;
62
+ i = cursor - 1; // loop increment lands on `cursor`
63
+ }
64
+ if (start < content.length) {
65
+ windows.push({ start, end: content.length });
66
+ }
67
+ return windows;
68
+ }
69
+ function containsWholeWord(content, term) {
70
+ const re = new RegExp(`(^|[^\\p{L}\\p{N}_])${escapeRegExp(term)}(?:[^\\p{L}\\p{N}_]|$)`, "iu");
71
+ return re.test(content);
72
+ }
73
+ function escapeRegExp(s) {
74
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
75
+ }
76
+ /**
77
+ * Extract passages: sentence windows of `content` containing >= 1 of
78
+ * `terms` as a whole word (case-folded), deduped by exact quote,
79
+ * capped at 5 first-encounter. Empty term set -> [] (valid).
80
+ */
81
+ export function extractPassages({ content, terms }) {
82
+ const normalized = normalizeTerms(terms);
83
+ if (normalized.length === 0) {
84
+ return [];
85
+ }
86
+ const seenQuotes = new Set();
87
+ const passages = [];
88
+ for (const window of splitWindows(content)) {
89
+ const quote = content.slice(window.start, window.end);
90
+ if (normalized.some((term) => containsWholeWord(quote, term)) &&
91
+ !seenQuotes.has(quote)) {
92
+ seenQuotes.add(quote);
93
+ passages.push({ quote, charRange: [window.start, window.end] });
94
+ if (passages.length >= MAX_PASSAGES) {
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ return passages;
100
+ }
101
+ //# sourceMappingURL=investigate-extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investigate-extract.js","sourceRoot":"","sources":["../../src/lib/investigate-extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Three-tier deterministic sub-query planner for `investigate`
3
+ * (investigate-pipeline lane, Ticket T2; DESIGN.md D2, ADR-0013).
4
+ *
5
+ * Tier resolution order:
6
+ * 1. explicit — the query contains an unescaped `|`: split on the
7
+ * SAME grammar `search --merge` uses; wins over `--context` with
8
+ * a notice stating the override.
9
+ * 2. context — a `--context` file is present: `deriveSubQueries`
10
+ * over the parsed text, verbatim (no original prepend — context
11
+ * mode owns the grid, mirroring `search --context`). An empty
12
+ * derivation degrades to the original query.
13
+ * 3. template — transforms of the bare question (see TEMPLATE
14
+ * order below).
15
+ *
16
+ * Byte-stable everywhere: no clocks, no randomness, no
17
+ * locale-dependent casing beyond `toLowerCase`. The unit has no
18
+ * filesystem access — `loadContextText` is dependency-injected;
19
+ * production wiring lives in the orchestrator command.
20
+ */
21
+ /**
22
+ * PR #264 R1 (owner-approved): explicit pipe plans are capped at 8
23
+ * sub-queries — the context tier's MAX_SUBQUERIES precedent. Exceeding
24
+ * the cap fails loud with VALIDATION_ERROR (silent truncation rejected:
25
+ * a truncated plan would silently under-bill the user's intent).
26
+ */
27
+ export declare const MAX_EXPLICIT_SUBQUERIES = 8;
28
+ export type PlannerTier = "explicit" | "context" | "template";
29
+ export interface PlanSubQueriesOptions {
30
+ readonly query: string;
31
+ /** `--context` file path; presence selects the context tier. */
32
+ readonly contextFile?: string;
33
+ }
34
+ export interface PlanSubQueriesDeps {
35
+ /**
36
+ * Injected context reader (production: the context-file io
37
+ * adapter). The unit never touches the filesystem itself.
38
+ */
39
+ loadContextText(filePath: string): Promise<string>;
40
+ }
41
+ export interface SubQueryPlan {
42
+ readonly subQueries: readonly string[];
43
+ readonly tier: PlannerTier;
44
+ readonly notice?: string;
45
+ }
46
+ /**
47
+ * Key-terms join for the template tier: lowercase the question,
48
+ * tokenize on non-alphanumerics, drop stopwords and tokens outside the
49
+ * context-file term bounds, preserve order, dedupe, join with spaces.
50
+ * Only `toLowerCase` is applied (byte-stable, no locale).
51
+ */
52
+ export declare function deriveTemplateTopic(query: string): string;
53
+ /**
54
+ * Resolve the sub-query plan for an investigation. Throws only on the
55
+ * explicit-tier fail-loud cases (all fragments empty; > 8 fragments).
56
+ */
57
+ export declare function planSubQueries(options: PlanSubQueriesOptions, deps: PlanSubQueriesDeps): Promise<SubQueryPlan>;
58
+ //# sourceMappingURL=investigate-planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investigate-planner.d.ts","sourceRoot":"","sources":["../../src/lib/investigate-planner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAyBzC,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAE9D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAczD;AAwBD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,EAC9B,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CA6BvB"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Three-tier deterministic sub-query planner for `investigate`
3
+ * (investigate-pipeline lane, Ticket T2; DESIGN.md D2, ADR-0013).
4
+ *
5
+ * Tier resolution order:
6
+ * 1. explicit — the query contains an unescaped `|`: split on the
7
+ * SAME grammar `search --merge` uses; wins over `--context` with
8
+ * a notice stating the override.
9
+ * 2. context — a `--context` file is present: `deriveSubQueries`
10
+ * over the parsed text, verbatim (no original prepend — context
11
+ * mode owns the grid, mirroring `search --context`). An empty
12
+ * derivation degrades to the original query.
13
+ * 3. template — transforms of the bare question (see TEMPLATE
14
+ * order below).
15
+ *
16
+ * Byte-stable everywhere: no clocks, no randomness, no
17
+ * locale-dependent casing beyond `toLowerCase`. The unit has no
18
+ * filesystem access — `loadContextText` is dependency-injected;
19
+ * production wiring lives in the orchestrator command.
20
+ */
21
+ import { ValidationError } from "./errors.js";
22
+ import { STOPWORDS, parseContextText } from "./context-file.js";
23
+ /**
24
+ * PR #264 R1 (owner-approved): explicit pipe plans are capped at 8
25
+ * sub-queries — the context tier's MAX_SUBQUERIES precedent. Exceeding
26
+ * the cap fails loud with VALIDATION_ERROR (silent truncation rejected:
27
+ * a truncated plan would silently under-bill the user's intent).
28
+ */
29
+ export const MAX_EXPLICIT_SUBQUERIES = 8;
30
+ /**
31
+ * Pinned to `src/commands/search.ts` `splitMergeSubQueries` — the
32
+ * same split grammar the `--merge` flag defines: split on unescaped
33
+ * `|` (a literal pipe is escaped as `\|`), unescape, trim, drop
34
+ * empty fragments. Deliberately duplicated as a local constant: the
35
+ * original is module-private there and this lane's file allowlist
36
+ * keeps `search.ts` read-only (orchestrator ruling, T2 pickup).
37
+ */
38
+ const MERGE_SPLIT_PATTERN = /(?<!\\)\|/;
39
+ const MERGE_UNESCAPE = /\\\|/g;
40
+ /**
41
+ * Pinned to `src/lib/context-file.ts` `MIN_TERM_CHARS`/`MAX_TERM_CHARS`
42
+ * (module-private there; allowlist keeps the file read-only — same
43
+ * ruling as the splitter). Keep in sync with that module's D2.3 bounds.
44
+ */
45
+ const MIN_TERM_CHARS = 4;
46
+ const MAX_TERM_CHARS = 40;
47
+ const STOPWORD_SET = new Set(STOPWORDS);
48
+ const TEMPLATE_SUFFIXES = ["overview", "evidence", "criticism"];
49
+ /**
50
+ * Key-terms join for the template tier: lowercase the question,
51
+ * tokenize on non-alphanumerics, drop stopwords and tokens outside the
52
+ * context-file term bounds, preserve order, dedupe, join with spaces.
53
+ * Only `toLowerCase` is applied (byte-stable, no locale).
54
+ */
55
+ export function deriveTemplateTopic(query) {
56
+ const terms = [];
57
+ const seen = new Set();
58
+ for (const token of query.toLowerCase().split(/[^a-z0-9]+/)) {
59
+ if (token.length < MIN_TERM_CHARS || token.length > MAX_TERM_CHARS) {
60
+ continue;
61
+ }
62
+ if (STOPWORD_SET.has(token) || seen.has(token)) {
63
+ continue;
64
+ }
65
+ seen.add(token);
66
+ terms.push(token);
67
+ }
68
+ return terms.join(" ");
69
+ }
70
+ /**
71
+ * Explicit tier split — see the grammar pin above. Same fail-loud
72
+ * contract as `search --merge`: no non-empty fragments is an error.
73
+ * R1: > 8 fragments is VALIDATION_ERROR (never silent truncation).
74
+ */
75
+ function splitExplicit(query) {
76
+ const subQueries = query
77
+ .split(MERGE_SPLIT_PATTERN)
78
+ .map((q) => q.replace(MERGE_UNESCAPE, "|").trim())
79
+ .filter((q) => q.length > 0);
80
+ if (subQueries.length === 0) {
81
+ throw new Error("--merge requires at least one non-empty query (split with '|')");
82
+ }
83
+ if (subQueries.length > MAX_EXPLICIT_SUBQUERIES) {
84
+ throw new ValidationError(`Explicit pipe plan exceeds the ${MAX_EXPLICIT_SUBQUERIES}-sub-query cap (${subQueries.length} fragments).`, "Split fewer sub-queries with '|', or move the investigation into a --context file (the context tier derives up to 8).");
85
+ }
86
+ return subQueries;
87
+ }
88
+ /**
89
+ * Resolve the sub-query plan for an investigation. Throws only on the
90
+ * explicit-tier fail-loud cases (all fragments empty; > 8 fragments).
91
+ */
92
+ export async function planSubQueries(options, deps) {
93
+ const hasPipes = MERGE_SPLIT_PATTERN.test(options.query);
94
+ if (hasPipes) {
95
+ return {
96
+ subQueries: splitExplicit(options.query),
97
+ tier: "explicit",
98
+ notice: "--context ignored: explicit pipe split takes precedence over the context file",
99
+ };
100
+ }
101
+ if (options.contextFile !== undefined) {
102
+ const text = await deps.loadContextText(options.contextFile);
103
+ const derived = [...parseContextText(text).subQueries];
104
+ // Orchestrator ruling (T2 pickup): search's zero-derivation
105
+ // fallback precedent — an empty derivation degrades to the
106
+ // original query rather than an empty grid.
107
+ const subQueries = derived.length > 0 ? derived : [options.query];
108
+ return { subQueries, tier: "context" };
109
+ }
110
+ const topic = deriveTemplateTopic(options.query);
111
+ const subQueries = [options.query];
112
+ if (topic.length > 0) {
113
+ subQueries.push(topic);
114
+ for (const suffix of TEMPLATE_SUFFIXES) {
115
+ subQueries.push(`${topic} ${suffix}`);
116
+ }
117
+ }
118
+ return { subQueries: dedupe(subQueries), tier: "template" };
119
+ }
120
+ /**
121
+ * Exact-equal dedupe on trimmed, case-sensitive members, preserving
122
+ * first-occurrence order, capped at 5 (the template tier emits at
123
+ * most 5 candidates, so the cap is a hard upper bound).
124
+ */
125
+ function dedupe(candidates) {
126
+ const seen = new Set();
127
+ const result = [];
128
+ for (const candidate of candidates) {
129
+ const key = candidate.trim();
130
+ if (key.length === 0 || seen.has(key)) {
131
+ continue;
132
+ }
133
+ seen.add(key);
134
+ result.push(key);
135
+ if (result.length === 5) {
136
+ break;
137
+ }
138
+ }
139
+ return result;
140
+ }
141
+ //# sourceMappingURL=investigate-planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investigate-planner.js","sourceRoot":"","sources":["../../src/lib/investigate-planner.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwBH,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMjE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,kBAAkB,EAyBtE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,UAAU,EACd,WAAW,GAAE,SAAS,kBAAkB,EAAkC,GACzE,kBAAkB,CAEpB;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,WAAW,GAAE,SAAS,kBAAkB,EAAkC,GACzE,SAAS,kBAAkB,EAAE,CAE/B"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwBH,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMjE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,kBAAkB,EA4BtE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,UAAU,EACd,WAAW,GAAE,SAAS,kBAAkB,EAAkC,GACzE,kBAAkB,CAEpB;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,WAAW,GAAE,SAAS,kBAAkB,EAAkC,GACzE,SAAS,kBAAkB,EAAE,CAE/B"}