zelari-code 1.48.1 → 1.49.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 (55) hide show
  1. package/dist/cli/desktopConfig.js +51 -4
  2. package/dist/cli/desktopConfig.js.map +1 -1
  3. package/dist/cli/hooks/useChatTurn.js +80 -0
  4. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  5. package/dist/cli/kraken/candidateRegistry.js +195 -0
  6. package/dist/cli/kraken/candidateRegistry.js.map +1 -0
  7. package/dist/cli/kraken/candidateRegistry.test.js +211 -0
  8. package/dist/cli/kraken/candidateRegistry.test.js.map +1 -0
  9. package/dist/cli/kraken/completionGate.js +116 -0
  10. package/dist/cli/kraken/completionGate.js.map +1 -0
  11. package/dist/cli/kraken/completionGate.test.js +172 -0
  12. package/dist/cli/kraken/completionGate.test.js.map +1 -0
  13. package/dist/cli/kraken/metrics.js +96 -0
  14. package/dist/cli/kraken/metrics.js.map +1 -0
  15. package/dist/cli/kraken/metrics.test.js +145 -0
  16. package/dist/cli/kraken/metrics.test.js.map +1 -0
  17. package/dist/cli/kraken/selectionPlaybook.js +23 -0
  18. package/dist/cli/kraken/selectionPlaybook.js.map +1 -0
  19. package/dist/cli/kraken/selectionPlaybook.test.js +55 -0
  20. package/dist/cli/kraken/selectionPlaybook.test.js.map +1 -0
  21. package/dist/cli/kraken/turnRuntime.js +207 -0
  22. package/dist/cli/kraken/turnRuntime.js.map +1 -0
  23. package/dist/cli/kraken/turnRuntime.test.js +377 -0
  24. package/dist/cli/kraken/turnRuntime.test.js.map +1 -0
  25. package/dist/cli/kraken/verifier.js +233 -0
  26. package/dist/cli/kraken/verifier.js.map +1 -0
  27. package/dist/cli/kraken/verifier.test.js +232 -0
  28. package/dist/cli/kraken/verifier.test.js.map +1 -0
  29. package/dist/cli/kraken/verifierSettings.test.js +237 -0
  30. package/dist/cli/kraken/verifierSettings.test.js.map +1 -0
  31. package/dist/cli/kraken/verifyReport.js +115 -0
  32. package/dist/cli/kraken/verifyReport.js.map +1 -0
  33. package/dist/cli/kraken/verifyReport.test.js +111 -0
  34. package/dist/cli/kraken/verifyReport.test.js.map +1 -0
  35. package/dist/cli/main.bundled.js +1343 -107
  36. package/dist/cli/main.bundled.js.map +4 -4
  37. package/dist/cli/providerConfig.js +46 -0
  38. package/dist/cli/providerConfig.js.map +1 -1
  39. package/dist/cli/runHeadless.js +85 -0
  40. package/dist/cli/runHeadless.js.map +1 -1
  41. package/dist/cli/toolRegistry.js +50 -6
  42. package/dist/cli/toolRegistry.js.map +1 -1
  43. package/dist/cli/tools/krakenSelectTool.js +165 -0
  44. package/dist/cli/tools/krakenSelectTool.js.map +1 -0
  45. package/dist/cli/tools/krakenSelectTool.test.js +246 -0
  46. package/dist/cli/tools/krakenSelectTool.test.js.map +1 -0
  47. package/dist/cli/tools/taskTool.dynamicChecks.test.js +156 -0
  48. package/dist/cli/tools/taskTool.dynamicChecks.test.js.map +1 -0
  49. package/dist/cli/tools/taskTool.js +174 -6
  50. package/dist/cli/tools/taskTool.js.map +1 -1
  51. package/dist/cli/tools/taskTool.planSafety.test.js +280 -0
  52. package/dist/cli/tools/taskTool.planSafety.test.js.map +1 -0
  53. package/dist/cli/tools/taskTool.verifyReport.test.js +140 -0
  54. package/dist/cli/tools/taskTool.verifyReport.test.js.map +1 -0
  55. package/package.json +1 -1
@@ -0,0 +1,195 @@
1
+ /**
2
+ * candidateRegistry — per-turn registry of Kraken candidate tentacles
3
+ * (Fase 3, ADR-0020).
4
+ *
5
+ * A "candidate" is an explore-only tentacle spawned to research ONE
6
+ * independent hypothesis. Its conclusion must end with a structured
7
+ * `<candidate-report>` JSON block; this module parses it LENIENTLY:
8
+ * malformed reports are preserved with `status: 'malformed'` (degraded
9
+ * evidence is evidence about the candidate, never silently dropped).
10
+ *
11
+ * In-memory, per-process, reset each parent turn via
12
+ * {@link resetKrakenCandidates} (same lifecycle as `resetTaskSpawnCount`).
13
+ * The future `kraken_select` tool (Fase 4) will read from here.
14
+ *
15
+ * v1 invariant (ADR-0020): candidates are explore-only — zero candidate
16
+ * implementations. The registry never stores edits or patches.
17
+ */
18
+ /** Max candidates per parent turn (v1: 3 — ADR-0020 §candidate cap). */
19
+ export const KRAKEN_CANDIDATE_CAP = 3;
20
+ /** Feature flag: candidate contracts are alpha-gated (default OFF). */
21
+ export function isKrakenSelectionEnabled() {
22
+ return process.env.ZELARI_KRAKEN_SELECTION === '1';
23
+ }
24
+ function store() {
25
+ const g = globalThis;
26
+ if (!g.__zelariKrakenCandidates)
27
+ g.__zelariKrakenCandidates = [];
28
+ return g.__zelariKrakenCandidates;
29
+ }
30
+ /** Reset the per-turn candidate registry (call at each parent user turn). */
31
+ export function resetKrakenCandidates() {
32
+ const g = globalThis;
33
+ g.__zelariKrakenCandidates = [];
34
+ g.__zelariKrakenSelection = null;
35
+ g.__zelariKrakenCheckResults = null;
36
+ }
37
+ /** Store the kraken_select verdict for this turn (Fase 6/8 consume it). */
38
+ export function setKrakenSelection(verdict) {
39
+ const g = globalThis;
40
+ g.__zelariKrakenSelection = verdict;
41
+ }
42
+ /** Verdict of this turn's kraken_select call, or null when none ran. */
43
+ export function getKrakenSelection() {
44
+ const g = globalThis;
45
+ return g.__zelariKrakenSelection ?? null;
46
+ }
47
+ /**
48
+ * Required checks of THIS turn's selection (Fase 6, ADR-0020).
49
+ *
50
+ * Only a `selected` verdict carries proof obligations: the checks belong to
51
+ * the winning candidate's path. A needs_more_evidence verdict leaves the
52
+ * judgment (and any acceptance) to the parent — its checks stay advisory
53
+ * (already visible in the kraken_select result text).
54
+ */
55
+ export function krakenRequiredChecks() {
56
+ const verdict = getKrakenSelection();
57
+ if (!verdict || verdict.status !== 'selected')
58
+ return [];
59
+ return verdict.requiredChecks;
60
+ }
61
+ /**
62
+ * Fase 7 (ADR-0020): store the per-check results of the latest verify
63
+ * tentacle. Called by the task tool for EVERY verify tentacle when the
64
+ * turn has required checks — a later tentacle replaces earlier results
65
+ * (the runtime counter follows the latest verification state).
66
+ */
67
+ export function setKrakenCheckResults(results) {
68
+ const g = globalThis;
69
+ g.__zelariKrakenCheckResults = results;
70
+ }
71
+ /**
72
+ * Per-check results of the latest verify tentacle this turn, or null when
73
+ * no verify tentacle reported (yet).
74
+ */
75
+ export function getKrakenCheckResults() {
76
+ const g = globalThis;
77
+ const results = g.__zelariKrakenCheckResults;
78
+ return results ? [...results] : null;
79
+ }
80
+ /**
81
+ * Passed-check counter (Fase 7): how many required checks carry an explicit
82
+ * `pass`. `unknown` NEVER counts — a degraded observation is not proof
83
+ * (§23). undefined when no verify report landed yet this turn.
84
+ */
85
+ export function krakenChecksPassed() {
86
+ const results = getKrakenCheckResults();
87
+ if (!results)
88
+ return undefined;
89
+ return results.filter((r) => r.status === 'pass').length;
90
+ }
91
+ /** Candidates registered this turn (defensive copy). */
92
+ export function krakenCandidates() {
93
+ return [...store()];
94
+ }
95
+ /**
96
+ * Try to reserve a candidate slot. Returns the 1-based index, or an error
97
+ * string when the cap is reached (caller turns it into a typedErr).
98
+ */
99
+ export function reserveCandidateSlot() {
100
+ const n = store().length;
101
+ if (n >= KRAKEN_CANDIDATE_CAP) {
102
+ return {
103
+ error: `task: candidate cap reached (${KRAKEN_CANDIDATE_CAP}). ` +
104
+ 'Compare the existing candidates instead of spawning more.',
105
+ };
106
+ }
107
+ return { index: n + 1 };
108
+ }
109
+ const REPORT_OPEN = '<candidate-report>';
110
+ const REPORT_CLOSE = '</candidate-report>';
111
+ /**
112
+ * Extract + parse the structured report from a candidate conclusion.
113
+ * Lenient by design:
114
+ * - no block → malformed('missing report block')
115
+ * - invalid JSON → malformed('invalid JSON')
116
+ * - missing fields → filled with safe defaults (empty arrays / '')
117
+ * - evidence items → degraded defaults to false; extra keys dropped
118
+ */
119
+ export function parseCandidateReport(raw) {
120
+ const open = raw.lastIndexOf(REPORT_OPEN);
121
+ const close = raw.lastIndexOf(REPORT_CLOSE);
122
+ if (open === -1 || close === -1 || close < open) {
123
+ return { ok: false, error: 'missing report block' };
124
+ }
125
+ const body = raw.slice(open + REPORT_OPEN.length, close).trim();
126
+ let parsed;
127
+ try {
128
+ parsed = JSON.parse(body);
129
+ }
130
+ catch (err) {
131
+ return {
132
+ ok: false,
133
+ error: `invalid JSON (${err instanceof Error ? err.message : String(err)})`,
134
+ };
135
+ }
136
+ if (typeof parsed !== 'object' || parsed === null) {
137
+ return { ok: false, error: 'report is not a JSON object' };
138
+ }
139
+ const obj = parsed;
140
+ const evidence = Array.isArray(obj.evidence)
141
+ ? obj.evidence
142
+ .filter((e) => typeof e === 'object' && e !== null)
143
+ .map((e) => ({
144
+ claim: typeof e.claim === 'string' ? e.claim : '',
145
+ basis: typeof e.basis === 'string' ? e.basis : '',
146
+ degraded: e.degraded === true,
147
+ }))
148
+ .filter((e) => e.claim.trim().length > 0)
149
+ : [];
150
+ const risks = Array.isArray(obj.risks)
151
+ ? obj.risks.filter((r) => typeof r === 'string' && r.trim().length > 0)
152
+ : [];
153
+ return {
154
+ ok: true,
155
+ report: {
156
+ hypothesis: typeof obj.hypothesis === 'string' ? obj.hypothesis.trim() : '',
157
+ evidence,
158
+ risks,
159
+ hasDegradedEvidence: evidence.some((e) => e.degraded),
160
+ },
161
+ };
162
+ }
163
+ export function registerCandidate(entry) {
164
+ const withIndex = { ...entry, index: entry.index ?? store().length + 1 };
165
+ store().push(withIndex);
166
+ return withIndex;
167
+ }
168
+ /**
169
+ * Instructions appended to the explore system prompt when the tentacle is a
170
+ * candidate: diversity + structured payload + observation integrity.
171
+ */
172
+ export const CANDIDATE_INSTRUCTIONS = [
173
+ 'You are CANDIDATE #{index} of at most {cap}: one independent hypothesis',
174
+ 'about the task, researched in parallel with other candidates.',
175
+ 'DIVERSITY: focus on ONE hypothesis and pursue it honestly — do not try to',
176
+ 'cover every angle. A narrower, well-evidenced hypothesis beats a broad guess.',
177
+ 'OBSERVATION INTEGRITY: mark degraded or inconclusive observations explicitly',
178
+ '(timeout, zero files walked, unavailable backend) — degraded is NOT proof',
179
+ 'of absence.',
180
+ 'END your final message with EXACTLY this block (valid JSON, no prose after):',
181
+ '<candidate-report>',
182
+ '{',
183
+ ' "hypothesis": "one-sentence hypothesis you investigated",',
184
+ ' "evidence": [',
185
+ ' { "claim": "what you found", "basis": "tool + file:line or command", "degraded": false }',
186
+ ' ],',
187
+ ' "risks": ["open questions / weaknesses"]',
188
+ '}',
189
+ '</candidate-report>',
190
+ ].join('\n');
191
+ /** Render the instructions for one candidate index. */
192
+ export function candidateInstructions(index, cap = KRAKEN_CANDIDATE_CAP) {
193
+ return CANDIDATE_INSTRUCTIONS.replaceAll('{index}', String(index)).replaceAll('{cap}', String(cap));
194
+ }
195
+ //# sourceMappingURL=candidateRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidateRegistry.js","sourceRoot":"","sources":["../../../src/cli/kraken/candidateRegistry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * candidateRegistry.test — Fase 3 (ADR-0020): candidate contracts.
3
+ * - structured parse (ok / malformed / lenient fills)
4
+ * - degraded evidence preserved
5
+ * - cap 3 per turn + reset
6
+ * - instructions rendering
7
+ */
8
+ import { describe, expect, it, beforeEach } from 'vitest';
9
+ import { KRAKEN_CANDIDATE_CAP, candidateInstructions, getKrakenCheckResults, getKrakenSelection, isKrakenSelectionEnabled, krakenCandidates, krakenChecksPassed, krakenRequiredChecks, parseCandidateReport, registerCandidate, resetKrakenCandidates, reserveCandidateSlot, setKrakenCheckResults, setKrakenSelection, } from './candidateRegistry.js';
10
+ const GOOD = [
11
+ 'Research summary: the refresh path races with the session bootstrap.',
12
+ '<candidate-report>',
13
+ '{',
14
+ ' "hypothesis": "Session lost because refresh races with bootstrap",',
15
+ ' "evidence": [',
16
+ ' { "claim": "refresh() reads stale token", "basis": "read_file src/auth.ts:120", "degraded": false },',
17
+ ' { "claim": "grep timed out on legacy dir", "basis": "grep_content legacy/", "degraded": true }',
18
+ ' ],',
19
+ ' "risks": ["bootstrap order may differ in prod"]',
20
+ '}',
21
+ '</candidate-report>',
22
+ ].join('\n');
23
+ describe('parseCandidateReport', () => {
24
+ it('parses a well-formed report with evidence + risks', () => {
25
+ const r = parseCandidateReport(GOOD);
26
+ expect(r.ok).toBe(true);
27
+ if (r.ok) {
28
+ expect(r.report.hypothesis).toContain('refresh races');
29
+ expect(r.report.evidence).toHaveLength(2);
30
+ expect(r.report.evidence[0].degraded).toBe(false);
31
+ expect(r.report.hasDegradedEvidence).toBe(true);
32
+ expect(r.report.risks).toHaveLength(1);
33
+ }
34
+ });
35
+ it('missing block → malformed', () => {
36
+ const r = parseCandidateReport('just prose, no block');
37
+ expect(r.ok).toBe(false);
38
+ if (!r.ok)
39
+ expect(r.error).toMatch(/missing report block/);
40
+ });
41
+ it('invalid JSON → malformed with reason', () => {
42
+ const r = parseCandidateReport('<candidate-report>{nope</candidate-report>');
43
+ expect(r.ok).toBe(false);
44
+ if (!r.ok)
45
+ expect(r.error).toMatch(/invalid JSON/);
46
+ });
47
+ it('lenient fills: missing/extra fields never break the parse', () => {
48
+ const r = parseCandidateReport('<candidate-report>{ "hypothesis": "h", "evidence": [ { "claim": "c" }, "junk", 5 ], "extra": 1 }</candidate-report>');
49
+ expect(r.ok).toBe(true);
50
+ if (r.ok) {
51
+ expect(r.report.hypothesis).toBe('h');
52
+ // "junk"/5 dropped; claim-only item kept with safe defaults
53
+ expect(r.report.evidence).toEqual([
54
+ { claim: 'c', basis: '', degraded: false },
55
+ ]);
56
+ expect(r.report.risks).toEqual([]);
57
+ expect(r.report.hasDegradedEvidence).toBe(false);
58
+ }
59
+ });
60
+ it('uses the LAST block when the model emits more than one', () => {
61
+ const r = parseCandidateReport('<candidate-report>{ "hypothesis": "first" }</candidate-report>\nmore\n<candidate-report>{ "hypothesis": "final" }</candidate-report>');
62
+ expect(r.ok).toBe(true);
63
+ if (r.ok)
64
+ expect(r.report.hypothesis).toBe('final');
65
+ });
66
+ });
67
+ describe('per-turn registry', () => {
68
+ beforeEach(() => resetKrakenCandidates());
69
+ function reportOf(raw) {
70
+ const r = parseCandidateReport(raw);
71
+ if (!r.ok)
72
+ throw new Error('fixture must parse');
73
+ return r.report;
74
+ }
75
+ it('cap: the 4th slot is refused, the first 3 accepted', () => {
76
+ expect(reserveCandidateSlot()).toEqual({ index: 1 });
77
+ registerCandidate({ status: 'ok', description: 'a', report: reportOf(GOOD), raw: GOOD });
78
+ expect(reserveCandidateSlot()).toEqual({ index: 2 });
79
+ registerCandidate({ status: 'ok', description: 'b', report: reportOf(GOOD), raw: GOOD });
80
+ expect(reserveCandidateSlot()).toEqual({ index: 3 });
81
+ registerCandidate({ status: 'malformed', description: 'c', error: 'missing report block', raw: 'no block' });
82
+ const fourth = reserveCandidateSlot();
83
+ expect('error' in fourth).toBe(true);
84
+ if ('error' in fourth)
85
+ expect(fourth.error).toMatch(/cap reached \(3\)/);
86
+ expect(krakenCandidates()).toHaveLength(3);
87
+ expect(krakenCandidates()[2].status).toBe('malformed');
88
+ });
89
+ it('malformed candidates are preserved, not dropped (degraded evidence rule)', () => {
90
+ registerCandidate({ status: 'malformed', description: 'x', error: 'invalid JSON (x)', raw: 'partial' });
91
+ const all = krakenCandidates();
92
+ expect(all).toHaveLength(1);
93
+ expect(all[0]).toMatchObject({ status: 'malformed', description: 'x' });
94
+ });
95
+ it('reset clears the registry', () => {
96
+ registerCandidate({ status: 'malformed', description: 'x', error: 'e', raw: '' });
97
+ resetKrakenCandidates();
98
+ expect(krakenCandidates()).toHaveLength(0);
99
+ });
100
+ });
101
+ describe('instructions + flag', () => {
102
+ it('renders candidate index and cap', () => {
103
+ const text = candidateInstructions(2);
104
+ expect(text).toContain('CANDIDATE #2');
105
+ expect(text).toContain(`at most ${KRAKEN_CANDIDATE_CAP}`);
106
+ expect(text).toContain('<candidate-report>');
107
+ expect(text).toContain('degraded');
108
+ });
109
+ it('feature flag defaults OFF and honours ZELARI_KRAKEN_SELECTION=1', () => {
110
+ const prev = process.env.ZELARI_KRAKEN_SELECTION;
111
+ delete process.env.ZELARI_KRAKEN_SELECTION;
112
+ expect(isKrakenSelectionEnabled()).toBe(false);
113
+ process.env.ZELARI_KRAKEN_SELECTION = '1';
114
+ expect(isKrakenSelectionEnabled()).toBe(true);
115
+ if (prev === undefined)
116
+ delete process.env.ZELARI_KRAKEN_SELECTION;
117
+ else
118
+ process.env.ZELARI_KRAKEN_SELECTION = prev;
119
+ });
120
+ });
121
+ describe('krakenRequiredChecks (ADR-0020 Fase 6)', () => {
122
+ it('returns [] when no selection ran this turn', () => {
123
+ resetKrakenCandidates();
124
+ expect(krakenRequiredChecks()).toEqual([]);
125
+ });
126
+ it('returns [] for needs_more_evidence — checks stay advisory', () => {
127
+ resetKrakenCandidates();
128
+ setKrakenSelection({
129
+ status: 'needs_more_evidence',
130
+ winnerIndex: null,
131
+ rationale: 'tie',
132
+ requiredChecks: ['check that must NOT be enforced'],
133
+ degraded: false,
134
+ verifier: null,
135
+ judgedBy: 'llm',
136
+ });
137
+ expect(krakenRequiredChecks()).toEqual([]);
138
+ });
139
+ it('returns the checks of a selected verdict', () => {
140
+ resetKrakenCandidates();
141
+ setKrakenSelection({
142
+ status: 'selected',
143
+ winnerIndex: 2,
144
+ rationale: 'grounded',
145
+ requiredChecks: ['race regression test', 'session e2e'],
146
+ degraded: false,
147
+ verifier: { provider: 'p', model: 'm' },
148
+ judgedBy: 'llm',
149
+ });
150
+ expect(krakenRequiredChecks()).toEqual(['race regression test', 'session e2e']);
151
+ });
152
+ it('reset clears verdict and checks together', () => {
153
+ setKrakenSelection({
154
+ status: 'selected',
155
+ winnerIndex: 1,
156
+ rationale: 'r',
157
+ requiredChecks: ['x'],
158
+ degraded: false,
159
+ verifier: null,
160
+ judgedBy: 'deterministic',
161
+ });
162
+ expect(getKrakenSelection()).not.toBeNull();
163
+ resetKrakenCandidates();
164
+ expect(getKrakenSelection()).toBeNull();
165
+ expect(krakenRequiredChecks()).toEqual([]);
166
+ });
167
+ });
168
+ describe('Fase 7 — check results storage', () => {
169
+ beforeEach(() => resetKrakenCandidates());
170
+ it('stores and returns per-check results (defensive copy)', () => {
171
+ setKrakenCheckResults([
172
+ { check: 'a', status: 'pass' },
173
+ { check: 'b', status: 'unknown', note: 'grep timed out' },
174
+ ]);
175
+ const first = getKrakenCheckResults();
176
+ expect(first).toEqual([
177
+ { check: 'a', status: 'pass' },
178
+ { check: 'b', status: 'unknown', note: 'grep timed out' },
179
+ ]);
180
+ first?.push({ check: 'c', status: 'fail' });
181
+ expect(getKrakenCheckResults()).toHaveLength(2);
182
+ });
183
+ it('krakenChecksPassed counts only explicit pass — unknown never counts', () => {
184
+ setKrakenCheckResults([
185
+ { check: 'a', status: 'pass' },
186
+ { check: 'b', status: 'fail' },
187
+ { check: 'c', status: 'unknown', note: 'degraded' },
188
+ ]);
189
+ expect(krakenChecksPassed()).toBe(1);
190
+ });
191
+ it('undefined before any verify report lands', () => {
192
+ expect(getKrakenCheckResults()).toBeNull();
193
+ expect(krakenChecksPassed()).toBeUndefined();
194
+ });
195
+ it('a later verify tentacle replaces earlier results', () => {
196
+ setKrakenCheckResults([
197
+ { check: 'a', status: 'unknown', note: 'first run degraded' },
198
+ ]);
199
+ setKrakenCheckResults([
200
+ { check: 'a', status: 'pass' },
201
+ ]);
202
+ expect(krakenChecksPassed()).toBe(1);
203
+ });
204
+ it('reset clears check results together with verdict and candidates', () => {
205
+ setKrakenCheckResults([{ check: 'a', status: 'pass' }]);
206
+ resetKrakenCandidates();
207
+ expect(getKrakenCheckResults()).toBeNull();
208
+ expect(krakenChecksPassed()).toBeUndefined();
209
+ });
210
+ });
211
+ //# sourceMappingURL=candidateRegistry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidateRegistry.test.js","sourceRoot":"","sources":["../../../src/cli/kraken/candidateRegistry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * completionGate — BUILD clean-finish gate for selection turns (Fase 8,
3
+ * ADR-0020).
4
+ *
5
+ * A BUILD turn that used kraken_select cannot cleanly finish while its
6
+ * required checks are unresolved: a `fail` blocks by contradiction, an
7
+ * `unknown` blocks because a degraded or missing observation is never proof
8
+ * (§23 observation integrity — the same rule verifyReport enforces).
9
+ *
10
+ * The gate integrates into the EXISTING recovery flow (the headless BUILD
11
+ * write-retry pattern) rather than adding a second recovery system: one
12
+ * automatic repair pass, structural budget = 1 (the TUI enqueues it once via
13
+ * a local flag; headless fires a single guarded block). PLAN turns are never
14
+ * gated — their checks fold into the final plan document (Fase 6 routing),
15
+ * and verify tentacles cannot run there anyway (Fase 1).
16
+ */
17
+ import { getKrakenCheckResults, krakenRequiredChecks } from './candidateRegistry.js';
18
+ /** Lowercase + collapse whitespace (mirrors verifyReport matching). */
19
+ function normalize(text) {
20
+ return text.toLowerCase().replace(/\s+/g, ' ').trim();
21
+ }
22
+ /**
23
+ * Map required checks onto their latest reported results. Matching mirrors
24
+ * verifyReport: normalized equality first, then containment (either
25
+ * direction, minimum length 8) so a lightly reworded criterion still maps.
26
+ * A check with no matching result is `unknown` — never assumed `pass`.
27
+ */
28
+ export function classifyKrakenChecks(requiredChecks, results) {
29
+ const byCriterion = new Map();
30
+ for (const result of results ?? []) {
31
+ byCriterion.set(normalize(result.check), result); // later duplicates win
32
+ }
33
+ const keys = [...byCriterion.keys()];
34
+ const out = { passed: [], failed: [], unknown: [] };
35
+ for (const check of requiredChecks) {
36
+ const norm = normalize(check);
37
+ let result = byCriterion.get(norm) ?? null;
38
+ if (!result) {
39
+ for (const key of keys) {
40
+ if (key.length >= 8 && (key.includes(norm) || norm.includes(key))) {
41
+ result = byCriterion.get(key) ?? null;
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ if (!result || result.status === 'unknown')
47
+ out.unknown.push(check);
48
+ else if (result.status === 'pass')
49
+ out.passed.push(check);
50
+ else
51
+ out.failed.push(check);
52
+ }
53
+ return out;
54
+ }
55
+ const OPEN_GATE = {
56
+ blocked: false,
57
+ selectionUsed: false,
58
+ total: 0,
59
+ passed: 0,
60
+ failedChecks: [],
61
+ unknownChecks: [],
62
+ };
63
+ /**
64
+ * Evaluate the turn registry. Reads the per-turn candidate registry
65
+ * (selection verdict + last verify report); returns an open gate for PLAN
66
+ * turns, turns without selection, and selections that produced no checks.
67
+ * Never throws.
68
+ */
69
+ export function evaluateKrakenCompletionGate(mode) {
70
+ try {
71
+ if (mode !== 'build')
72
+ return OPEN_GATE;
73
+ const checks = krakenRequiredChecks();
74
+ if (checks.length === 0)
75
+ return OPEN_GATE;
76
+ const classification = classifyKrakenChecks(checks, getKrakenCheckResults());
77
+ return {
78
+ blocked: classification.failed.length > 0 || classification.unknown.length > 0,
79
+ selectionUsed: true,
80
+ total: checks.length,
81
+ passed: classification.passed.length,
82
+ failedChecks: classification.failed,
83
+ unknownChecks: classification.unknown,
84
+ };
85
+ }
86
+ catch {
87
+ // The gate must never break the turn — treat errors as open.
88
+ return OPEN_GATE;
89
+ }
90
+ }
91
+ /**
92
+ * User directive for the single automatic repair pass. The selection itself
93
+ * is settled (no re-running kraken_select); the model must fix, then
94
+ * re-verify ALL required checks via a verify tentacle.
95
+ */
96
+ export function buildKrakenRepairPrompt(gate) {
97
+ const lines = [
98
+ `The BUILD turn is ending, but the required checks from kraken_select are not all satisfied (passed ${gate.passed}/${gate.total}).`,
99
+ '',
100
+ ];
101
+ if (gate.failedChecks.length > 0) {
102
+ lines.push('FAILED checks (evidence contradicts them):');
103
+ for (const check of gate.failedChecks)
104
+ lines.push(`- ${check}`);
105
+ lines.push('');
106
+ }
107
+ if (gate.unknownChecks.length > 0) {
108
+ lines.push('UNKNOWN checks (never conclusively verified — a degraded or missing observation is NOT proof):');
109
+ for (const check of gate.unknownChecks)
110
+ lines.push(`- ${check}`);
111
+ lines.push('');
112
+ }
113
+ lines.push('Recover this turn:', '1. The approach selection is settled — do NOT call kraken_select again.', '2. Fix each failing check directly with focused edits.', '3. Make each unknown check conclusively verifiable (run the real command, read the real output).', '4. Spawn a `task verify` tentacle whose Acceptance lists ALL required checks; its conclusion must contain one <verify-report> block per check with an explicit status.', '5. Only end the turn when every check reports status: pass — an unverified assumption is not a pass.');
114
+ return lines.join('\n');
115
+ }
116
+ //# sourceMappingURL=completionGate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completionGate.js","sourceRoot":"","sources":["../../../src/cli/kraken/completionGate.ts"],"names":[],"mappings":"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"}