@njeybe/nativ 2.4.1 → 2.5.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +26 -0
  3. package/dist/commands/db/db-formatter.d.ts +5 -0
  4. package/dist/commands/db/db-formatter.js +116 -0
  5. package/dist/commands/db/db-introspect.d.ts +11 -0
  6. package/dist/commands/db/db-introspect.js +84 -0
  7. package/dist/commands/db.js +4 -206
  8. package/dist/commands/profile.d.ts +3 -0
  9. package/dist/commands/profile.js +39 -0
  10. package/dist/commands/task/task-add.d.ts +11 -0
  11. package/dist/commands/task/task-add.js +169 -0
  12. package/dist/commands/task/task-common.d.ts +57 -0
  13. package/dist/commands/task/task-common.js +45 -0
  14. package/dist/commands/task/task-escalate.d.ts +7 -0
  15. package/dist/commands/task/task-escalate.js +119 -0
  16. package/dist/commands/task/task-lifecycle.d.ts +5 -0
  17. package/dist/commands/task/task-lifecycle.js +252 -0
  18. package/dist/commands/task/task-query.d.ts +10 -0
  19. package/dist/commands/task/task-query.js +269 -0
  20. package/dist/commands/task.d.ts +9 -87
  21. package/dist/commands/task.js +8 -850
  22. package/dist/commands/triage/triage-store.d.ts +56 -0
  23. package/dist/commands/triage/triage-store.js +108 -0
  24. package/dist/commands/triage/triage-ui.d.ts +14 -0
  25. package/dist/commands/triage/triage-ui.js +104 -0
  26. package/dist/commands/triage.d.ts +3 -52
  27. package/dist/commands/triage.js +8 -216
  28. package/dist/core/enforcement.d.ts +1 -0
  29. package/dist/core/enforcement.js +19 -0
  30. package/dist/core/human-gate.d.ts +2 -1
  31. package/dist/core/human-gate.js +9 -2
  32. package/dist/core/profiles.d.ts +14 -0
  33. package/dist/core/profiles.js +82 -0
  34. package/dist/core/setup/config-merger.d.ts +59 -0
  35. package/dist/core/setup/config-merger.js +107 -0
  36. package/dist/core/setup/session-context.d.ts +4 -0
  37. package/dist/core/setup/session-context.js +65 -0
  38. package/dist/core/setup/setup-planner.d.ts +14 -0
  39. package/dist/core/setup/setup-planner.js +180 -0
  40. package/dist/core/setup/template-planner.d.ts +8 -0
  41. package/dist/core/setup/template-planner.js +32 -0
  42. package/dist/core/setup/types.d.ts +65 -0
  43. package/dist/core/setup/types.js +55 -0
  44. package/dist/core/setup-assets.d.ts +8 -119
  45. package/dist/core/setup-assets.js +7 -437
  46. package/dist/core/tier1-liaison.d.ts +2 -0
  47. package/dist/core/tier1-liaison.js +64 -3
  48. package/dist/core/verifier/executor.d.ts +5 -0
  49. package/dist/core/verifier/executor.js +62 -0
  50. package/dist/core/verifier/phases.d.ts +5 -0
  51. package/dist/core/verifier/phases.js +71 -0
  52. package/dist/core/verifier/task-verifier.d.ts +37 -0
  53. package/dist/core/verifier/task-verifier.js +233 -0
  54. package/dist/core/verifier/types.d.ts +51 -0
  55. package/dist/core/verifier/types.js +1 -0
  56. package/dist/core/verifier.d.ts +5 -93
  57. package/dist/core/verifier.js +5 -359
  58. package/dist/db/introspector.js +59 -59
  59. package/dist/governor/evaluator.d.ts +4 -64
  60. package/dist/governor/evaluator.js +21 -686
  61. package/dist/governor/patch-applier.d.ts +8 -0
  62. package/dist/governor/patch-applier.js +121 -0
  63. package/dist/governor/self-healing.d.ts +24 -0
  64. package/dist/governor/self-healing.js +351 -0
  65. package/dist/governor/test-integrity.d.ts +41 -0
  66. package/dist/governor/test-integrity.js +237 -0
  67. package/dist/index.js +8 -0
  68. package/dist/mcp/capture.d.ts +4 -0
  69. package/dist/mcp/capture.js +33 -0
  70. package/dist/mcp/resources.d.ts +39 -0
  71. package/dist/mcp/resources.js +44 -0
  72. package/dist/mcp/server.d.ts +11 -0
  73. package/dist/mcp/server.js +14 -89
  74. package/dist/mcp/tools.d.ts +1 -0
  75. package/dist/mcp/tools.js +1 -0
  76. package/dist/runner/agent-supervisor.d.ts +8 -302
  77. package/dist/runner/agent-supervisor.js +41 -1030
  78. package/dist/runner/cli-config.d.ts +84 -0
  79. package/dist/runner/cli-config.js +215 -0
  80. package/dist/runner/native-driver.d.ts +18 -0
  81. package/dist/runner/native-driver.js +208 -0
  82. package/dist/runner/native-engine.d.ts +27 -0
  83. package/dist/runner/native-engine.js +184 -0
  84. package/dist/runner/native-tools.d.ts +31 -0
  85. package/dist/runner/native-tools.js +241 -0
  86. package/dist/runner/process-utils.d.ts +12 -0
  87. package/dist/runner/process-utils.js +96 -0
  88. package/dist/runner/run-lifecycle.d.ts +6 -0
  89. package/dist/runner/run-lifecycle.js +42 -0
  90. package/dist/runner/types.d.ts +215 -0
  91. package/dist/runner/types.js +41 -0
  92. package/dist/scanner/detector.js +42 -663
  93. package/dist/scanner/detectors/ecosystem-detector.d.ts +16 -0
  94. package/dist/scanner/detectors/ecosystem-detector.js +354 -0
  95. package/dist/scanner/detectors/orm-detector.d.ts +5 -0
  96. package/dist/scanner/detectors/orm-detector.js +59 -0
  97. package/dist/scanner/detectors/workspace-detector.d.ts +19 -0
  98. package/dist/scanner/detectors/workspace-detector.js +266 -0
  99. package/dist/server/cli-runner.d.ts +15 -0
  100. package/dist/server/cli-runner.js +43 -0
  101. package/dist/server/event-hub.d.ts +42 -0
  102. package/dist/server/event-hub.js +169 -0
  103. package/dist/server/http-utils.d.ts +15 -0
  104. package/dist/server/http-utils.js +56 -0
  105. package/dist/server/plan-utils.d.ts +9 -0
  106. package/dist/server/plan-utils.js +31 -0
  107. package/dist/server/routes/db-routes.d.ts +57 -0
  108. package/dist/server/routes/db-routes.js +484 -0
  109. package/dist/server/routes/task-routes.d.ts +266 -0
  110. package/dist/server/routes/task-routes.js +285 -0
  111. package/dist/server/routes/telemetry-routes.d.ts +128 -0
  112. package/dist/server/routes/telemetry-routes.js +179 -0
  113. package/dist/server/routes/triage-routes.d.ts +81 -0
  114. package/dist/server/routes/triage-routes.js +244 -0
  115. package/dist/server/routes/worktree-routes.d.ts +31 -0
  116. package/dist/server/routes/worktree-routes.js +144 -0
  117. package/dist/server/studio-server.d.ts +4 -11
  118. package/dist/server/studio-server.js +34 -1578
  119. package/dist/server/studio-ui/shell/pipeline-state.js +2 -0
  120. package/dist/server/studio-ui/shell/triage.js +10 -0
  121. package/package.json +2 -2
  122. package/plugin/.claude-plugin/plugin.json +1 -1
  123. package/plugin/agents/architect.md +4 -2
  124. package/plugin/agents/verifier.md +1 -1
  125. package/plugin/agents/worker.md +7 -4
  126. package/plugin/skills/nativ/SKILL.md +1 -0
  127. package/templates/.claudeignore +62 -45
  128. package/templates/AGENTS.md +1 -0
  129. package/templates/CLAUDE.md +13 -0
  130. package/templates/claude-agents/architect.md +4 -2
  131. package/templates/claude-agents/verifier.md +1 -1
  132. package/templates/claude-agents/worker.md +7 -4
  133. package/templates/dot-ai/api_contracts.json +66 -66
  134. package/templates/dot-ai/context.md +38 -38
  135. package/templates/dot-ai/db_schema.json +39 -39
  136. package/templates/dot-ai/subagents/architect.md +54 -0
  137. package/dist/commands/session.d.ts +0 -22
  138. package/dist/commands/session.js +0 -106
  139. package/dist/core/session-memory.d.ts +0 -42
  140. package/dist/core/session-memory.js +0 -121
@@ -1,852 +1,10 @@
1
- import path from 'node:path';
2
- import pc from 'picocolors';
3
- import { isSubagentType, SUBAGENT_TYPES, validateMasterPlanTask, } from '../scanner/types.js';
4
- import { runTaskVerification } from '../core/verifier.js';
5
- import { ContractGovernor, CircuitBreaker, TestIntegrityGuard, resolveGitHead, appendEscalation, taskEscalationHistory, closeEscalationsForTask, } from '../governor/index.js';
6
- import { loadPlan, savePlan, withPlanLock } from '../core/lock-manager.js';
7
- import { recordTaskStart, recordTaskComplete } from '../core/telemetry.js';
8
- import { resolveSpecSlices } from '../core/spec-slices.js';
9
- import { resolveWorkerModel } from '../core/model-routing.js';
10
- import { addTaskUnlock, removeTaskUnlock } from '../core/enforcement.js';
11
- import { refuseHeadless } from '../core/human-gate.js';
12
- import { learningsForTask } from '../core/learnings.js';
13
- export { loadPlan, savePlan, withPlanLock };
14
- function getPlanPath(targetDirArg) {
15
- const targetDir = path.resolve(targetDirArg || process.cwd());
16
- const planPath = path.join(targetDir, '.ai', 'master_plan.json');
17
- return { targetDir, planPath };
18
- }
19
- export function getRoleGuide(assignedSubagent) {
20
- const normalized = assignedSubagent
21
- .replace(/-agent$/, '')
22
- .replace(/^qa$/, 'qa-tester');
23
- const fileMap = {
24
- backend: 'backend.md',
25
- frontend: 'frontend.md',
26
- database: 'database.md',
27
- 'qa-tester': 'qa-tester.md',
28
- 'flutter-developer': 'flutter-developer.md',
29
- 'devops-agent': 'devops-agent.md',
30
- 'security-auditor': 'security-auditor.md',
31
- 'db-migration': 'db-migration.md',
32
- };
33
- const filename = fileMap[assignedSubagent] || fileMap[normalized] || `${normalized}.md`;
34
- return `.ai/subagents/${filename}`;
35
- }
36
- export function getRecommendedContractSlice(assignedSubagent) {
37
- const sub = assignedSubagent.toLowerCase();
38
- if (sub.includes('frontend') || sub.includes('flutter')) {
39
- return '.ai/ui_specs.md (UI tokens & hierarchy) + .ai/api_contracts.json (Client endpoint integration)';
40
- }
41
- if (sub.includes('backend')) {
42
- return '.ai/api_contracts.json (Endpoint routes, request/response schemas) + .ai/db_schema.json (Models)';
43
- }
44
- if (sub.includes('database') || sub.includes('db-migration')) {
45
- return '.ai/db_schema.json (Database schema contract & models)';
46
- }
47
- if (sub.includes('devops')) {
48
- return '.ai/context.md (Infrastructure, runtime & deployment guardrails)';
49
- }
50
- if (sub.includes('security')) {
51
- return 'Target files & dependency manifests (.ai/context.md security guardrails)';
52
- }
53
- if (sub.includes('qa')) {
54
- return 'Target files, .ai/api_contracts.json, and verification test suite';
55
- }
56
- return '.ai/context.md';
57
- }
58
- export async function runTaskList(targetDirArg, options = {}) {
59
- const { planPath } = getPlanPath(targetDirArg);
60
- const plan = loadPlan(planPath);
61
- if (!plan) {
62
- process.exitCode = 1;
63
- return;
64
- }
65
- const completedTaskIds = new Set();
66
- for (const m of plan.milestones) {
67
- for (const t of m.tasks) {
68
- if (t.status === 'completed') {
69
- completedTaskIds.add(t.id);
70
- }
71
- }
72
- }
73
- const results = [];
74
- for (const m of plan.milestones) {
75
- if (options.milestone && m.id !== options.milestone && !m.name.toLowerCase().includes(options.milestone.toLowerCase())) {
76
- continue;
77
- }
78
- for (const t of m.tasks) {
79
- const deps = t.dependencies || [];
80
- const depsSatisfied = deps.every((d) => completedTaskIds.has(d));
81
- const isAvailable = (t.status === 'pending' || t.status === 'in_progress') && depsSatisfied;
82
- if (options.available && !isAvailable) {
83
- continue;
84
- }
85
- if (options.status && t.status !== options.status) {
86
- continue;
87
- }
88
- if (options.fastPath && t.fastPath !== true && m.fastPath !== true) {
89
- continue;
90
- }
91
- results.push({
92
- task: t,
93
- milestoneId: m.id,
94
- milestoneName: m.name,
95
- isAvailable,
96
- });
97
- }
98
- }
99
- if (options.json) {
100
- const jsonOutput = results.map(({ task, milestoneId, milestoneName, isAvailable }) => ({
101
- ...task,
102
- milestoneId,
103
- milestoneName,
104
- isAvailable,
105
- roleGuide: getRoleGuide(task.assignedSubagent),
106
- contractSlice: getRecommendedContractSlice(task.assignedSubagent),
107
- }));
108
- console.log(JSON.stringify(jsonOutput, null, 2));
109
- return;
110
- }
111
- console.log(pc.bold(pc.cyan(`\n📋 nativ Tasks: ${plan.projectName}`)));
112
- console.log(pc.dim(`Overall Status: ${plan.overallStatus.toUpperCase()} | Active Milestone: ${plan.activeMilestoneId}\n`));
113
- if (results.length === 0) {
114
- console.log(pc.yellow(' No tasks match the specified filters.\n'));
115
- return;
116
- }
117
- const grouped = new Map();
118
- for (const item of results) {
119
- const key = `[${item.milestoneId}] ${item.milestoneName}`;
120
- if (!grouped.has(key)) {
121
- grouped.set(key, []);
122
- }
123
- grouped.get(key).push(item);
124
- }
125
- for (const [milestoneTitle, items] of grouped.entries()) {
126
- console.log(pc.bold(`🚩 Milestone ${milestoneTitle}`));
127
- for (const { task: t, isAvailable } of items) {
128
- let statusBadge = pc.gray('○ pending');
129
- if (t.status === 'completed') {
130
- statusBadge = pc.green('✔ completed');
131
- }
132
- else if (t.status === 'in_progress') {
133
- statusBadge = pc.yellow('▶ in_progress');
134
- }
135
- else if (t.status === 'blocked') {
136
- statusBadge = pc.red('✖ blocked');
137
- }
138
- const readyBadge = isAvailable && t.status === 'pending' ? pc.bgGreen(pc.black(' READY ')) + ' ' : '';
139
- const fastBadge = t.fastPath ? pc.magenta('[fast path] ') : '';
140
- console.log(` ${statusBadge} ${readyBadge}${fastBadge}${pc.bold(t.id)}: ${t.title}`);
141
- const depText = t.dependencies && t.dependencies.length > 0 ? pc.dim(`deps: ${t.dependencies.join(', ')}`) : pc.dim('no deps');
142
- const filesText = t.targetFiles && t.targetFiles.length > 0 ? pc.cyan(t.targetFiles.join(', ')) : pc.dim('none');
143
- console.log(pc.dim(` Agent: `) + pc.magenta(`[${t.assignedSubagent}]`) + pc.dim(` | ${depText} | Files: ${filesText}`));
144
- if (t.notes) {
145
- console.log(pc.dim(` Note: `) + (t.status === 'blocked' ? pc.red(t.notes) : pc.dim(t.notes)));
146
- }
147
- }
148
- console.log();
149
- }
150
- const completedCount = results.filter((r) => r.task.status === 'completed').length;
151
- const inProgressCount = results.filter((r) => r.task.status === 'in_progress').length;
152
- const pendingCount = results.filter((r) => r.task.status === 'pending').length;
153
- const blockedCount = results.filter((r) => r.task.status === 'blocked').length;
154
- console.log(pc.dim(`Showing ${results.length} tasks: `) +
155
- pc.green(`${completedCount} completed`) + pc.dim(', ') +
156
- pc.yellow(`${inProgressCount} in progress`) + pc.dim(', ') +
157
- pc.gray(`${pendingCount} pending`) + pc.dim(', ') +
158
- pc.red(`${blockedCount} blocked`) + '\n');
159
- }
160
- export async function runTaskNext(targetDirArg, options = {}) {
161
- const { planPath } = getPlanPath(targetDirArg);
162
- const plan = loadPlan(planPath);
163
- if (!plan) {
164
- process.exitCode = 1;
165
- return;
166
- }
167
- // Completed task lookup for dependency check
168
- const completedTaskIds = new Set();
169
- for (const m of plan.milestones) {
170
- for (const t of m.tasks) {
171
- if (t.status === 'completed') {
172
- completedTaskIds.add(t.id);
173
- }
174
- }
175
- }
176
- // ── Prioritize Fast-Path tasks ─────────────────────────────────────────────
177
- // If any fast-path task is in_progress or ready to execute, prioritize it immediately!
178
- let fastPathCandidate = null;
179
- for (const m of plan.milestones) {
180
- for (const t of m.tasks) {
181
- if (t.fastPath) {
182
- if (t.status === 'in_progress') {
183
- fastPathCandidate = { task: t, milestone: m };
184
- break;
185
- }
186
- if (t.status === 'pending') {
187
- const deps = t.dependencies || [];
188
- if (deps.every((depId) => completedTaskIds.has(depId))) {
189
- if (!fastPathCandidate) {
190
- fastPathCandidate = { task: t, milestone: m };
191
- }
192
- }
193
- }
194
- }
195
- }
196
- if (fastPathCandidate && fastPathCandidate.task.status === 'in_progress')
197
- break;
198
- }
199
- let activeMilestone;
200
- let targetTask = null;
201
- if (fastPathCandidate) {
202
- activeMilestone = fastPathCandidate.milestone;
203
- targetTask = fastPathCandidate.task;
204
- }
205
- else {
206
- activeMilestone = plan.milestones.find((m) => m.id === plan.activeMilestoneId);
207
- if (!activeMilestone || activeMilestone.status === 'completed') {
208
- activeMilestone = plan.milestones.find((m) => m.status !== 'completed');
209
- if (activeMilestone) {
210
- plan.activeMilestoneId = activeMilestone.id;
211
- await withPlanLock(planPath, (p) => {
212
- p.activeMilestoneId = activeMilestone.id;
213
- });
214
- }
215
- }
216
- if (!activeMilestone) {
217
- if (options.json) {
218
- console.log(JSON.stringify({ status: 'all_milestones_completed', activeTask: null }, null, 2));
219
- }
220
- else {
221
- console.log(pc.bold(pc.green('\n🎉 All milestones and tasks are completed!\n')));
222
- console.log(pc.dim('Run `nativ status` to inspect final project statistics.\n'));
223
- }
224
- return;
225
- }
226
- // 1. Look for in_progress task first
227
- targetTask = activeMilestone.tasks.find((t) => t.status === 'in_progress') || null;
228
- // 2. If none in progress, find first pending task whose dependencies are satisfied
229
- if (!targetTask) {
230
- targetTask = activeMilestone.tasks.find((t) => {
231
- if (t.status !== 'pending')
232
- return false;
233
- const deps = t.dependencies || [];
234
- return deps.every((depId) => completedTaskIds.has(depId));
235
- }) || null;
236
- }
237
- // 3. If still none found, check if milestone has blocked tasks or waiting dependencies
238
- if (!targetTask) {
239
- const hasBlocked = activeMilestone.tasks.some((t) => t.status === 'blocked');
240
- if (options.json) {
241
- console.log(JSON.stringify({
242
- status: hasBlocked ? 'milestone_blocked' : 'waiting_dependencies',
243
- milestoneId: activeMilestone.id,
244
- milestoneName: activeMilestone.name,
245
- activeTask: null,
246
- }, null, 2));
247
- }
248
- else {
249
- if (hasBlocked) {
250
- console.log(pc.bold(pc.red(`\n✖ Milestone [${activeMilestone.id}] has blocked tasks.`)));
251
- console.log(pc.yellow('Run `nativ status` to review blockers.\n'));
252
- }
253
- else {
254
- console.log(pc.bold(pc.yellow(`\n⚠ Milestone [${activeMilestone.id}] has no immediately executable tasks due to unresolved dependencies.\n`)));
255
- }
256
- }
257
- return;
258
- }
259
- }
260
- const roleGuide = getRoleGuide(targetTask.assignedSubagent);
261
- const contractSlice = getRecommendedContractSlice(targetTask.assignedSubagent);
262
- const projectRoot = path.dirname(path.dirname(planPath));
263
- const learnings = learningsForTask(projectRoot, targetTask);
264
- const priorEscalations = taskEscalationHistory(projectRoot, targetTask.id);
265
- if (options.json) {
266
- const specResult = targetTask.specRefs?.length
267
- ? resolveSpecSlices(projectRoot, targetTask.specRefs)
268
- : null;
269
- const recommendedModel = resolveWorkerModel(projectRoot, targetTask);
270
- console.log(JSON.stringify({
271
- status: 'ready',
272
- milestoneId: activeMilestone.id,
273
- milestoneName: activeMilestone.name,
274
- task: {
275
- ...targetTask,
276
- roleGuide,
277
- recommendedContractSlice: contractSlice,
278
- ...(recommendedModel ? { recommendedModel } : {}),
279
- ...(specResult ? { specSlices: specResult.slices, ...(specResult.warnings.length ? { specWarnings: specResult.warnings } : {}) } : {}),
280
- ...(learnings.length ? { learnings: learnings.map(toTaskLearning) } : {}),
281
- ...(priorEscalations.length ? { priorEscalations } : {}),
282
- }
283
- }, null, 2));
284
- return;
285
- }
286
- const fastTag = targetTask.fastPath ? pc.cyan(' [FAST-PATH PRIORITY]') : '';
287
- console.log(pc.bold(pc.cyan(`\n⚡ Next Executable Task in [${activeMilestone.id} - ${activeMilestone.name}]:`)) + fastTag);
288
- console.log(pc.bold(`\n Task ID: `) + pc.yellow(targetTask.id));
289
- console.log(pc.bold(` Title: `) + pc.white(targetTask.title));
290
- console.log(pc.bold(` Status: `) + (targetTask.status === 'in_progress' ? pc.yellow('▶ in_progress') : pc.gray('○ pending')));
291
- if (targetTask.fastPath) {
292
- console.log(pc.bold(` Track: `) + pc.cyan('⚡ fast-path (prioritized execution)'));
293
- }
294
- console.log(pc.bold(` Assigned Role: `) + pc.magenta(`[${targetTask.assignedSubagent}]`) + pc.dim(` -> ${roleGuide}`));
295
- console.log(pc.bold(` JIT Contract Slice:`) + pc.green(` ${contractSlice}`));
296
- console.log(pc.bold(` Target Files: `) + (targetTask.targetFiles.length > 0 ? pc.cyan(targetTask.targetFiles.join(', ')) : pc.dim('none specified')));
297
- console.log(pc.bold(` Verification Cmd: `) + pc.yellow(`\`${targetTask.verificationCommand || 'none'}\``));
298
- if (targetTask.dependencies && targetTask.dependencies.length > 0) {
299
- console.log(pc.bold(` Dependencies: `) + pc.dim(targetTask.dependencies.join(', ')));
300
- }
301
- if (targetTask.notes) {
302
- console.log(pc.bold(` Notes: `) + pc.red(targetTask.notes));
303
- }
304
- for (const l of learnings) {
305
- console.log(pc.bold(` Learning: `) + pc.white(`${l.id} ${l.insight}`));
306
- }
307
- for (const e of priorEscalations) {
308
- const outcome = e.resolutionNotes ? ` -> ${e.resolutionNotes}` : '';
309
- console.log(pc.bold(` Past escalation: `) + pc.white(`${e.id} ${e.status}: ${e.summary}${outcome}`));
310
- }
311
- console.log(pc.dim('\n─── Quick CLI Actions ──────────────────────────────────────────'));
312
- console.log(pc.dim(' Start task: ') + pc.white(`nativ task start ${targetTask.id}`));
313
- console.log(pc.dim(' Complete task:') + pc.white(`nativ task complete ${targetTask.id}`));
314
- console.log(pc.dim(' Block task: ') + pc.white(`nativ task block ${targetTask.id} --reason "..."`));
315
- console.log(pc.dim(' Escalate task:') + pc.white(`nativ task escalate ${targetTask.id} --type schema_flaw --details "..."\n`));
316
- }
317
- /** What a worker needs from a lesson; status and audit fields stay in .ai/learnings.json. */
318
- function toTaskLearning({ id, insight, details, files }) {
319
- return { id, insight, ...(details ? { details } : {}), ...(files ? { files } : {}) };
320
- }
321
- export async function runTaskStart(taskId, targetDirArg) {
322
- const { targetDir, planPath } = getPlanPath(targetDirArg);
323
- return withPlanLock(planPath, async (plan, ctx) => {
324
- let foundTask = null;
325
- let foundMilestone = null;
326
- for (const m of plan.milestones) {
327
- const t = m.tasks.find((task) => task.id === taskId);
328
- if (t) {
329
- foundTask = t;
330
- foundMilestone = m;
331
- break;
332
- }
333
- }
334
- if (!foundTask) {
335
- ctx.abort();
336
- console.error(pc.red(`\n✖ Task [${taskId}] not found in .ai/master_plan.json\n`));
337
- process.exitCode = 1;
338
- return;
339
- }
340
- foundTask.status = 'in_progress';
341
- if (foundMilestone.status === 'pending') {
342
- foundMilestone.status = 'in_progress';
343
- }
344
- if (plan.overallStatus === 'pending') {
345
- plan.overallStatus = 'in_progress';
346
- }
347
- plan.activeMilestoneId = foundMilestone.id;
348
- // Pin the commit this task starts from so completion can prove the test
349
- // suites that existed here were not deleted or weakened.
350
- const baseline = resolveGitHead(targetDir);
351
- if (baseline)
352
- CircuitBreaker.recordBaseline(targetDir, taskId, baseline);
353
- try {
354
- await recordTaskStart(targetDir, foundTask);
355
- }
356
- catch {
357
- // Non-fatal
358
- }
359
- console.log(pc.green(`\n✔ Task [${pc.bold(taskId)}] marked as `) + pc.yellow('▶ in_progress') + '\n');
360
- });
361
- }
362
1
  /**
363
- * Lifts role enforcement's target-file scope for one task (emergency escape hatch), or restores it with
364
- * --revoke. Contract and secret paths stay protected either way. Deliberately CLI-only and not an MCP
365
- * tool: an agent must not be able to remove its own guardrail.
2
+ * Barrel exports for task subcommands.
3
+ * Re-exports all public APIs to preserve backward compatibility.
366
4
  */
367
- export async function runTaskUnlock(taskId, targetDirArg, options = {}) {
368
- if (!options.revoke && refuseHeadless('nativ task unlock'))
369
- return;
370
- const { targetDir, planPath } = getPlanPath(targetDirArg);
371
- const plan = loadPlan(planPath);
372
- if (!plan) {
373
- process.exitCode = 1;
374
- return;
375
- }
376
- if (!plan.milestones.some((m) => m.tasks.some((t) => t.id === taskId))) {
377
- console.error(pc.red(`\n✖ Task [${taskId}] not found in .ai/master_plan.json\n`));
378
- process.exitCode = 1;
379
- return;
380
- }
381
- if (options.revoke) {
382
- const removed = removeTaskUnlock(targetDir, taskId);
383
- console.log(removed ? pc.green(`\n✔ Task [${taskId}] re-locked to its target files.\n`) : pc.dim(`\nTask [${taskId}] was not unlocked.\n`));
384
- return;
385
- }
386
- addTaskUnlock(targetDir, taskId, options.reason?.trim() || undefined);
387
- console.log(pc.yellow(`\n⚠ Task [${pc.bold(taskId)}] unlocked: its agent may edit files outside targetFiles.`));
388
- console.log(pc.dim(' .ai/ contracts and secret files stay protected. Re-lock with `nativ task unlock ' + taskId + ' --revoke`.\n'));
389
- }
390
- export async function runTaskComplete(taskId, targetDirArg, options = {}) {
391
- if (options.skipVerify && refuseHeadless('nativ task complete --no-verify'))
392
- return;
393
- const { targetDir, planPath } = getPlanPath(targetDirArg);
394
- const plan = loadPlan(planPath);
395
- if (!plan) {
396
- process.exitCode = 1;
397
- return;
398
- }
399
- let foundTask = null;
400
- let foundMilestone = null;
401
- for (const m of plan.milestones) {
402
- const t = m.tasks.find((task) => task.id === taskId);
403
- if (t) {
404
- foundTask = t;
405
- foundMilestone = m;
406
- break;
407
- }
408
- }
409
- if (!foundTask) {
410
- console.error(pc.red(`\n✖ Task [${taskId}] not found in .ai/master_plan.json\n`));
411
- process.exitCode = 1;
412
- return;
413
- }
414
- // ── Test-Integrity Invariant ───────────────────────────────────────────────
415
- // Runs before verification: a suite that was deleted or weakened can pass
416
- // trivially. `--no-verify` bypasses it only from an interactive terminal, so
417
- // headless agents cannot use the emergency override to skip it.
418
- const integrity = TestIntegrityGuard.evaluate(targetDir, taskId);
419
- if (!integrity.approved) {
420
- const humanOverride = Boolean(options.skipVerify && process.stdin.isTTY);
421
- if (humanOverride) {
422
- console.log(pc.yellow(`\n⚠ Test-integrity violations overridden via --no-verify for [${taskId}]:`));
423
- integrity.findings.forEach((f) => console.log(pc.yellow(` • ${f.detail}`)));
424
- }
425
- else {
426
- console.error(pc.red(`\n✖ Task [${pc.bold(taskId)}] cannot complete: ${integrity.message}`));
427
- integrity.findings.forEach((f) => console.error(pc.red(` • [${f.rule}] ${f.detail}`)));
428
- console.error(pc.dim(` Compared against ${integrity.baselineRecorded ? 'the commit recorded at task start' : 'HEAD'} (${integrity.baselineRef?.slice(0, 12)}).`));
429
- const affected = [...new Set(integrity.findings.map((f) => f.path))];
430
- const { state, escalationId, proposal } = CircuitBreaker.recordGuardViolation(targetDir, taskId, integrity, { escalationType: 'architectural_ambiguity', affected, intent: `Complete task ${taskId} with modified test suites` }, () => TestIntegrityGuard.proposeRestoration(targetDir, integrity));
431
- console.error(pc.yellow(` Circuit Breaker: ${state.consecutiveFailures}/${state.maxThreshold} failures`));
432
- if (state.tripped) {
433
- console.error(pc.bold(pc.red(` 🛑 CIRCUIT BREAKER TRIPPED: Task locked to blocked.`)));
434
- console.error(pc.magenta(` Escalation record written to .ai/escalation.json (${escalationId})`));
435
- if (proposal?.kind === 'restore_tests') {
436
- console.error(pc.magenta(` Self-healing proposal ${proposal.proposalId}: ${proposal.commands[0]}\n`));
437
- }
438
- }
439
- else {
440
- console.error(pc.white(' Restore the tests (or strengthen them) and retry. If removing them is intended, escalate:'));
441
- console.error(pc.white(` nativ task escalate ${taskId} --type architectural_ambiguity --details "..."\n`));
442
- }
443
- process.exitCode = 1;
444
- return;
445
- }
446
- }
447
- // ── Verification Gatekeeper ────────────────────────────────────────────────
448
- let vResult = null;
449
- if (!options.skipVerify) {
450
- vResult = await runTaskVerification(foundTask, { cwd: targetDir, timeout: options.timeout });
451
- if (!vResult.success) {
452
- console.error(pc.red(`\n✖ Task [${pc.bold(taskId)}] verification FAILED with exit code ${vResult.exitCode}:`));
453
- const failedPhase = vResult.phases?.find((p) => !p.success);
454
- if (failedPhase)
455
- console.error(pc.yellow(` Phase: ${failedPhase.name}`));
456
- console.error(pc.yellow(` Command: \`${vResult.command}\``));
457
- if (vResult.stderr && vResult.stderr.trim()) {
458
- console.error(pc.red('\n--- stderr ---'));
459
- console.error(pc.red(vResult.stderr.trim()));
460
- }
461
- if (vResult.stdout && vResult.stdout.trim() && !vResult.stderr?.trim()) {
462
- console.error(pc.dim('\n--- stdout ---'));
463
- console.error(pc.dim(vResult.stdout.trim()));
464
- }
465
- if (vResult.error && !vResult.stderr?.includes(vResult.error)) {
466
- console.error(pc.red(`\nError: ${vResult.error}`));
467
- }
468
- console.error(pc.yellow(`\n⚠ Task remains '${foundTask.status}'. Fix the issue and retry:`));
469
- console.error(pc.white(` nativ task complete ${taskId}`));
470
- console.error(pc.dim(' (or pass --no-verify to bypass verification check)\n'));
471
- process.exitCode = 1;
472
- return;
473
- }
474
- if (vResult.skipped) {
475
- console.log(pc.dim(`\n○ Verification skipped: no verification command specified for [${taskId}]`));
476
- }
477
- else {
478
- const elapsed = (vResult.durationMs / 1000).toFixed(2);
479
- console.log(pc.green(`\n✔ Verification passed (${elapsed}s): `) + pc.yellow(`\`${foundTask.verificationCommand}\``));
480
- }
481
- }
482
- else {
483
- vResult = {
484
- command: foundTask.verificationCommand || 'none',
485
- durationMs: 0,
486
- exitCode: 0,
487
- stdout: '',
488
- stderr: '',
489
- skipped: true,
490
- success: true,
491
- };
492
- console.log(pc.yellow(`\n⚠ Verification skipped via --no-verify for [${taskId}]`));
493
- }
494
- await withPlanLock(planPath, (plan, ctx) => {
495
- let taskToComplete = null;
496
- let milestoneToComplete = null;
497
- for (const m of plan.milestones) {
498
- const t = m.tasks.find((task) => task.id === taskId);
499
- if (t) {
500
- taskToComplete = t;
501
- milestoneToComplete = m;
502
- break;
503
- }
504
- }
505
- if (!taskToComplete) {
506
- ctx.abort();
507
- console.error(pc.red(`\n✖ Task [${taskId}] not found in .ai/master_plan.json\n`));
508
- process.exitCode = 1;
509
- return;
510
- }
511
- taskToComplete.status = 'completed';
512
- if (options.notes) {
513
- taskToComplete.notes = options.notes;
514
- }
515
- // Check if milestone is completed
516
- const allMilestoneTasksDone = milestoneToComplete.tasks.every((t) => t.status === 'completed');
517
- if (allMilestoneTasksDone) {
518
- milestoneToComplete.status = 'completed';
519
- console.log(pc.green(`\n🏁 Milestone [${pc.bold(milestoneToComplete.id)}] completed!`));
520
- // Find next milestone
521
- const nextMilestone = plan.milestones.find((m) => m.status !== 'completed');
522
- if (nextMilestone) {
523
- plan.activeMilestoneId = nextMilestone.id;
524
- console.log(pc.cyan(`▶ Active milestone advanced to: [${nextMilestone.id}] ${nextMilestone.name}`));
525
- }
526
- else {
527
- plan.overallStatus = 'completed';
528
- console.log(pc.bold(pc.green('🎉 All project milestones completed!')));
529
- }
530
- }
531
- });
532
- CircuitBreaker.recordSuccess(targetDir, taskId);
533
- CircuitBreaker.clearBaseline(targetDir, taskId);
534
- const closed = closeEscalationsForTask(targetDir, taskId, 'Closed automatically: the task was completed.');
535
- if (closed.length)
536
- console.log(pc.dim(` Closed its open escalations: ${closed.join(', ')}`));
537
- try {
538
- await recordTaskComplete(targetDir, foundTask, vResult, options.notes);
539
- }
540
- catch {
541
- // Non-fatal telemetry recording
542
- }
543
- console.log(pc.green(`\n✔ Task [${pc.bold(taskId)}] successfully marked as `) + pc.green('✔ completed') + '\n');
544
- }
545
- export async function runTaskBlock(taskId, reason, targetDirArg) {
546
- if (!reason || reason.trim().length === 0) {
547
- console.error(pc.red('\n✖ A reason must be provided via `--reason <text>` when blocking a task.\n'));
548
- process.exitCode = 1;
549
- return;
550
- }
551
- const { planPath } = getPlanPath(targetDirArg);
552
- return withPlanLock(planPath, (plan, ctx) => {
553
- let foundTask = null;
554
- for (const m of plan.milestones) {
555
- const t = m.tasks.find((task) => task.id === taskId);
556
- if (t) {
557
- foundTask = t;
558
- break;
559
- }
560
- }
561
- if (!foundTask) {
562
- ctx.abort();
563
- console.error(pc.red(`\n✖ Task [${taskId}] not found in .ai/master_plan.json\n`));
564
- process.exitCode = 1;
565
- return;
566
- }
567
- foundTask.status = 'blocked';
568
- foundTask.notes = reason.trim();
569
- console.log(pc.yellow(`\n⚠ Task [${pc.bold(taskId)}] marked as `) + pc.red('✖ blocked'));
570
- console.log(pc.dim(` Reason: ${foundTask.notes}\n`));
571
- });
572
- }
573
- export async function runTaskEscalate(taskId, targetDirArg, options = {}) {
574
- const { targetDir, planPath } = getPlanPath(targetDirArg);
575
- return withPlanLock(planPath, (plan, ctx) => {
576
- let foundTask = null;
577
- for (const m of plan.milestones) {
578
- const t = m.tasks.find((task) => task.id === taskId);
579
- if (t) {
580
- foundTask = t;
581
- break;
582
- }
583
- }
584
- if (!foundTask) {
585
- ctx.abort();
586
- console.error(pc.red(`\n✖ Task [${taskId}] not found in .ai/master_plan.json\n`));
587
- process.exitCode = 1;
588
- return;
589
- }
590
- const validTypes = [
591
- 'contract_drift',
592
- 'schema_flaw',
593
- 'missing_credential',
594
- 'dependency_conflict',
595
- 'architectural_ambiguity',
596
- ];
597
- const escType = (options.type && validTypes.includes(options.type))
598
- ? options.type
599
- : 'architectural_ambiguity';
600
- const affectedContracts = options.affected
601
- ? options.affected.split(',').map((s) => s.trim())
602
- : ['.ai/db_schema.json', '.ai/api_contracts.json'];
603
- const summary = options.details || `Task ${taskId} blocked by ${escType}`;
604
- const reportedBy = foundTask.assignedSubagent;
605
- // Locked append: the id is numbered under the escalation file lock, so concurrent
606
- // escalations (and circuit-breaker trips) never collide or overwrite each other.
607
- const escId = appendEscalation(targetDir, (id) => ({
608
- id,
609
- taskId,
610
- type: escType,
611
- reportedBy,
612
- timestamp: new Date().toISOString(),
613
- summary,
614
- details: options.details || '',
615
- affectedContracts,
616
- status: 'pending_review',
617
- }), { style: 'sequential', position: 'last', projectName: plan.projectName });
618
- foundTask.status = 'blocked';
619
- foundTask.notes = `Escalated [${escId}]: ${summary}`;
620
- console.log(pc.bold(pc.yellow(`\n🚨 Task [${pc.bold(taskId)}] Escalated to the Architect!`)));
621
- console.log(pc.dim(' Escalation ID: ') + pc.cyan(escId));
622
- console.log(pc.dim(' Type: ') + pc.white(escType));
623
- console.log(pc.dim(' Reported By: ') + pc.magenta(`[${foundTask.assignedSubagent}]`));
624
- console.log(pc.dim(' Affected Contracts: ') + pc.white(affectedContracts.join(', ')));
625
- console.log(pc.dim(' Details: ') + pc.yellow(summary));
626
- console.log(pc.dim('\nNext Step for the Architect:'));
627
- console.log(pc.white(` Ask the architect agent (Claude Code, or Antigravity if that is your host) to review .ai/escalation.json, update the affected contracts, and unblock the task. \`nativ triage\` can assess it first.\n`));
628
- });
629
- }
630
- // ─── Task add: dual-track router (planned milestones + fast path) ─────────
631
- export const FAST_PATH_MILESTONE_NAME = 'Fast-Path Tasks';
632
- /** Next `<prefix><n>` after the highest existing number, keeping that ID's zero-padding. */
633
- function nextNumberedId(ids, prefix, defaultWidth) {
634
- const pattern = new RegExp(`^${prefix}(\\d+)$`);
635
- let max = 0;
636
- let width = defaultWidth;
637
- for (const id of ids) {
638
- const match = pattern.exec(id);
639
- if (match && Number(match[1]) >= max) {
640
- max = Number(match[1]);
641
- width = match[1].length;
642
- }
643
- }
644
- const taken = new Set(ids);
645
- let n = max + 1;
646
- let candidate = `${prefix}${String(n).padStart(width, '0')}`;
647
- while (taken.has(candidate))
648
- candidate = `${prefix}${String(++n).padStart(width, '0')}`;
649
- return candidate;
650
- }
651
- export function generateNextTaskId(plan) {
652
- return nextNumberedId(plan.milestones.flatMap((m) => m.tasks.map((t) => t.id)), 'task-', 2);
653
- }
654
- export function generateNextMilestoneId(plan) {
655
- return nextNumberedId(plan.milestones.map((m) => m.id), 'm', 1);
656
- }
657
- function splitList(value) {
658
- const items = Array.isArray(value) ? value : value ? [value] : [];
659
- return items
660
- .flatMap((item) => item.split(','))
661
- .map((item) => item.trim())
662
- .filter(Boolean);
663
- }
664
- function failTaskAdd(code, message, json) {
665
- if (json)
666
- console.log(JSON.stringify({ error: { code, message } }, null, 2));
667
- else
668
- console.error(pc.red(`\n✖ ${message}\n`));
669
- process.exitCode = 1;
670
- return null;
671
- }
672
- /** Resolves a milestone by exact ID, exact name, or a unique partial name (all case-insensitive). */
673
- function findMilestone(plan, query) {
674
- const q = query.trim().toLowerCase();
675
- const exact = plan.milestones.find((m) => m.id.toLowerCase() === q || m.name.toLowerCase() === q);
676
- if (exact)
677
- return exact;
678
- const partial = plan.milestones.filter((m) => m.name.toLowerCase().includes(q));
679
- if (partial.length === 1)
680
- return partial[0];
681
- return partial.length > 1 ? partial : null;
682
- }
683
- /**
684
- * Appends a task to .ai/master_plan.json with an auto-incremented ID.
685
- * Routing: --milestone wins; otherwise fast-path tasks go to the fast-path milestone (created on
686
- * first use) and planned tasks to the active milestone. Completed milestones are reopened.
687
- */
688
- export async function runTaskAdd(title, targetDirArg, options = {}) {
689
- const { planPath } = getPlanPath(targetDirArg);
690
- return withPlanLock(planPath, (plan, ctx) => {
691
- if (!Array.isArray(plan.milestones)) {
692
- ctx.abort();
693
- return failTaskAdd('INVALID_PLAN', '.ai/master_plan.json is missing its "milestones" array', options.json);
694
- }
695
- const cleanTitle = (title ?? '').trim();
696
- if (!cleanTitle) {
697
- ctx.abort();
698
- return failTaskAdd('INVALID_TITLE', 'A task title is required', options.json);
699
- }
700
- const agent = (options.agent ?? 'backend').trim();
701
- if (!isSubagentType(agent)) {
702
- ctx.abort();
703
- return failTaskAdd('INVALID_AGENT', `Unknown sub-agent "${agent}". Expected one of: ${SUBAGENT_TYPES.join(', ')}`, options.json);
704
- }
705
- const existingIds = new Set(plan.milestones.flatMap((m) => m.tasks.map((t) => t.id)));
706
- const deps = [...new Set(splitList(options.deps))];
707
- const unknownDeps = deps.filter((d) => !existingIds.has(d));
708
- if (unknownDeps.length) {
709
- ctx.abort();
710
- return failTaskAdd('UNKNOWN_DEPENDENCY', `Unknown dependency task ID(s): ${unknownDeps.join(', ')}`, options.json);
711
- }
712
- const fastPath = options.fastPath === true;
713
- let milestone;
714
- let createdMilestone = false;
715
- if (options.milestone) {
716
- const found = findMilestone(plan, options.milestone);
717
- if (Array.isArray(found)) {
718
- ctx.abort();
719
- return failTaskAdd('AMBIGUOUS_MILESTONE', `"${options.milestone}" matches several milestones: ${found.map((m) => m.id).join(', ')}`, options.json);
720
- }
721
- if (!found) {
722
- ctx.abort();
723
- return failTaskAdd('UNKNOWN_MILESTONE', `Milestone "${options.milestone}" not found`, options.json);
724
- }
725
- milestone = found;
726
- }
727
- else if (fastPath) {
728
- milestone = plan.milestones.find((m) => m.fastPath === true);
729
- if (!milestone) {
730
- milestone = { id: generateNextMilestoneId(plan), name: FAST_PATH_MILESTONE_NAME, status: 'pending', fastPath: true, tasks: [] };
731
- plan.milestones.push(milestone);
732
- createdMilestone = true;
733
- }
734
- }
735
- else {
736
- const active = plan.milestones.find((m) => m.id === plan.activeMilestoneId);
737
- milestone = active && active.status !== 'completed' ? active : plan.milestones.find((m) => m.status !== 'completed');
738
- if (!milestone) {
739
- ctx.abort();
740
- return failTaskAdd('NO_OPEN_MILESTONE', 'Every milestone is completed. Pass --milestone <id> to reopen one, or --fast-path', options.json);
741
- }
742
- }
743
- const specRefs = [...new Set(splitList(options.specRefs))];
744
- const acceptanceCriteria = (Array.isArray(options.accept) ? options.accept : options.accept ? [options.accept] : [])
745
- .flatMap((item) => item.split('|'))
746
- .map((item) => item.trim())
747
- .filter(Boolean);
748
- const complexity = options.complexity?.trim().toLowerCase();
749
- const task = {
750
- id: generateNextTaskId(plan),
751
- title: cleanTitle,
752
- description: (options.description ?? '').trim(),
753
- assignedSubagent: agent,
754
- dependencies: deps,
755
- targetFiles: splitList(options.files),
756
- status: 'pending',
757
- verificationCommand: (options.verify ?? '').trim(),
758
- notes: (options.notes ?? '').trim(),
759
- ...(fastPath ? { fastPath: true } : {}),
760
- ...(specRefs.length ? { specRefs } : {}),
761
- ...(complexity ? { complexity: complexity } : {}),
762
- ...(acceptanceCriteria.length ? { acceptanceCriteria } : {}),
763
- };
764
- const problems = validateMasterPlanTask(task);
765
- if (problems.length) {
766
- ctx.abort();
767
- return failTaskAdd('INVALID_TASK', problems.join('; '), options.json);
768
- }
769
- milestone.tasks.push(task);
770
- const reopenedMilestone = milestone.status === 'completed';
771
- if (reopenedMilestone)
772
- milestone.status = 'in_progress';
773
- if (plan.overallStatus === 'completed')
774
- plan.overallStatus = 'in_progress';
775
- const result = { task, milestoneId: milestone.id, milestoneName: milestone.name, createdMilestone, reopenedMilestone };
776
- if (options.json) {
777
- console.log(JSON.stringify(result, null, 2));
778
- return result;
779
- }
780
- const track = fastPath ? pc.magenta(' [fast path]') : '';
781
- console.log(pc.green(`\n✔ Task [${pc.bold(task.id)}] added to [${milestone.id}] ${milestone.name}`) + track);
782
- if (createdMilestone)
783
- console.log(pc.cyan(` Created fast-path milestone [${milestone.id}]`));
784
- if (reopenedMilestone)
785
- console.log(pc.yellow(` Reopened completed milestone [${milestone.id}]`));
786
- console.log(pc.dim(' Title: ') + task.title);
787
- console.log(pc.dim(' Agent: ') + pc.magenta(`[${task.assignedSubagent}]`) + pc.dim(` -> ${getRoleGuide(task.assignedSubagent)}`));
788
- console.log(pc.dim(' Files: ') + (task.targetFiles.length ? pc.cyan(task.targetFiles.join(', ')) : pc.dim('none')));
789
- console.log(pc.dim(' Verify: ') + (task.verificationCommand ? pc.yellow(`\`${task.verificationCommand}\``) : pc.dim('none (completion will not be gated)')));
790
- if (task.dependencies.length)
791
- console.log(pc.dim(' Deps: ') + task.dependencies.join(', '));
792
- console.log(pc.dim('\n Start it: ') + pc.white(`nativ task start ${task.id}\n`));
793
- return result;
794
- });
795
- }
796
- export async function runTaskProposePatch(targetDirArg, options) {
797
- const targetDir = path.resolve(targetDirArg || process.cwd());
798
- let parsedValue = options.value;
799
- if (typeof options.value === 'string') {
800
- try {
801
- parsedValue = JSON.parse(options.value);
802
- }
803
- catch {
804
- parsedValue = options.value;
805
- }
806
- }
807
- const patch = {
808
- taskId: options.taskId,
809
- target: options.target,
810
- operation: options.operation,
811
- path: options.path,
812
- value: parsedValue,
813
- reason: options.reason || 'No reason provided',
814
- baseHash: options.baseHash,
815
- };
816
- const verdict = ContractGovernor.evaluate(targetDir, patch);
817
- if (options.json) {
818
- console.log(JSON.stringify(verdict, null, 2));
819
- if (!verdict.approved) {
820
- process.exitCode = 1;
821
- }
822
- return verdict;
823
- }
824
- if (verdict.approved) {
825
- console.log(pc.green(`\n✔ [GOVERNOR APPROVED] Blast Radius: `) + pc.cyan(verdict.blastRadius));
826
- console.log(pc.dim(' Rule: ') + pc.yellow(verdict.ruleId));
827
- console.log(pc.dim(' Message: ') + verdict.message);
828
- if (verdict.patchApplied) {
829
- console.log(pc.green(` Patch automatically merged into .ai/${options.target === 'db_schema' ? 'db_schema.json' : 'api_contracts.json'}`));
830
- console.log(pc.dim(` Audit log updated at .ai/audit_log.jsonl\n`));
831
- }
832
- }
833
- else {
834
- console.error(pc.red(`\n✖ [GOVERNOR REJECTED] Blast Radius: `) + pc.red(verdict.blastRadius));
835
- console.error(pc.yellow(` Rule: ${verdict.ruleId}`));
836
- console.error(pc.red(` Message: ${verdict.message}`));
837
- if (verdict.violations.length > 0) {
838
- console.error(pc.dim(' Violations:'));
839
- verdict.violations.forEach((v) => console.error(pc.red(` • ${v}`)));
840
- }
841
- console.error(pc.yellow(` Circuit Breaker: ${verdict.circuitBreaker.consecutiveFailures}/${verdict.circuitBreaker.maxThreshold} failures`));
842
- if (verdict.circuitBreaker.tripped) {
843
- console.error(pc.bold(pc.red(` 🛑 CIRCUIT BREAKER TRIPPED: Task locked to blocked.`)));
844
- console.error(pc.magenta(` Escalation record written to .ai/escalation.json (${verdict.diagnosticBundle?.escalationId})\n`));
845
- }
846
- else {
847
- console.error(pc.dim(' Fix the issue or formulate a backward-compatible proposal.\n'));
848
- }
849
- process.exitCode = 1;
850
- }
851
- return verdict;
852
- }
5
+ export { loadPlan, savePlan, withPlanLock } from '../core/lock-manager.js';
6
+ export { getRoleGuide, getRecommendedContractSlice, } from './task/task-common.js';
7
+ export { runTaskList, runTaskNext } from './task/task-query.js';
8
+ export { runTaskStart, runTaskComplete, runTaskBlock, runTaskUnlock, } from './task/task-lifecycle.js';
9
+ export { FAST_PATH_MILESTONE_NAME, generateNextTaskId, generateNextMilestoneId, runTaskAdd, } from './task/task-add.js';
10
+ export { runTaskEscalate, runTaskProposePatch, } from './task/task-escalate.js';