@njeybe/nativ 2.0.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 (140) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +334 -0
  4. package/bin/cli.js +31 -0
  5. package/dist/commands/bench.d.ts +8 -0
  6. package/dist/commands/bench.js +52 -0
  7. package/dist/commands/db.d.ts +22 -0
  8. package/dist/commands/db.js +415 -0
  9. package/dist/commands/doctor.d.ts +38 -0
  10. package/dist/commands/doctor.js +169 -0
  11. package/dist/commands/hook.d.ts +31 -0
  12. package/dist/commands/hook.js +105 -0
  13. package/dist/commands/init.d.ts +3 -0
  14. package/dist/commands/init.js +144 -0
  15. package/dist/commands/mcp.d.ts +5 -0
  16. package/dist/commands/mcp.js +16 -0
  17. package/dist/commands/setup.d.ts +17 -0
  18. package/dist/commands/setup.js +51 -0
  19. package/dist/commands/status.d.ts +5 -0
  20. package/dist/commands/status.js +75 -0
  21. package/dist/commands/task.d.ts +83 -0
  22. package/dist/commands/task.js +807 -0
  23. package/dist/commands/test-gen.d.ts +14 -0
  24. package/dist/commands/test-gen.js +52 -0
  25. package/dist/commands/triage.d.ts +70 -0
  26. package/dist/commands/triage.js +343 -0
  27. package/dist/commands/update.d.ts +14 -0
  28. package/dist/commands/update.js +112 -0
  29. package/dist/commands/validate.d.ts +1 -0
  30. package/dist/commands/validate.js +89 -0
  31. package/dist/commands/verify.d.ts +8 -0
  32. package/dist/commands/verify.js +65 -0
  33. package/dist/commands/worktree.d.ts +47 -0
  34. package/dist/commands/worktree.js +396 -0
  35. package/dist/core/benchmark.d.ts +57 -0
  36. package/dist/core/benchmark.js +772 -0
  37. package/dist/core/enforcement.d.ts +80 -0
  38. package/dist/core/enforcement.js +271 -0
  39. package/dist/core/legacy-templates.d.ts +24 -0
  40. package/dist/core/legacy-templates.js +60 -0
  41. package/dist/core/lock-manager.d.ts +54 -0
  42. package/dist/core/lock-manager.js +260 -0
  43. package/dist/core/root-resolver.d.ts +32 -0
  44. package/dist/core/root-resolver.js +149 -0
  45. package/dist/core/setup-assets.d.ts +123 -0
  46. package/dist/core/setup-assets.js +438 -0
  47. package/dist/core/telemetry.d.ts +156 -0
  48. package/dist/core/telemetry.js +416 -0
  49. package/dist/core/test-generator-types.d.ts +227 -0
  50. package/dist/core/test-generator-types.js +179 -0
  51. package/dist/core/test-generator.d.ts +34 -0
  52. package/dist/core/test-generator.js +1690 -0
  53. package/dist/core/tier1-liaison.d.ts +139 -0
  54. package/dist/core/tier1-liaison.js +320 -0
  55. package/dist/core/verifier.d.ts +52 -0
  56. package/dist/core/verifier.js +249 -0
  57. package/dist/core/version.d.ts +5 -0
  58. package/dist/core/version.js +14 -0
  59. package/dist/core/worktree-merge.d.ts +30 -0
  60. package/dist/core/worktree-merge.js +98 -0
  61. package/dist/db/data-engine.d.ts +46 -0
  62. package/dist/db/data-engine.js +802 -0
  63. package/dist/db/diff.d.ts +39 -0
  64. package/dist/db/diff.js +420 -0
  65. package/dist/db/env-parser.d.ts +86 -0
  66. package/dist/db/env-parser.js +549 -0
  67. package/dist/db/introspector.d.ts +21 -0
  68. package/dist/db/introspector.js +835 -0
  69. package/dist/db/types.d.ts +217 -0
  70. package/dist/db/types.js +9 -0
  71. package/dist/governor/circuit-breaker.d.ts +100 -0
  72. package/dist/governor/circuit-breaker.js +231 -0
  73. package/dist/governor/evaluator.d.ts +67 -0
  74. package/dist/governor/evaluator.js +851 -0
  75. package/dist/governor/index.d.ts +6 -0
  76. package/dist/governor/index.js +6 -0
  77. package/dist/governor/rules/api-rules.d.ts +2 -0
  78. package/dist/governor/rules/api-rules.js +140 -0
  79. package/dist/governor/rules/db-rules.d.ts +2 -0
  80. package/dist/governor/rules/db-rules.js +180 -0
  81. package/dist/governor/store.d.ts +33 -0
  82. package/dist/governor/store.js +84 -0
  83. package/dist/governor/types.d.ts +64 -0
  84. package/dist/governor/types.js +1 -0
  85. package/dist/index.d.ts +3 -0
  86. package/dist/index.js +358 -0
  87. package/dist/mcp/server.d.ts +3 -0
  88. package/dist/mcp/server.js +309 -0
  89. package/dist/providers/claude-api.d.ts +5 -0
  90. package/dist/providers/claude-api.js +75 -0
  91. package/dist/providers/claude-cli.d.ts +13 -0
  92. package/dist/providers/claude-cli.js +134 -0
  93. package/dist/providers/gemini.d.ts +8 -0
  94. package/dist/providers/gemini.js +78 -0
  95. package/dist/providers/index.d.ts +5 -0
  96. package/dist/providers/index.js +5 -0
  97. package/dist/providers/registry.d.ts +38 -0
  98. package/dist/providers/registry.js +121 -0
  99. package/dist/providers/types.d.ts +74 -0
  100. package/dist/providers/types.js +25 -0
  101. package/dist/runner/agent-supervisor.d.ts +363 -0
  102. package/dist/runner/agent-supervisor.js +1672 -0
  103. package/dist/scanner/context-builder.d.ts +2 -0
  104. package/dist/scanner/context-builder.js +29 -0
  105. package/dist/scanner/detector.d.ts +2 -0
  106. package/dist/scanner/detector.js +760 -0
  107. package/dist/scanner/types.d.ts +80 -0
  108. package/dist/scanner/types.js +50 -0
  109. package/dist/server/studio-server.d.ts +39 -0
  110. package/dist/server/studio-server.js +1931 -0
  111. package/dist/server/studio-ui.d.ts +15 -0
  112. package/dist/server/studio-ui.js +5321 -0
  113. package/package.json +70 -0
  114. package/plugin/.claude-plugin/plugin.json +18 -0
  115. package/plugin/.mcp.json +10 -0
  116. package/plugin/agents/architect.md +28 -0
  117. package/plugin/agents/verifier.md +28 -0
  118. package/plugin/agents/worker.md +26 -0
  119. package/plugin/hooks/hooks.json +28 -0
  120. package/plugin/skills/nativ/SKILL.md +75 -0
  121. package/templates/.claudeignore +45 -0
  122. package/templates/AGENTS.md +67 -0
  123. package/templates/CLAUDE.md +32 -0
  124. package/templates/GEMINI.md +66 -0
  125. package/templates/claude-agents/architect.md +28 -0
  126. package/templates/claude-agents/verifier.md +28 -0
  127. package/templates/claude-agents/worker.md +26 -0
  128. package/templates/dot-ai/api_contracts.json +66 -0
  129. package/templates/dot-ai/context.md +38 -0
  130. package/templates/dot-ai/db_schema.json +39 -0
  131. package/templates/dot-ai/master_plan.json +28 -0
  132. package/templates/dot-ai/subagents/backend.md +11 -0
  133. package/templates/dot-ai/subagents/database.md +10 -0
  134. package/templates/dot-ai/subagents/db-migration.md +11 -0
  135. package/templates/dot-ai/subagents/devops-agent.md +11 -0
  136. package/templates/dot-ai/subagents/flutter-developer.md +11 -0
  137. package/templates/dot-ai/subagents/frontend.md +126 -0
  138. package/templates/dot-ai/subagents/qa-tester.md +13 -0
  139. package/templates/dot-ai/subagents/security-auditor.md +11 -0
  140. package/templates/dot-ai/ui_specs.md +78 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * nativ · Tier 1 AI Strategist Liaison (provider-agnostic, Claude by default)
3
+ *
4
+ * Autonomously mediates between Tier 2 (Claude Code PM) and Tier 1 (Strategy Engine / Human).
5
+ * Inspects .ai/ specification contracts against runtime blockers and escalations,
6
+ * classifies risk (AUTO_RESOLVE vs REQUIRE_HUMAN_DECISION), and generates either
7
+ * verified specification patches or zero-jargon 4-part human decision cards.
8
+ *
9
+ * The model call goes through the provider chain in src/providers/ (Claude login, Claude API key,
10
+ * Gemini). When no provider is reachable, or every one fails, evaluation ends at the deterministic
11
+ * rules engine, so a rate limit or outage never blocks the pipeline.
12
+ */
13
+ import type { CandidatePatch } from '../governor/circuit-breaker.js';
14
+ import { type ProviderConfig, type ProviderDeps, type ProviderId } from '../providers/index.js';
15
+ export type RiskThreshold = 'safe_contracts_only' | 'all_non_destructive';
16
+ export type TriageClassification = 'AUTO_RESOLVE' | 'REQUIRE_HUMAN_DECISION';
17
+ export type TriageSource = 'claude' | 'gemini' | 'sandbox' | 'deterministic';
18
+ export interface TriageOption {
19
+ id: string;
20
+ label: string;
21
+ outcome: string;
22
+ description?: string;
23
+ recommended?: boolean;
24
+ }
25
+ export interface HumanDecisionCard {
26
+ symptom: string;
27
+ rootCause: string;
28
+ blastRadius: string;
29
+ options: TriageOption[];
30
+ }
31
+ export interface TriageResolution {
32
+ summary: string;
33
+ patch: CandidatePatch;
34
+ proof: {
35
+ passed: boolean;
36
+ };
37
+ proposalId?: string;
38
+ }
39
+ export interface TriageEvaluation {
40
+ ok: true;
41
+ escalationId: string;
42
+ taskId: string;
43
+ /** Provider that answered (`claude-cli`, `claude-api`, `gemini`), or `deterministic` when none did. */
44
+ provider: string;
45
+ model: string;
46
+ source: TriageSource;
47
+ latencyMs: number;
48
+ classification: TriageClassification;
49
+ riskLevel: 'low' | 'medium' | 'high' | 'critical';
50
+ reasoning: string;
51
+ autoPatchApplied: boolean;
52
+ guardrails: string[];
53
+ unblockedTaskId: string | null;
54
+ resolution?: TriageResolution;
55
+ humanCard?: HumanDecisionCard;
56
+ }
57
+ export interface TriageFailure {
58
+ ok: false;
59
+ escalationId: string;
60
+ error: string;
61
+ code?: string;
62
+ }
63
+ export interface TriageConfig {
64
+ autoTriageEnabled: boolean;
65
+ riskThreshold: RiskThreshold;
66
+ model: string;
67
+ }
68
+ export interface Tier1LiaisonOptions {
69
+ /** Pins the model. A `gemini-*` model restricts the chain to Gemini; a Claude model or alias excludes it. */
70
+ model?: string;
71
+ /** Legacy Gemini key override: implies the Gemini provider unless `provider` says otherwise. */
72
+ apiKey?: string;
73
+ /** Provider id(s) to use instead of the configured chain. */
74
+ provider?: ProviderId | ProviderId[];
75
+ /** Inline provider config instead of `.nativ/config.json`. */
76
+ providerConfig?: ProviderConfig;
77
+ /** Injectable spawn, fetch, SDK client and clock for tests. */
78
+ deps?: ProviderDeps;
79
+ config?: {
80
+ autoTriageEnabled?: boolean;
81
+ riskThreshold?: RiskThreshold;
82
+ };
83
+ mockEvaluation?: TriageEvaluation;
84
+ }
85
+ export interface TriageStatusResponse {
86
+ ok: true;
87
+ /** Provider a call would use right now, or `deterministic` when none is available. */
88
+ provider: string;
89
+ model: string;
90
+ autoTriageEnabled: boolean;
91
+ hasApiKey: boolean;
92
+ stats: {
93
+ totalEvaluated: number;
94
+ autoResolved: number;
95
+ escalatedToHuman: number;
96
+ };
97
+ }
98
+ /** Triage runs often and in the background, so it defaults to the cheapest Claude tier. */
99
+ export declare const DEFAULT_MODEL = "haiku";
100
+ /**
101
+ * Resolves the Google/Gemini API key from .env files or process.env.
102
+ * Zero-credential air-gap: the returned key is never stored in contracts or responses.
103
+ */
104
+ export declare function resolveGeminiApiKey(root?: string): string | null;
105
+ export declare function hasGeminiApiKey(root?: string): boolean;
106
+ /**
107
+ * Masks connection-string passwords, credential assignments and well-known key formats before
108
+ * text leaves the machine. `extraSecrets` (e.g. the Gemini key itself) are masked verbatim.
109
+ */
110
+ export declare function redactSecrets(text: string, extraSecrets?: string[]): string;
111
+ /**
112
+ * Tier 1 AI Strategist Liaison instance managing escalation evaluation,
113
+ * auto-patch application, and configuration.
114
+ */
115
+ export declare class Tier1Liaison {
116
+ private readonly root;
117
+ private readonly options;
118
+ private config;
119
+ private stats;
120
+ constructor(root: string, options?: Tier1LiaisonOptions);
121
+ /** Provider options after the legacy `apiKey` and `model` hints are folded in. */
122
+ private providerOptions;
123
+ /** Providers that could answer right now, in order. Empty means the deterministic engine handles it. */
124
+ private activeChain;
125
+ hasApiKey(): boolean;
126
+ getStatus(): TriageStatusResponse;
127
+ updateConfig(body: Record<string, unknown>): {
128
+ ok: true;
129
+ config: TriageConfig;
130
+ } | {
131
+ ok: false;
132
+ error: string;
133
+ };
134
+ evaluate(escalationId: string): Promise<TriageEvaluation | TriageFailure>;
135
+ }
136
+ /**
137
+ * Functional wrapper matching evaluateEscalation interface.
138
+ */
139
+ export declare function evaluateEscalation(root: string, escalationId: string, options?: Tier1LiaisonOptions): Promise<TriageEvaluation | TriageFailure>;
@@ -0,0 +1,320 @@
1
+ /**
2
+ * nativ · Tier 1 AI Strategist Liaison (provider-agnostic, Claude by default)
3
+ *
4
+ * Autonomously mediates between Tier 2 (Claude Code PM) and Tier 1 (Strategy Engine / Human).
5
+ * Inspects .ai/ specification contracts against runtime blockers and escalations,
6
+ * classifies risk (AUTO_RESOLVE vs REQUIRE_HUMAN_DECISION), and generates either
7
+ * verified specification patches or zero-jargon 4-part human decision cards.
8
+ *
9
+ * The model call goes through the provider chain in src/providers/ (Claude login, Claude API key,
10
+ * Gemini). When no provider is reachable, or every one fails, evaluation ends at the deterministic
11
+ * rules engine, so a rate limit or outage never blocks the pipeline.
12
+ */
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { MASK } from '../db/env-parser.js';
16
+ import { escalationPath, loadEscalationFile } from '../governor/store.js';
17
+ import { applySelfHealingProposal } from '../governor/index.js';
18
+ import { CLAUDE_CLI_DEFAULT_MODEL, completeWithChain, resolveAnthropicApiKey, resolveGeminiApiKey as resolveGeminiKey, resolveProviderChain, } from '../providers/index.js';
19
+ /** Triage runs often and in the background, so it defaults to the cheapest Claude tier. */
20
+ export const DEFAULT_MODEL = CLAUDE_CLI_DEFAULT_MODEL;
21
+ /** A stalled provider aborts here and the chain moves on, so triage never hangs on one vendor. */
22
+ const TRIAGE_TIMEOUT_MS = 20_000;
23
+ const TRIAGE_SYSTEM = 'You are Tier 1 Macro-Architect in a 3-Tier Multi-Agent Software Development Pipeline. Respond with strictly valid JSON only.';
24
+ /**
25
+ * Resolves the Google/Gemini API key from .env files or process.env.
26
+ * Zero-credential air-gap: the returned key is never stored in contracts or responses.
27
+ */
28
+ export function resolveGeminiApiKey(root = process.cwd()) {
29
+ return resolveGeminiKey(root);
30
+ }
31
+ export function hasGeminiApiKey(root = process.cwd()) {
32
+ return resolveGeminiApiKey(root) !== null;
33
+ }
34
+ const SECRET_PATTERNS = [
35
+ [/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, MASK],
36
+ // Connection strings: scheme://user:password@host keeps everything but the password.
37
+ // The user may be empty (redis://:password@host) and raw passwords may contain '/'.
38
+ [/\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@"']*:)[^\s@"']+@/gi, `$1${MASK}@`],
39
+ // key=value / "key": "value" credentials, including prefixed and suffixed names such as DB_PASSWORD,
40
+ // dbPassword or SECRET_KEY; contract field declarations such as "token": "string" stay readable.
41
+ [/\b(\w*(?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key)\w*["']?\s*[:=]\s*["']?)(?!(?:string|number|boolean|integer|object|array|null|true|false)\b)[^\s"'&,;]+/gi, `$1${MASK}`],
42
+ [/\b(Bearer\s+)[A-Za-z0-9._~+/-]+=*/g, `$1${MASK}`],
43
+ // JSON Web Tokens: header.payload.signature, each part base64url.
44
+ [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, MASK],
45
+ [/\bAIza[0-9A-Za-z_-]{35}\b/g, MASK],
46
+ [/\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}/g, MASK],
47
+ [/\bgh[pousr]_[A-Za-z0-9]{36,}\b/g, MASK],
48
+ [/\bAKIA[0-9A-Z]{16}\b/g, MASK],
49
+ ];
50
+ /**
51
+ * Masks connection-string passwords, credential assignments and well-known key formats before
52
+ * text leaves the machine. `extraSecrets` (e.g. the Gemini key itself) are masked verbatim.
53
+ */
54
+ export function redactSecrets(text, extraSecrets = []) {
55
+ let out = text;
56
+ for (const secret of extraSecrets) {
57
+ if (secret && secret.length >= 8)
58
+ out = out.split(secret).join(MASK);
59
+ }
60
+ for (const [pattern, replacement] of SECRET_PATTERNS)
61
+ out = out.replace(pattern, replacement);
62
+ return out;
63
+ }
64
+ function loadContracts(root) {
65
+ const read = (name) => {
66
+ const p = path.join(root, '.ai', name);
67
+ try {
68
+ return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ };
74
+ const readJson = (name) => {
75
+ const raw = read(name);
76
+ if (!raw)
77
+ return undefined;
78
+ try {
79
+ return JSON.parse(raw);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ };
85
+ return {
86
+ context: read('context.md') ?? undefined,
87
+ masterPlan: readJson('master_plan.json'),
88
+ dbSchema: readJson('db_schema.json'),
89
+ apiContracts: readJson('api_contracts.json'),
90
+ uiSpecs: read('ui_specs.md') ?? undefined,
91
+ };
92
+ }
93
+ /**
94
+ * Tier 1 AI Strategist Liaison instance managing escalation evaluation,
95
+ * auto-patch application, and configuration.
96
+ */
97
+ export class Tier1Liaison {
98
+ root;
99
+ options;
100
+ config;
101
+ stats = {
102
+ totalEvaluated: 0,
103
+ autoResolved: 0,
104
+ escalatedToHuman: 0,
105
+ };
106
+ constructor(root, options = {}) {
107
+ this.root = root;
108
+ this.options = options;
109
+ this.config = {
110
+ // Off by default: even a fix classified as safe waits for confirmation unless auto-triage is enabled.
111
+ autoTriageEnabled: options.config?.autoTriageEnabled ?? false,
112
+ riskThreshold: options.config?.riskThreshold ?? 'safe_contracts_only',
113
+ model: options.model ?? DEFAULT_MODEL,
114
+ };
115
+ }
116
+ /** Provider options after the legacy `apiKey` and `model` hints are folded in. */
117
+ providerOptions() {
118
+ const { apiKey, model } = this.options;
119
+ let provider = this.options.provider;
120
+ if (!provider && model) {
121
+ if (/^gemini/i.test(model))
122
+ provider = 'gemini';
123
+ else if (/^(claude|haiku|sonnet|opus)/i.test(model))
124
+ provider = ['claude-cli', 'claude-api'];
125
+ }
126
+ if (!provider && apiKey)
127
+ provider = 'gemini';
128
+ const deps = apiKey
129
+ ? { ...this.options.deps, env: { ...(this.options.deps?.env ?? process.env), GEMINI_API_KEY: apiKey } }
130
+ : this.options.deps;
131
+ return { provider, deps, config: this.options.providerConfig };
132
+ }
133
+ /** Providers that could answer right now, in order. Empty means the deterministic engine handles it. */
134
+ activeChain() {
135
+ return resolveProviderChain('triage', this.root, this.providerOptions());
136
+ }
137
+ hasApiKey() {
138
+ return this.activeChain().length > 0;
139
+ }
140
+ getStatus() {
141
+ const active = this.activeChain()[0];
142
+ return {
143
+ ok: true,
144
+ provider: active?.id ?? 'deterministic',
145
+ model: this.options.model ?? active?.defaultModel ?? this.config.model,
146
+ autoTriageEnabled: this.config.autoTriageEnabled,
147
+ hasApiKey: active !== undefined,
148
+ stats: { ...this.stats },
149
+ };
150
+ }
151
+ updateConfig(body) {
152
+ if (typeof body.autoTriageEnabled === 'boolean') {
153
+ this.config.autoTriageEnabled = body.autoTriageEnabled;
154
+ }
155
+ if (body.riskThreshold === 'safe_contracts_only' || body.riskThreshold === 'all_non_destructive') {
156
+ this.config.riskThreshold = body.riskThreshold;
157
+ }
158
+ return { ok: true, config: { ...this.config } };
159
+ }
160
+ async evaluate(escalationId) {
161
+ const startTime = Date.now();
162
+ const escalationFile = escalationPath(this.root);
163
+ if (!fs.existsSync(escalationFile)) {
164
+ return { ok: false, escalationId, error: 'ESCALATION_FILE_NOT_FOUND' };
165
+ }
166
+ let record = null;
167
+ try {
168
+ const data = loadEscalationFile(this.root, path.basename(this.root));
169
+ record = data.escalations.find((e) => e?.id === escalationId) ?? null;
170
+ }
171
+ catch (err) {
172
+ return { ok: false, escalationId, error: `Could not read escalation file: ${String(err)}` };
173
+ }
174
+ if (!record) {
175
+ return { ok: false, escalationId, error: `Escalation "${escalationId}" was not found.` };
176
+ }
177
+ const taskId = record.taskId || 'unknown';
178
+ const details = `${record.summary || ''} ${record.details || ''}`;
179
+ const isDestructive = /drop\s+table|drop\s+column|delete|truncate|destructive/i.test(details);
180
+ const isDrift = /schema\s+drift|contract\s+mismatch|unknown\s+property|api\s+contracts|syntax/i.test(details);
181
+ let classification = isDestructive ? 'REQUIRE_HUMAN_DECISION' : 'AUTO_RESOLVE';
182
+ let riskLevel = isDestructive ? 'high' : isDrift ? 'low' : 'medium';
183
+ let reasoning = isDestructive
184
+ ? 'Destructive operation detected. Paused for human authorization under zero-data-loss guardrails.'
185
+ : 'Contract alignment evaluated. Safe additive resolution approved.';
186
+ let humanCard;
187
+ let source = 'deterministic';
188
+ let provider = 'deterministic';
189
+ let model = this.options.model ?? this.config.model;
190
+ // 1. If any provider is reachable, ask it for a contextual evaluation; otherwise keep the deterministic verdict.
191
+ const providerOptions = this.providerOptions();
192
+ if (resolveProviderChain('triage', this.root, providerOptions).length > 0) {
193
+ try {
194
+ const contracts = loadContracts(this.root);
195
+ // Everything below leaves the machine: mask credentials (and every provider key) first.
196
+ const env = providerOptions.deps?.env ?? process.env;
197
+ const secrets = [resolveGeminiKey(this.root, env), resolveAnthropicApiKey(this.root, env)].filter((k) => k !== null);
198
+ const masked = (value) => redactSecrets(JSON.stringify(value, null, 2), secrets);
199
+ const prompt = `An autonomous agent escalated an issue:
200
+ ${masked(record)}
201
+
202
+ SPECIFICATION CONTRACTS:
203
+ - API Contracts: ${masked(contracts.apiContracts || {})}
204
+ - Database Schema: ${masked(contracts.dbSchema || {})}
205
+
206
+ Respond with strictly valid JSON:
207
+ {
208
+ "classification": "AUTO_RESOLVE" | "REQUIRE_HUMAN_DECISION",
209
+ "riskLevel": "low" | "medium" | "high" | "critical",
210
+ "reasoning": "...",
211
+ "humanCard": {
212
+ "symptom": "Plain English user experience symptom",
213
+ "rootCause": "Plain English root cause",
214
+ "blastRadius": "What is affected vs safe",
215
+ "options": [
216
+ { "id": "opt-a", "label": "Option A (Recommended)", "outcome": "...", "recommended": true },
217
+ { "id": "opt-b", "label": "Option B", "outcome": "...", "recommended": false }
218
+ ]
219
+ }
220
+ }`;
221
+ // Providers are tried in order; a rate limit puts one on cooldown and the next takes over.
222
+ const answer = await completeWithChain('triage', this.root, { system: TRIAGE_SYSTEM, prompt, json: true, timeoutMs: TRIAGE_TIMEOUT_MS, model: this.options.model }, providerOptions);
223
+ const parsed = JSON.parse(answer.text);
224
+ if (parsed.classification)
225
+ classification = parsed.classification;
226
+ if (parsed.riskLevel)
227
+ riskLevel = parsed.riskLevel;
228
+ if (parsed.reasoning)
229
+ reasoning = parsed.reasoning;
230
+ if (parsed.humanCard)
231
+ humanCard = parsed.humanCard;
232
+ provider = answer.provider;
233
+ model = answer.model;
234
+ source = answer.provider === 'gemini' ? 'gemini' : 'claude';
235
+ }
236
+ catch {
237
+ // Every provider failed or the reply was not JSON: keep the deterministic evaluation
238
+ }
239
+ }
240
+ if (classification === 'REQUIRE_HUMAN_DECISION' && !humanCard) {
241
+ humanCard = {
242
+ symptom: isDestructive
243
+ ? 'An automated database migration paused to prevent removing active customer data.'
244
+ : 'The autonomous runner encountered an architectural divergence and paused for decision.',
245
+ rootCause: isDestructive
246
+ ? 'The requested operation drops an existing table or column that may contain active user records.'
247
+ : 'A contract specification requires human clarification before the builder can proceed safely.',
248
+ blastRadius: 'All existing data and previously completed milestones remain completely intact.',
249
+ options: [
250
+ {
251
+ id: 'opt-safe',
252
+ label: 'Option A (Recommended): Safe Archive Column',
253
+ outcome: 'Rename the affected entity instead of permanently dropping it, keeping all user data safe.',
254
+ recommended: true,
255
+ },
256
+ {
257
+ id: 'opt-drop',
258
+ label: 'Option B: Confirm Permanent Drop',
259
+ outcome: 'Verify that this legacy entity is obsolete and proceed with the deletion.',
260
+ recommended: false,
261
+ },
262
+ ],
263
+ };
264
+ }
265
+ // Check if there is an isolated sandbox proposal available
266
+ let resolution;
267
+ let autoPatchApplied = false;
268
+ let unblockedTaskId = null;
269
+ if (record.proposedPatch) {
270
+ const patch = record.proposedPatch.kind === 'contract_patch' ? record.proposedPatch.candidate : record.proposedPatch;
271
+ resolution = {
272
+ summary: record.proposedPatch.strategy || 'Self-healing proposal',
273
+ patch,
274
+ proof: { passed: Boolean(record.proposedPatch.verificationProof?.passed) },
275
+ proposalId: record.proposedPatch.proposalId,
276
+ };
277
+ // Auto-apply if autoTriageEnabled and classification is AUTO_RESOLVE
278
+ if (this.config.autoTriageEnabled && classification === 'AUTO_RESOLVE') {
279
+ const app = applySelfHealingProposal(this.root, taskId, record.proposedPatch);
280
+ if (app.applied) {
281
+ autoPatchApplied = true;
282
+ unblockedTaskId = taskId;
283
+ }
284
+ }
285
+ }
286
+ this.stats.totalEvaluated++;
287
+ if (autoPatchApplied)
288
+ this.stats.autoResolved++;
289
+ else if (classification === 'REQUIRE_HUMAN_DECISION')
290
+ this.stats.escalatedToHuman++;
291
+ const latencyMs = Date.now() - startTime;
292
+ return {
293
+ ok: true,
294
+ escalationId,
295
+ taskId,
296
+ provider,
297
+ model,
298
+ source,
299
+ latencyMs,
300
+ classification,
301
+ riskLevel,
302
+ reasoning,
303
+ autoPatchApplied,
304
+ guardrails: [
305
+ 'Zero-credential air-gap enforced',
306
+ 'Human confirmation required for destructive operations',
307
+ ],
308
+ unblockedTaskId,
309
+ resolution,
310
+ humanCard: classification === 'REQUIRE_HUMAN_DECISION' ? humanCard : undefined,
311
+ };
312
+ }
313
+ }
314
+ /**
315
+ * Functional wrapper matching evaluateEscalation interface.
316
+ */
317
+ export async function evaluateEscalation(root, escalationId, options = {}) {
318
+ const liaison = new Tier1Liaison(root, options);
319
+ return liaison.evaluate(escalationId);
320
+ }
@@ -0,0 +1,52 @@
1
+ import { MasterPlan } from '../scanner/types.js';
2
+ export interface VerificationResult {
3
+ success: boolean;
4
+ exitCode: number;
5
+ command: string;
6
+ stdout: string;
7
+ stderr: string;
8
+ durationMs: number;
9
+ error?: string;
10
+ skipped?: boolean;
11
+ }
12
+ export interface TaskVerificationResult {
13
+ taskId: string;
14
+ title: string;
15
+ milestoneId: string;
16
+ milestoneName: string;
17
+ status: string;
18
+ result: VerificationResult;
19
+ }
20
+ export interface BatchVerificationResult {
21
+ projectName: string;
22
+ total: number;
23
+ passed: number;
24
+ failed: number;
25
+ skipped: number;
26
+ durationMs: number;
27
+ tasks: TaskVerificationResult[];
28
+ }
29
+ export declare function loadMasterPlan(targetDir: string): {
30
+ planPath: string;
31
+ plan: MasterPlan | null;
32
+ error?: string;
33
+ };
34
+ /**
35
+ * Execute a verification command in the workspace directory with timeout and output capture.
36
+ */
37
+ export declare function executeVerification(command: string, cwd: string, timeoutMs?: number): Promise<VerificationResult>;
38
+ /**
39
+ * Run verification for a single specific task in the plan.
40
+ */
41
+ export declare function verifyTask(taskId: string, targetDirArg?: string, options?: {
42
+ timeout?: number;
43
+ }): Promise<TaskVerificationResult>;
44
+ /**
45
+ * Verify a batch of tasks (by milestone, all completed tasks, or the active in-progress task).
46
+ */
47
+ export declare function verifyBatch(targetDirArg?: string, options?: {
48
+ taskId?: string;
49
+ milestoneId?: string;
50
+ all?: boolean;
51
+ timeout?: number;
52
+ }): Promise<BatchVerificationResult>;