@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,89 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import pc from 'picocolors';
4
+ export async function runValidate(targetDirArg) {
5
+ const targetDir = path.resolve(targetDirArg || process.cwd());
6
+ console.log(pc.bold(pc.cyan(`\nšŸ” Validating AI Agent Workflow Contracts in: ${targetDir}\n`)));
7
+ let hasErrors = false;
8
+ function checkFile(relPath, isJson = false) {
9
+ const fullPath = path.join(targetDir, relPath);
10
+ if (!fs.existsSync(fullPath)) {
11
+ console.log(pc.red(` āœ– Missing: ${relPath}`));
12
+ hasErrors = true;
13
+ return null;
14
+ }
15
+ const content = fs.readFileSync(fullPath, 'utf8');
16
+ if (content.trim().length === 0) {
17
+ console.log(pc.yellow(` ⚠ Empty file: ${relPath}`));
18
+ return null;
19
+ }
20
+ if (isJson) {
21
+ try {
22
+ const parsed = JSON.parse(content);
23
+ console.log(pc.green(` āœ” Valid JSON: ${relPath}`));
24
+ return parsed;
25
+ }
26
+ catch (err) {
27
+ console.log(pc.red(` āœ– Malformed JSON in ${relPath}: ${err.message}`));
28
+ hasErrors = true;
29
+ return null;
30
+ }
31
+ }
32
+ console.log(pc.green(` āœ” Present: ${relPath}`));
33
+ return content;
34
+ }
35
+ // Directives tell agents how to work; they are guidance, not contracts, and GEMINI.md is only an optional adapter guide.
36
+ console.log(pc.bold('1. Directives:'));
37
+ const directives = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'].filter((f) => fs.existsSync(path.join(targetDir, f)));
38
+ for (const file of directives)
39
+ checkFile(file);
40
+ if (!directives.length) {
41
+ console.log(pc.yellow(' ⚠ No directive file (AGENTS.md, CLAUDE.md). Agents will not be told the workflow; run `nativ setup` to add them.'));
42
+ }
43
+ console.log(pc.bold('\n2. Core Specification Contracts:'));
44
+ checkFile('.ai/context.md');
45
+ checkFile('.ai/db_schema.json', true);
46
+ checkFile('.ai/api_contracts.json', true);
47
+ checkFile('.ai/ui_specs.md');
48
+ const plan = checkFile('.ai/master_plan.json', true);
49
+ if (plan) {
50
+ if (!Array.isArray(plan.milestones)) {
51
+ console.log(pc.red(' āœ– .ai/master_plan.json missing `milestones` array'));
52
+ hasErrors = true;
53
+ }
54
+ }
55
+ const escalationPath = path.join(targetDir, '.ai/escalation.json');
56
+ if (fs.existsSync(escalationPath)) {
57
+ const escData = checkFile('.ai/escalation.json', true);
58
+ if (escData && !Array.isArray(escData.escalations)) {
59
+ console.log(pc.red(' āœ– .ai/escalation.json missing `escalations` array'));
60
+ hasErrors = true;
61
+ }
62
+ }
63
+ console.log(pc.bold('\n3. Sub-agent Profiles:'));
64
+ console.log(pc.dim(' Core Units:'));
65
+ checkFile('.ai/subagents/database.md');
66
+ checkFile('.ai/subagents/backend.md');
67
+ checkFile('.ai/subagents/frontend.md');
68
+ checkFile('.ai/subagents/qa-tester.md');
69
+ console.log(pc.dim(' Specialized Units:'));
70
+ const specializedProfiles = [
71
+ 'flutter-developer.md',
72
+ 'devops-agent.md',
73
+ 'security-auditor.md',
74
+ 'db-migration.md',
75
+ ];
76
+ for (const profile of specializedProfiles) {
77
+ const rel = `.ai/subagents/${profile}`;
78
+ if (fs.existsSync(path.join(targetDir, rel))) {
79
+ checkFile(rel);
80
+ }
81
+ }
82
+ if (hasErrors) {
83
+ console.log(pc.bold(pc.red('\nāœ– Workflow contracts validation failed with errors.')));
84
+ process.exitCode = 1;
85
+ }
86
+ else {
87
+ console.log(pc.bold(pc.green('\nāœ” All contracts and specifications are valid and orchestration-ready!\n')));
88
+ }
89
+ }
@@ -0,0 +1,8 @@
1
+ import { BatchVerificationResult } from '../core/verifier.js';
2
+ export interface VerifyOptions {
3
+ all?: boolean;
4
+ milestone?: string;
5
+ json?: boolean;
6
+ timeout?: number;
7
+ }
8
+ export declare function runVerify(taskId?: string, targetDirArg?: string, options?: VerifyOptions): Promise<BatchVerificationResult>;
@@ -0,0 +1,65 @@
1
+ import path from 'node:path';
2
+ import pc from 'picocolors';
3
+ import { verifyBatch } from '../core/verifier.js';
4
+ export async function runVerify(taskId, targetDirArg, options = {}) {
5
+ const targetDir = path.resolve(targetDirArg || process.cwd());
6
+ const batch = await verifyBatch(targetDir, {
7
+ taskId,
8
+ milestoneId: options.milestone,
9
+ all: options.all,
10
+ timeout: options.timeout,
11
+ });
12
+ if (options.json) {
13
+ console.log(JSON.stringify(batch, null, 2));
14
+ if (batch.failed > 0) {
15
+ process.exitCode = 1;
16
+ }
17
+ return batch;
18
+ }
19
+ console.log(pc.bold(pc.cyan(`\nVerification Gatekeeper: ${batch.projectName}`)));
20
+ console.log(pc.dim(`Workspace: ${targetDir}\n`));
21
+ if (batch.total === 0) {
22
+ console.log(pc.yellow('No candidate tasks found to verify. Specify a taskId, --milestone, or --all.\n'));
23
+ return batch;
24
+ }
25
+ for (const t of batch.tasks) {
26
+ const elapsed = (t.result.durationMs / 1000).toFixed(2);
27
+ if (t.result.skipped) {
28
+ console.log(pc.dim(` ā—‹ [${t.taskId}] ${t.title} — no verification command (skipped)`));
29
+ continue;
30
+ }
31
+ if (t.result.success) {
32
+ console.log(pc.green(` āœ” [${pc.bold(t.taskId)}] ${t.title}`) +
33
+ pc.dim(` (${elapsed}s)`) +
34
+ pc.yellow(` \`${t.result.command}\``));
35
+ }
36
+ else {
37
+ console.log(pc.red(` āœ– [${pc.bold(t.taskId)}] ${t.title}`) +
38
+ pc.dim(` (${elapsed}s, exit code: ${t.result.exitCode})`) +
39
+ pc.yellow(` \`${t.result.command}\``));
40
+ if (t.result.stderr && t.result.stderr.trim()) {
41
+ console.log(pc.red(`\n--- [${t.taskId}] stderr ---`));
42
+ console.log(pc.red(t.result.stderr.trim()));
43
+ }
44
+ if (t.result.stdout && t.result.stdout.trim() && !t.result.stderr?.trim()) {
45
+ console.log(pc.dim(`\n--- [${t.taskId}] stdout ---`));
46
+ console.log(pc.dim(t.result.stdout.trim()));
47
+ }
48
+ if (t.result.error && !t.result.stderr?.includes(t.result.error)) {
49
+ console.log(pc.red(`\nError: ${t.result.error}`));
50
+ }
51
+ console.log('');
52
+ }
53
+ }
54
+ const totalTime = (batch.durationMs / 1000).toFixed(2);
55
+ console.log(pc.dim('\n────────────────────────────────────────────────────────────────'));
56
+ const summary = `Tasks: ${batch.total} | Passed: ${batch.passed} | Failed: ${batch.failed} | Skipped: ${batch.skipped} (${totalTime}s)`;
57
+ if (batch.failed > 0) {
58
+ console.log(pc.bold(pc.red(`āœ– Verification FAILED: ${summary}\n`)));
59
+ process.exitCode = 1;
60
+ }
61
+ else {
62
+ console.log(pc.bold(pc.green(`āœ” Verification PASSED: ${summary}\n`)));
63
+ }
64
+ return batch;
65
+ }
@@ -0,0 +1,47 @@
1
+ export interface WorktreeCreateOptions {
2
+ json?: boolean;
3
+ }
4
+ export interface WorktreeCreateResult {
5
+ success: boolean;
6
+ taskId: string;
7
+ branch: string;
8
+ worktreeDir: string;
9
+ aiMounted: boolean;
10
+ message: string;
11
+ }
12
+ export declare function runWorktreeCreate(taskId: string, targetDirArg?: string, options?: WorktreeCreateOptions): Promise<WorktreeCreateResult | null>;
13
+ export interface WorktreeInfo {
14
+ path: string;
15
+ head: string;
16
+ branch: string;
17
+ isAgentWorktree: boolean;
18
+ taskId?: string;
19
+ }
20
+ export declare function runWorktreeList(targetDirArg?: string, options?: {
21
+ json?: boolean;
22
+ }): Promise<WorktreeInfo[]>;
23
+ export interface WorktreeMergeOptions {
24
+ force?: boolean;
25
+ json?: boolean;
26
+ }
27
+ export interface WorktreeMergeResult {
28
+ success: boolean;
29
+ taskId: string;
30
+ branch: string;
31
+ merged: boolean;
32
+ /** Uncommitted agent work was committed on the agent branch before merging. */
33
+ committedPendingWork?: boolean;
34
+ message: string;
35
+ }
36
+ export declare function runWorktreeMerge(taskId: string, targetDirArg?: string, options?: WorktreeMergeOptions): Promise<WorktreeMergeResult | null>;
37
+ export interface WorktreeRemoveOptions {
38
+ force?: boolean;
39
+ json?: boolean;
40
+ }
41
+ export declare function runWorktreeRemove(taskId: string, targetDirArg?: string, options?: WorktreeRemoveOptions): Promise<{
42
+ success: boolean;
43
+ taskId: string;
44
+ branch: string;
45
+ removed: boolean;
46
+ message: string;
47
+ } | null>;
@@ -0,0 +1,396 @@
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import pc from 'picocolors';
5
+ import { resolveProjectRoot, linkWorktreeAiDirectory, safeUnlinkWorktreeAiDirectory, linkWorktreeNodeModules, safeUnlinkWorktreeNodeModules, } from '../core/root-resolver.js';
6
+ import { CircuitBreaker } from '../governor/index.js';
7
+ import { mergeAgentWorktree, WorktreeMergeError } from '../core/worktree-merge.js';
8
+ function isGitRepo(targetDir) {
9
+ try {
10
+ execSync('git rev-parse --is-inside-work-tree', { cwd: targetDir, stdio: 'ignore' });
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ function loadPlan(rootDir) {
18
+ const planPath = path.join(rootDir, '.ai', 'master_plan.json');
19
+ if (!fs.existsSync(planPath))
20
+ return null;
21
+ try {
22
+ return JSON.parse(fs.readFileSync(planPath, 'utf8'));
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export async function runWorktreeCreate(taskId, targetDirArg, options = {}) {
29
+ const rootDir = resolveProjectRoot(targetDirArg);
30
+ if (!isGitRepo(rootDir)) {
31
+ const msg = `Not a git repository: ${rootDir}`;
32
+ if (options.json) {
33
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
34
+ }
35
+ else {
36
+ console.error(pc.red(`\nāœ– ${msg}`));
37
+ console.log(pc.yellow('Git must be initialized to use worktree features.\n'));
38
+ }
39
+ process.exitCode = 1;
40
+ return null;
41
+ }
42
+ const plan = loadPlan(rootDir);
43
+ if (plan) {
44
+ let taskExists = false;
45
+ for (const m of plan.milestones) {
46
+ if (m.tasks.some((t) => t.id === taskId)) {
47
+ taskExists = true;
48
+ break;
49
+ }
50
+ }
51
+ if (!taskExists && !options.json) {
52
+ console.log(pc.yellow(`⚠ Warning: Task [${taskId}] is not declared in .ai/master_plan.json.`));
53
+ }
54
+ }
55
+ const worktreeDir = path.join(rootDir, '.worktrees', `task-${taskId}`);
56
+ const branchName = `agent/task-${taskId}`;
57
+ if (fs.existsSync(path.join(worktreeDir, '.git'))) {
58
+ // A real worktree: make sure its mounts survived (they are the first thing a manual cleanup removes).
59
+ const aiMounted = linkWorktreeAiDirectory(worktreeDir, rootDir);
60
+ linkWorktreeNodeModules(worktreeDir, rootDir);
61
+ const msg = `Worktree already exists at: ${worktreeDir}`;
62
+ if (options.json) {
63
+ console.log(JSON.stringify({ success: true, taskId, branch: branchName, worktreeDir, alreadyExists: true, aiMounted, message: msg }, null, 2));
64
+ }
65
+ else {
66
+ console.log(pc.yellow(`\n⚠ ${msg}\n`));
67
+ }
68
+ return { success: true, taskId, branch: branchName, worktreeDir, aiMounted, message: msg };
69
+ }
70
+ if (fs.existsSync(worktreeDir)) {
71
+ // Left behind by a manual or interrupted cleanup: git no longer knows it. An empty shell is safe
72
+ // to clear; anything with files in it may be someone's work, so stop instead of deleting it.
73
+ safeUnlinkWorktreeNodeModules(worktreeDir);
74
+ safeUnlinkWorktreeAiDirectory(worktreeDir);
75
+ if (fs.readdirSync(worktreeDir).length > 0) {
76
+ const msg = `${worktreeDir} exists but is not a git worktree and still contains files. Move or delete it, then retry.`;
77
+ if (options.json) {
78
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
79
+ }
80
+ else {
81
+ console.error(pc.red(`\nāœ– ${msg}\n`));
82
+ }
83
+ process.exitCode = 1;
84
+ return null;
85
+ }
86
+ fs.rmdirSync(worktreeDir);
87
+ }
88
+ try {
89
+ if (!options.json) {
90
+ console.log(pc.cyan(`\n🌿 Spawning isolated Git Worktree for Task [${taskId}]...`));
91
+ }
92
+ fs.mkdirSync(path.join(rootDir, '.worktrees'), { recursive: true });
93
+ // Prune stale git worktrees so git worktree add does not fail on orphaned records
94
+ try {
95
+ execSync('git worktree prune', { cwd: rootDir, stdio: 'ignore' });
96
+ }
97
+ catch { }
98
+ // The agent branch outlives a manually deleted worktree folder; reuse it (keeping its commits)
99
+ // instead of failing on "a branch named ... already exists".
100
+ let branchExists = true;
101
+ try {
102
+ execSync(`git rev-parse --verify --quiet "refs/heads/${branchName}"`, { cwd: rootDir, stdio: 'ignore' });
103
+ }
104
+ catch {
105
+ branchExists = false;
106
+ }
107
+ execSync(branchExists ? `git worktree add "${worktreeDir}" "${branchName}"` : `git worktree add "${worktreeDir}" -b "${branchName}"`, {
108
+ cwd: rootDir,
109
+ stdio: options.json ? 'pipe' : 'inherit',
110
+ });
111
+ if (branchExists && !options.json) {
112
+ console.log(pc.dim(` Reused existing branch ${branchName} (its earlier commits are kept).`));
113
+ }
114
+ // Link/mount .ai contract directory into the worktree
115
+ const aiMounted = linkWorktreeAiDirectory(worktreeDir, rootDir);
116
+ linkWorktreeNodeModules(worktreeDir, rootDir);
117
+ const result = {
118
+ success: true,
119
+ taskId,
120
+ branch: branchName,
121
+ worktreeDir,
122
+ aiMounted,
123
+ message: `Worktree for ${taskId} created successfully`,
124
+ };
125
+ if (options.json) {
126
+ console.log(JSON.stringify(result, null, 2));
127
+ }
128
+ else {
129
+ console.log(pc.bold(pc.green(`\nāœ” Worktree created successfully!`)));
130
+ console.log(pc.dim(' Branch: ') + pc.cyan(branchName));
131
+ console.log(pc.dim(' Location: ') + pc.white(worktreeDir));
132
+ console.log(pc.dim(' .ai Mount: ') + (aiMounted ? pc.green('Linked to project root') : pc.dim('Resolved dynamically')));
133
+ console.log(pc.dim('\nExecution Instructions:'));
134
+ console.log(pc.white(` Run your agent in: ${worktreeDir}`));
135
+ console.log(pc.dim(` When completed and verified, merge with: `) + pc.cyan(`nativ worktree merge ${taskId}\n`));
136
+ }
137
+ return result;
138
+ }
139
+ catch (err) {
140
+ const msg = `Failed to create git worktree: ${err.message}`;
141
+ if (options.json) {
142
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
143
+ }
144
+ else {
145
+ console.error(pc.red(`\nāœ– ${msg}\n`));
146
+ }
147
+ process.exitCode = 1;
148
+ return null;
149
+ }
150
+ }
151
+ export async function runWorktreeList(targetDirArg, options = {}) {
152
+ const rootDir = resolveProjectRoot(targetDirArg);
153
+ if (!isGitRepo(rootDir)) {
154
+ if (options.json) {
155
+ console.log(JSON.stringify({ success: false, error: 'Not a git repository' }, null, 2));
156
+ }
157
+ else {
158
+ console.error(pc.red(`\nāœ– Not a git repository: ${rootDir}\n`));
159
+ }
160
+ process.exitCode = 1;
161
+ return [];
162
+ }
163
+ try {
164
+ const raw = execSync('git worktree list --porcelain', { cwd: rootDir, encoding: 'utf8' });
165
+ const worktrees = [];
166
+ const blocks = raw.trim().split('\n\n');
167
+ for (const block of blocks) {
168
+ const lines = block.split('\n');
169
+ let wtPath = '';
170
+ let head = '';
171
+ let branch = '';
172
+ for (const line of lines) {
173
+ if (line.startsWith('worktree '))
174
+ wtPath = line.substring(9).trim();
175
+ else if (line.startsWith('HEAD '))
176
+ head = line.substring(5).trim();
177
+ else if (line.startsWith('branch '))
178
+ branch = line.substring(7).trim().replace('refs/heads/', '');
179
+ }
180
+ if (wtPath) {
181
+ const isAgent = wtPath.includes('.worktrees') || branch.startsWith('agent/task-');
182
+ const match = branch.match(/^agent\/task-(.+)$/);
183
+ worktrees.push({
184
+ path: wtPath,
185
+ head,
186
+ branch: branch || 'detached',
187
+ isAgentWorktree: isAgent,
188
+ taskId: match ? match[1] : undefined,
189
+ });
190
+ }
191
+ }
192
+ if (options.json) {
193
+ console.log(JSON.stringify(worktrees, null, 2));
194
+ return worktrees;
195
+ }
196
+ console.log(pc.bold(pc.cyan('\n🌿 Active Git Worktrees:')));
197
+ worktrees.forEach((wt, index) => {
198
+ if (wt.isAgentWorktree) {
199
+ console.log(pc.green(` ā–¶ [${wt.taskId || 'agent'}] `) + pc.white(wt.branch) + pc.dim(` (${wt.path})`));
200
+ }
201
+ else {
202
+ // git always lists the main worktree first; any other non-agent entry was added outside nativ.
203
+ console.log(pc.dim(` • [${index === 0 ? 'main' : 'linked'}] `) + pc.white(wt.branch) + pc.dim(` (${wt.path})`));
204
+ }
205
+ });
206
+ console.log();
207
+ return worktrees;
208
+ }
209
+ catch (err) {
210
+ if (options.json) {
211
+ console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
212
+ }
213
+ else {
214
+ console.error(pc.red(`\nāœ– Failed to list worktrees: ${err.message}\n`));
215
+ }
216
+ process.exitCode = 1;
217
+ return [];
218
+ }
219
+ }
220
+ export async function runWorktreeMerge(taskId, targetDirArg, options = {}) {
221
+ const rootDir = resolveProjectRoot(targetDirArg);
222
+ if (!isGitRepo(rootDir)) {
223
+ const msg = `Not a git repository: ${rootDir}`;
224
+ if (options.json) {
225
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
226
+ }
227
+ else {
228
+ console.error(pc.red(`\nāœ– ${msg}\n`));
229
+ }
230
+ process.exitCode = 1;
231
+ return null;
232
+ }
233
+ // ─── Safe Merge Gatekeeper ─────────────────────────────────────────────────
234
+ if (!options.force) {
235
+ // Check 1: Verify task status in master_plan.json
236
+ const plan = loadPlan(rootDir);
237
+ if (plan) {
238
+ let foundTask = null;
239
+ for (const m of plan.milestones) {
240
+ const t = m.tasks.find((task) => task.id === taskId);
241
+ if (t) {
242
+ foundTask = t;
243
+ break;
244
+ }
245
+ }
246
+ if (foundTask && foundTask.status !== 'completed') {
247
+ const msg = `[GATEKEEPER REJECTED] Task '${taskId}' has status '${foundTask.status}'. Only tasks verified and marked 'completed' may be merged. (Use --force to override)`;
248
+ if (options.json) {
249
+ console.log(JSON.stringify({ success: false, error: msg, status: foundTask.status }, null, 2));
250
+ }
251
+ else {
252
+ console.error(pc.red(`\nāœ– ${msg}`));
253
+ console.log(pc.yellow(`Complete task verification first with:\n nativ task complete ${taskId}\n`));
254
+ }
255
+ process.exitCode = 1;
256
+ return null;
257
+ }
258
+ }
259
+ // Check 2: Verify Governor Circuit Breaker is not tripped
260
+ const breaker = CircuitBreaker.getStatus(rootDir, taskId);
261
+ if (breaker.tripped) {
262
+ const msg = `[GATEKEEPER REJECTED] Task '${taskId}' has a tripped Circuit Breaker in the Contract Governor. Active escalation must be resolved before merging.`;
263
+ if (options.json) {
264
+ console.log(JSON.stringify({ success: false, error: msg, circuitBreaker: breaker }, null, 2));
265
+ }
266
+ else {
267
+ console.error(pc.red(`\nāœ– ${msg}\n`));
268
+ }
269
+ process.exitCode = 1;
270
+ return null;
271
+ }
272
+ }
273
+ else if (!options.json) {
274
+ console.log(pc.yellow(`⚠ Safe Merge Gatekeeper bypassed via --force.`));
275
+ }
276
+ const worktreeDir = path.join(rootDir, '.worktrees', `task-${taskId}`);
277
+ const branchName = `agent/task-${taskId}`;
278
+ try {
279
+ if (!options.json) {
280
+ console.log(pc.cyan(`\nšŸ”€ Merging worktree for Task [${taskId}]...`));
281
+ }
282
+ // Commits any uncommitted agent work, merges, and only then removes the worktree and branch.
283
+ const outcome = mergeAgentWorktree(rootDir, taskId);
284
+ if (!options.json) {
285
+ if (outcome.committedPendingWork) {
286
+ console.log(pc.yellow(` āœ” Committed uncommitted agent work on ${branchName} before merging (${outcome.pendingCommit?.slice(0, 7)})`));
287
+ }
288
+ console.log(pc.green(` āœ” Merged branch ${pc.bold(branchName)} into current branch`));
289
+ console.log(outcome.cleanedUp ? pc.dim(` āœ” Removed ${worktreeDir} and branch ${branchName}`) : pc.yellow(` ⚠ ${outcome.message}`));
290
+ }
291
+ const result = {
292
+ success: true,
293
+ taskId,
294
+ branch: branchName,
295
+ merged: true,
296
+ committedPendingWork: outcome.committedPendingWork,
297
+ message: outcome.cleanedUp ? `Task [${taskId}] worktree cleanly merged` : outcome.message,
298
+ };
299
+ if (options.json) {
300
+ console.log(JSON.stringify(result, null, 2));
301
+ }
302
+ else {
303
+ console.log(pc.bold(pc.green(`\nāœ” Task [${taskId}] worktree cleanly merged!\n`)));
304
+ }
305
+ return result;
306
+ }
307
+ catch (err) {
308
+ const msg = `Failed to merge worktree: ${err.message}`;
309
+ const workPreserved = err instanceof WorktreeMergeError ? err.workPreserved : undefined;
310
+ if (options.json) {
311
+ console.log(JSON.stringify({ success: false, error: msg, ...(workPreserved !== undefined ? { workPreserved } : {}) }, null, 2));
312
+ }
313
+ else {
314
+ console.error(pc.red(`\nāœ– ${msg}\n`));
315
+ }
316
+ process.exitCode = 1;
317
+ return null;
318
+ }
319
+ }
320
+ export async function runWorktreeRemove(taskId, targetDirArg, options = {}) {
321
+ const rootDir = resolveProjectRoot(targetDirArg);
322
+ if (!isGitRepo(rootDir)) {
323
+ const msg = `Not a git repository: ${rootDir}`;
324
+ if (options.json) {
325
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
326
+ }
327
+ else {
328
+ console.error(pc.red(`\nāœ– ${msg}\n`));
329
+ }
330
+ process.exitCode = 1;
331
+ return null;
332
+ }
333
+ const worktreeDir = path.join(rootDir, '.worktrees', `task-${taskId}`);
334
+ const branchName = `agent/task-${taskId}`;
335
+ try {
336
+ if (!options.json) {
337
+ console.log(pc.yellow(`\nšŸ—‘ Cleaning up worktree for Task [${taskId}] without merging...`));
338
+ }
339
+ if (fs.existsSync(worktreeDir)) {
340
+ safeUnlinkWorktreeNodeModules(worktreeDir);
341
+ safeUnlinkWorktreeAiDirectory(worktreeDir);
342
+ try {
343
+ execSync(`git worktree remove "${worktreeDir}" --force`, { cwd: rootDir, stdio: options.json ? 'pipe' : 'inherit' });
344
+ }
345
+ catch { }
346
+ try {
347
+ execSync('git worktree prune', { cwd: rootDir, stdio: 'ignore' });
348
+ }
349
+ catch { }
350
+ if (fs.existsSync(worktreeDir)) {
351
+ try {
352
+ fs.rmSync(worktreeDir, { recursive: true, force: true });
353
+ }
354
+ catch { }
355
+ }
356
+ if (!options.json) {
357
+ console.log(pc.dim(` āœ” Removed worktree directory: ${worktreeDir}`));
358
+ }
359
+ }
360
+ // Delete branch
361
+ try {
362
+ execSync(`git branch -D "${branchName}"`, { cwd: rootDir, stdio: 'ignore' });
363
+ if (!options.json) {
364
+ console.log(pc.dim(` āœ” Discarded branch ${branchName}`));
365
+ }
366
+ }
367
+ catch {
368
+ // Ignore if branch doesn't exist
369
+ }
370
+ const result = {
371
+ success: true,
372
+ taskId,
373
+ branch: branchName,
374
+ removed: true,
375
+ message: `Worktree for Task [${taskId}] removed`,
376
+ };
377
+ if (options.json) {
378
+ console.log(JSON.stringify(result, null, 2));
379
+ }
380
+ else {
381
+ console.log(pc.green(`\nāœ” Worktree for Task [${taskId}] successfully removed.\n`));
382
+ }
383
+ return result;
384
+ }
385
+ catch (err) {
386
+ const msg = `Failed to remove worktree: ${err.message}`;
387
+ if (options.json) {
388
+ console.log(JSON.stringify({ success: false, error: msg }, null, 2));
389
+ }
390
+ else {
391
+ console.error(pc.red(`\nāœ– ${msg}\n`));
392
+ }
393
+ process.exitCode = 1;
394
+ return null;
395
+ }
396
+ }
@@ -0,0 +1,57 @@
1
+ export interface BenchmarkScenarioResult {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ status: 'passed' | 'failed';
6
+ durationMs: number;
7
+ operations: number;
8
+ throughputOpsPerSec: number;
9
+ assertionsPassed: number;
10
+ assertionsFailed: number;
11
+ details: Record<string, any>;
12
+ errors?: string[];
13
+ }
14
+ export interface BenchmarkSummary {
15
+ totalScenarios: number;
16
+ passedScenarios: number;
17
+ failedScenarios: number;
18
+ score: number;
19
+ totalDurationMs: number;
20
+ totalOperations: number;
21
+ averageThroughputOpsPerSec: number;
22
+ }
23
+ export interface BenchmarkReport {
24
+ version: string;
25
+ timestamp: string;
26
+ environment: {
27
+ platform: string;
28
+ nodeVersion: string;
29
+ cpuCount: number;
30
+ arch: string;
31
+ };
32
+ summary: BenchmarkSummary;
33
+ scenarios: BenchmarkScenarioResult[];
34
+ }
35
+ export interface BenchmarkOptions {
36
+ scenario?: string;
37
+ concurrency?: number;
38
+ output?: string;
39
+ json?: boolean;
40
+ verbose?: boolean;
41
+ }
42
+ /**
43
+ * Creates an isolated scratch workspace for benchmarking with valid .ai/ contracts.
44
+ */
45
+ export declare function createBenchmarkSandbox(): string;
46
+ /**
47
+ * Resilient sandbox cleanup handling Windows NTFS delayed locks.
48
+ */
49
+ export declare function cleanupSandbox(dir: string): void;
50
+ /**
51
+ * Main benchmark execution coordinator.
52
+ */
53
+ export declare function runBenchmarks(options?: BenchmarkOptions): Promise<BenchmarkReport>;
54
+ /**
55
+ * Formats the benchmark report for high-readability CLI output.
56
+ */
57
+ export declare function formatBenchmarkReport(report: BenchmarkReport): string;