agentic-orchestrator 0.1.4 → 0.1.6
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.
- package/.claude/settings.local.json +6 -1
- package/README.md +81 -54
- package/agentic/orchestrator/defaults/policy.defaults.yaml +212 -0
- package/agentic/orchestrator/schemas/adapters.schema.json +17 -4
- package/agentic/orchestrator/schemas/agents.schema.json +58 -13
- package/agentic/orchestrator/schemas/gates.schema.json +88 -17
- package/agentic/orchestrator/schemas/index.schema.json +172 -37
- package/agentic/orchestrator/schemas/multi-project.schema.json +85 -17
- package/agentic/orchestrator/schemas/plan.schema.json +135 -30
- package/agentic/orchestrator/schemas/policy.schema.json +198 -69
- package/agentic/orchestrator/schemas/policy.user.schema.json +165 -0
- package/agentic/orchestrator/schemas/qa_test_index.schema.json +71 -14
- package/agentic/orchestrator/schemas/state.schema.json +196 -39
- package/apps/control-plane/scripts/validate-architecture-rules.mjs +5 -3
- package/apps/control-plane/scripts/validate-docker-mcp-contract.mjs +0 -3
- package/apps/control-plane/scripts/validate-mcp-contracts.ts +2 -6
- package/apps/control-plane/src/application/services/policy-loader-service.ts +115 -0
- package/apps/control-plane/src/cli/aop.ts +35 -1
- package/apps/control-plane/src/cli/cli-argument-parser.ts +4 -0
- package/apps/control-plane/src/cli/help-command-handler.ts +2 -1
- package/apps/control-plane/src/cli/init-command-handler.ts +136 -27
- package/apps/control-plane/src/cli/retry-command-handler.ts +3 -6
- package/apps/control-plane/src/cli/types.ts +1 -0
- package/apps/control-plane/src/core/git.ts +1 -3
- package/apps/control-plane/src/core/kernel.ts +3 -6
- package/apps/control-plane/src/core/schemas.ts +36 -1
- package/apps/control-plane/src/interfaces/cli/bootstrap.ts +1 -1
- package/apps/control-plane/src/mcp/tool-registry-loader.ts +37 -1
- package/apps/control-plane/src/supervisor/qa-wave-executor.ts +1 -1
- package/apps/control-plane/test/aop.spec.ts +37 -0
- package/apps/control-plane/test/batch-operations.spec.ts +5 -3
- package/apps/control-plane/test/bootstrap-attach.spec.ts +28 -18
- package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +25 -15
- package/apps/control-plane/test/bootstrap.spec.ts +30 -22
- package/apps/control-plane/test/cli.unit.spec.ts +55 -10
- package/apps/control-plane/test/core-utils.spec.ts +12 -3
- package/apps/control-plane/test/feature-deletion-service.spec.ts +2 -1
- package/apps/control-plane/test/feature-lifecycle.spec.ts +1 -1
- package/apps/control-plane/test/helpers.ts +22 -2
- package/apps/control-plane/test/init-wizard.spec.ts +160 -7
- package/apps/control-plane/test/issue-tracker.spec.ts +3 -3
- package/apps/control-plane/test/kernel.spec.ts +62 -0
- package/apps/control-plane/test/lock-service.spec.ts +4 -4
- package/apps/control-plane/test/policy-loader-service.spec.ts +346 -0
- package/apps/control-plane/test/reactions.spec.ts +8 -6
- package/apps/control-plane/test/resume-command.spec.ts +31 -15
- package/apps/control-plane/test/supervisor.unit.spec.ts +2 -2
- package/apps/control-plane/test/tool-registry-loader.spec.ts +12 -0
- package/apps/control-plane/vitest.config.ts +1 -1
- package/dist/apps/control-plane/application/services/policy-loader-service.d.ts +24 -0
- package/dist/apps/control-plane/application/services/policy-loader-service.js +90 -0
- package/dist/apps/control-plane/application/services/policy-loader-service.js.map +1 -0
- package/dist/apps/control-plane/application/tools/tool-metadata.js +1 -0
- package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
- package/dist/apps/control-plane/cli/aop.js +33 -1
- package/dist/apps/control-plane/cli/aop.js.map +1 -1
- package/dist/apps/control-plane/cli/cli-argument-parser.js +4 -0
- package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
- package/dist/apps/control-plane/cli/dashboard-command-handler.js +11 -5
- package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/help-command-handler.js +5 -4
- package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/init-command-handler.d.ts +1 -0
- package/dist/apps/control-plane/cli/init-command-handler.js +110 -26
- package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/retry-command-handler.js +3 -6
- package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/types.d.ts +1 -0
- package/dist/apps/control-plane/core/git.js +1 -3
- package/dist/apps/control-plane/core/git.js.map +1 -1
- package/dist/apps/control-plane/core/kernel.js +3 -5
- package/dist/apps/control-plane/core/kernel.js.map +1 -1
- package/dist/apps/control-plane/core/schemas.d.ts +2 -0
- package/dist/apps/control-plane/core/schemas.js +31 -1
- package/dist/apps/control-plane/core/schemas.js.map +1 -1
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js +1 -1
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
- package/dist/apps/control-plane/mcp/tool-registry-loader.d.ts +2 -0
- package/dist/apps/control-plane/mcp/tool-registry-loader.js +31 -1
- package/dist/apps/control-plane/mcp/tool-registry-loader.js.map +1 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.js +1 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
- package/package.json +18 -17
- package/packages/web-dashboard/package.json +1 -1
- package/packages/web-dashboard/src/app/api/actions/route.ts +2 -2
- package/packages/web-dashboard/src/app/api/events/route.ts +1 -1
- package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +1 -1
- package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +1 -1
- package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +1 -1
- package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +2 -2
- package/packages/web-dashboard/src/app/api/features/[id]/route.ts +1 -1
- package/packages/web-dashboard/src/app/api/projects/route.ts +2 -2
- package/packages/web-dashboard/src/app/api/status/route.ts +1 -1
- package/packages/web-dashboard/src/app/page.tsx +1 -1
- package/packages/web-dashboard/src/lib/aop-client.ts +1 -1
- package/packages/web-dashboard/src/lib/orchestrator-tools.ts +1 -1
- package/packages/web-dashboard/tsconfig.json +1 -0
- package/spec-files/completed/agentic_orchestrator_init_policy_ux_simplification_spec.md +481 -0
- package/spec-files/outstanding/agentic_orchestrator_artifact_database_publishing_spec.md +500 -0
- package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +500 -195
- package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +471 -162
- package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +532 -268
- package/spec-files/outstanding/agentic_orchestrator_productization_commercial_spec.md +645 -0
- package/spec-files/progress.md +99 -2
- package/tsconfig.base.json +4 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_validator_hardening_spec.md +0 -0
|
@@ -1,18 +1,54 @@
|
|
|
1
|
+
import fsSync from 'node:fs';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { pathExists } from '../core/fs.js';
|
|
4
6
|
import { MCP_PROTOCOL_PIN, REQUIRED_MCP_TRANSPORTS } from './protocol-contract.js';
|
|
5
7
|
import type { LoadedToolRegistry, ProtocolContract, ToolCatalog, ToolCatalogEntry } from './runtime-types.js';
|
|
6
8
|
|
|
9
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
function resolveBundledToolsRoot(startDir: string): string | null {
|
|
12
|
+
let current = path.resolve(startDir);
|
|
13
|
+
|
|
14
|
+
while (true) {
|
|
15
|
+
const candidateCatalog = path.join(current, 'agentic', 'orchestrator', 'tools', 'catalog.json');
|
|
16
|
+
if (fsSync.existsSync(candidateCatalog)) {
|
|
17
|
+
return path.dirname(candidateCatalog);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const parent = path.dirname(current);
|
|
21
|
+
if (parent === current) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
current = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const BUNDLED_TOOLS_ROOT = resolveBundledToolsRoot(MODULE_DIR);
|
|
29
|
+
|
|
7
30
|
export class ToolRegistryLoader {
|
|
8
31
|
readonly repoRoot: string;
|
|
32
|
+
private readonly resolvedToolsRoot: string;
|
|
9
33
|
|
|
10
34
|
constructor(repoRoot: string) {
|
|
11
35
|
this.repoRoot = repoRoot;
|
|
36
|
+
this.resolvedToolsRoot = this.resolveToolsRoot();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private resolveToolsRoot(): string {
|
|
40
|
+
const repoToolsRoot = path.join(this.repoRoot, 'agentic', 'orchestrator', 'tools');
|
|
41
|
+
if (fsSync.existsSync(path.join(repoToolsRoot, 'catalog.json'))) {
|
|
42
|
+
return repoToolsRoot;
|
|
43
|
+
}
|
|
44
|
+
if (BUNDLED_TOOLS_ROOT) {
|
|
45
|
+
return BUNDLED_TOOLS_ROOT;
|
|
46
|
+
}
|
|
47
|
+
return repoToolsRoot;
|
|
12
48
|
}
|
|
13
49
|
|
|
14
50
|
get toolsRoot(): string {
|
|
15
|
-
return
|
|
51
|
+
return this.resolvedToolsRoot;
|
|
16
52
|
}
|
|
17
53
|
|
|
18
54
|
get catalogPath(): string {
|
|
@@ -79,7 +79,7 @@ export class QaWaveExecutor {
|
|
|
79
79
|
const initialRetryCount = stateForRetry.data.front_matter.gate_retry_count ?? 0;
|
|
80
80
|
|
|
81
81
|
let gateOverall: string;
|
|
82
|
-
let gateExitCode
|
|
82
|
+
let gateExitCode: number;
|
|
83
83
|
let gateEvidencePath = '';
|
|
84
84
|
let gateLogs = '';
|
|
85
85
|
const failureHistory: GateRepairContext['failureHistory'] = [];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
1
3
|
import path from 'node:path';
|
|
2
4
|
import { pathToFileURL } from 'node:url';
|
|
3
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -61,6 +63,21 @@ describe('aop.ts entrypoint', () => {
|
|
|
61
63
|
expect(isDirectExecution(`file://${modulePath}`, undefined)).toBe(false);
|
|
62
64
|
});
|
|
63
65
|
|
|
66
|
+
it('GIVEN_symlinked_entrypoint_WHEN_direct_execution_checked_THEN_returns_true', async () => {
|
|
67
|
+
const { isDirectExecution } = await import('../src/cli/aop.js');
|
|
68
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-entrypoint-'));
|
|
69
|
+
try {
|
|
70
|
+
const realFilePath = path.join(tempDir, 'real-aop.js');
|
|
71
|
+
const symlinkPath = path.join(tempDir, 'aop');
|
|
72
|
+
await fs.writeFile(realFilePath, 'export {};');
|
|
73
|
+
await fs.symlink(realFilePath, symlinkPath);
|
|
74
|
+
|
|
75
|
+
expect(isDirectExecution(pathToFileURL(realFilePath).href, symlinkPath)).toBe(true);
|
|
76
|
+
} finally {
|
|
77
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
64
81
|
it('GIVEN_not_direct_execution_WHEN_runAsEntrypoint_called_THEN_does_not_call_main_or_change_exit_code', async () => {
|
|
65
82
|
const { runAsEntrypoint } = await import('../src/cli/aop.js');
|
|
66
83
|
const executeMain = vi.fn(async () => 11);
|
|
@@ -84,6 +101,26 @@ describe('aop.ts entrypoint', () => {
|
|
|
84
101
|
expect(process.exitCode).toBe(13);
|
|
85
102
|
});
|
|
86
103
|
|
|
104
|
+
it('GIVEN_direct_execution_via_symlink_WHEN_runAsEntrypoint_called_THEN_calls_main', async () => {
|
|
105
|
+
const { runAsEntrypoint } = await import('../src/cli/aop.js');
|
|
106
|
+
const executeMain = vi.fn(async () => 19);
|
|
107
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-entrypoint-run-'));
|
|
108
|
+
try {
|
|
109
|
+
const modulePath = path.resolve(process.cwd(), 'apps/control-plane/src/cli/aop.ts');
|
|
110
|
+
const symlinkPath = path.join(tempDir, 'aop');
|
|
111
|
+
await fs.symlink(modulePath, symlinkPath);
|
|
112
|
+
|
|
113
|
+
const started = runAsEntrypoint(`file://${modulePath}`, symlinkPath, executeMain);
|
|
114
|
+
|
|
115
|
+
expect(started).toBe(true);
|
|
116
|
+
expect(executeMain).toHaveBeenCalledTimes(1);
|
|
117
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
118
|
+
expect(process.exitCode).toBe(19);
|
|
119
|
+
} finally {
|
|
120
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
87
124
|
it('GIVEN_module_executed_as_script_WHEN_imported_fresh_THEN_sets_process_exit_code_from_main', async () => {
|
|
88
125
|
vi.mocked(runCli).mockResolvedValueOnce(17);
|
|
89
126
|
const modulePath = path.resolve(process.cwd(), 'apps/control-plane/src/cli/aop.ts');
|
|
@@ -13,9 +13,11 @@ vi.mock('node:fs/promises', () => ({
|
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
15
|
vi.mock('../src/cli/spec-input-resolver.js', () => ({
|
|
16
|
-
SpecInputResolver:
|
|
17
|
-
resolve:
|
|
18
|
-
|
|
16
|
+
SpecInputResolver: class SpecInputResolver {
|
|
17
|
+
async resolve(...args: unknown[]) {
|
|
18
|
+
return await specResolverResolveMock(...args);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
19
21
|
}));
|
|
20
22
|
|
|
21
23
|
import { RunCommandHandler } from '../src/cli/run-command-handler.js';
|
|
@@ -38,36 +38,46 @@ vi.mock('../src/mcp/runtime-factory.js', () => ({
|
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
40
|
vi.mock('../src/cli/init-command-handler.js', () => ({
|
|
41
|
-
InitCommandHandler:
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
InitCommandHandler: class InitCommandHandler {
|
|
42
|
+
async execute() {
|
|
43
|
+
return { ok: true, data: { command: 'init', created: [], updated: [], skipped: [], validation_warnings: [], next_steps: [] } };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
44
46
|
}));
|
|
45
47
|
|
|
46
48
|
vi.mock('../src/cli/dashboard-command-handler.js', () => ({
|
|
47
|
-
DashboardCommandHandler:
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
DashboardCommandHandler: class DashboardCommandHandler {
|
|
50
|
+
async execute() {
|
|
51
|
+
return { ok: true, data: { message: 'started', port: 3000 } };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
50
54
|
}));
|
|
51
55
|
|
|
52
56
|
vi.mock('../src/cli/run-command-handler.js', () => ({
|
|
53
|
-
RunCommandHandler:
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
RunCommandHandler: class RunCommandHandler {
|
|
58
|
+
async execute() {
|
|
59
|
+
return { ok: true, data: { status: 'running' } };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
56
62
|
}));
|
|
57
63
|
|
|
58
64
|
vi.mock('../src/cli/resume-command-handler.js', () => ({
|
|
59
|
-
ResumeCommandHandler:
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
ResumeCommandHandler: class ResumeCommandHandler {
|
|
66
|
+
async execute() {
|
|
67
|
+
return { ok: true, data: { resumed: false } };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
62
70
|
}));
|
|
63
71
|
|
|
64
72
|
vi.mock('../src/cli/attach-command-handler.js', () => ({
|
|
65
|
-
AttachCommandHandler:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
AttachCommandHandler: class AttachCommandHandler {
|
|
74
|
+
async execute() {
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
data: { command: 'attach', feature_id: 'feat_x', status: 'building', role: 'builder', session_id: 'sess-1', attached: true }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
71
81
|
}));
|
|
72
82
|
|
|
73
83
|
describe('bootstrap runCli attach success path', () => {
|
|
@@ -65,33 +65,43 @@ vi.mock('../src/mcp/runtime-factory.js', () => ({
|
|
|
65
65
|
}));
|
|
66
66
|
|
|
67
67
|
vi.mock('../src/cli/init-command-handler.js', () => ({
|
|
68
|
-
InitCommandHandler:
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
InitCommandHandler: class InitCommandHandler {
|
|
69
|
+
async execute() {
|
|
70
|
+
return { ok: true, data: { command: 'init', created: [], updated: [], skipped: [], validation_warnings: [], next_steps: [] } };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
71
73
|
}));
|
|
72
74
|
|
|
73
75
|
vi.mock('../src/cli/dashboard-command-handler.js', () => ({
|
|
74
|
-
DashboardCommandHandler:
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
DashboardCommandHandler: class DashboardCommandHandler {
|
|
77
|
+
async execute() {
|
|
78
|
+
return { ok: true, data: { message: 'started', port: 3000 } };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
77
81
|
}));
|
|
78
82
|
|
|
79
83
|
vi.mock('../src/cli/run-command-handler.js', () => ({
|
|
80
|
-
RunCommandHandler:
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
RunCommandHandler: class RunCommandHandler {
|
|
85
|
+
async execute() {
|
|
86
|
+
return { ok: true, data: { status: 'running' } };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
83
89
|
}));
|
|
84
90
|
|
|
85
91
|
vi.mock('../src/cli/resume-command-handler.js', () => ({
|
|
86
|
-
ResumeCommandHandler:
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
ResumeCommandHandler: class ResumeCommandHandler {
|
|
93
|
+
async execute() {
|
|
94
|
+
return { ok: true, data: { resumed: false } };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
89
97
|
}));
|
|
90
98
|
|
|
91
99
|
vi.mock('../src/cli/attach-command-handler.js', () => ({
|
|
92
|
-
AttachCommandHandler:
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
AttachCommandHandler: class AttachCommandHandler {
|
|
101
|
+
async execute() {
|
|
102
|
+
return { ok: true, data: { command: 'attach' } };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
95
105
|
}));
|
|
96
106
|
|
|
97
107
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
@@ -63,37 +63,45 @@ vi.mock('../src/mcp/runtime-factory.js', () => ({
|
|
|
63
63
|
}));
|
|
64
64
|
|
|
65
65
|
vi.mock('../src/cli/init-command-handler.js', () => ({
|
|
66
|
-
InitCommandHandler:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
InitCommandHandler: class InitCommandHandler {
|
|
67
|
+
async execute() {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
data: {
|
|
71
|
+
command: 'init',
|
|
72
|
+
created: [],
|
|
73
|
+
updated: [],
|
|
74
|
+
skipped: [],
|
|
75
|
+
validation_warnings: [],
|
|
76
|
+
next_steps: []
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
79
81
|
}));
|
|
80
82
|
|
|
81
83
|
vi.mock('../src/cli/dashboard-command-handler.js', () => ({
|
|
82
|
-
DashboardCommandHandler:
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
DashboardCommandHandler: class DashboardCommandHandler {
|
|
85
|
+
async execute() {
|
|
86
|
+
return { ok: true, data: { message: 'started', port: 3000 } };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
85
89
|
}));
|
|
86
90
|
|
|
87
91
|
vi.mock('../src/cli/run-command-handler.js', () => ({
|
|
88
|
-
RunCommandHandler:
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
RunCommandHandler: class RunCommandHandler {
|
|
93
|
+
async execute() {
|
|
94
|
+
return { ok: true, data: { status: 'running' } };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
91
97
|
}));
|
|
92
98
|
|
|
93
99
|
vi.mock('../src/cli/resume-command-handler.js', () => ({
|
|
94
|
-
ResumeCommandHandler:
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
ResumeCommandHandler: class ResumeCommandHandler {
|
|
101
|
+
async execute() {
|
|
102
|
+
return { ok: true, data: { resumed: false } };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
97
105
|
}));
|
|
98
106
|
|
|
99
107
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
@@ -178,18 +178,63 @@ describe('aop CLI unit', () => {
|
|
|
178
178
|
beforeEach(async () => {
|
|
179
179
|
cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-cli-unit-'));
|
|
180
180
|
await fs.mkdir(path.join(cwd, '.aop', 'features'), { recursive: true });
|
|
181
|
+
resolveToolClientMock.mockReset();
|
|
181
182
|
resolveToolClientMock.mockReturnValue({ call: toolCallMock });
|
|
182
183
|
toolCallMock.mockReset();
|
|
183
|
-
ensureLoadedMock.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
ensureLoadedMock.mockReset();
|
|
185
|
+
ensureLoadedMock.mockResolvedValue(undefined);
|
|
186
|
+
recoverFromStateMock.mockReset();
|
|
187
|
+
recoverFromStateMock.mockResolvedValue({
|
|
188
|
+
data: {
|
|
189
|
+
recovered: true,
|
|
190
|
+
stale_features: [],
|
|
191
|
+
active: {}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
readIndexMock.mockReset();
|
|
195
|
+
readIndexMock.mockResolvedValue({
|
|
196
|
+
version: 1,
|
|
197
|
+
active: ['feature_resume'],
|
|
198
|
+
blocked: [],
|
|
199
|
+
blocked_queue: []
|
|
200
|
+
});
|
|
201
|
+
getRuntimeSessionsMock.mockReset();
|
|
202
|
+
getRuntimeSessionsMock.mockResolvedValue({
|
|
203
|
+
run_id: 'run:resume',
|
|
204
|
+
owner_instance_id: 'owner:resume',
|
|
205
|
+
feature_sessions: {}
|
|
206
|
+
});
|
|
207
|
+
featureDiscoverSpecsMock.mockReset();
|
|
208
|
+
featureDiscoverSpecsMock.mockResolvedValue({
|
|
209
|
+
data: {
|
|
210
|
+
specs: [{ feature_id: 'feature_resume', spec_path: '.aop/features/feature_resume/spec.md' }]
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
featureStateGetMock.mockReset();
|
|
214
|
+
featureStateGetMock.mockResolvedValue({
|
|
215
|
+
data: {
|
|
216
|
+
front_matter: {
|
|
217
|
+
status: 'planning'
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
createToolingRuntimeMock.mockReset();
|
|
222
|
+
createToolingRuntimeMock.mockResolvedValue({ runtime: 'ok' });
|
|
223
|
+
resolveProviderSelectionMock.mockReset();
|
|
224
|
+
resolveProviderSelectionMock.mockReturnValue({
|
|
225
|
+
provider: 'custom',
|
|
226
|
+
model: 'model',
|
|
227
|
+
provider_config_env: null,
|
|
228
|
+
provider_config_ref: null
|
|
229
|
+
});
|
|
230
|
+
supervisorConstructorOptionsMock.mockReset();
|
|
231
|
+
supervisorStartMock.mockReset();
|
|
232
|
+
supervisorStartMock.mockResolvedValue({
|
|
233
|
+
queue_depth: 0,
|
|
234
|
+
status: 'running',
|
|
235
|
+
run_metadata: { started_at: 'now' },
|
|
236
|
+
dashboard: { features: [] }
|
|
237
|
+
});
|
|
193
238
|
stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
194
239
|
});
|
|
195
240
|
|
|
@@ -263,13 +263,22 @@ describe('core-utils additional branch coverage', () => {
|
|
|
263
263
|
const repoRoot = process.cwd();
|
|
264
264
|
const registry = new SchemaRegistry(repoRoot);
|
|
265
265
|
// Pre-populate cache with a manually compiled validator
|
|
266
|
-
const AjvCtor =
|
|
266
|
+
const { Ajv: AjvCtor } = await import('ajv');
|
|
267
267
|
const ajv = new AjvCtor({ strict: false });
|
|
268
268
|
const validator = ajv.compile({ type: 'object' });
|
|
269
|
-
|
|
270
|
-
registry['validators'].set('policy.schema.json' as any, validator);
|
|
269
|
+
(registry as any).validators.set('policy.schema.json' as any, validator);
|
|
271
270
|
// Second call should use cached validator
|
|
272
271
|
const result = await registry.validate('policy.schema.json' as any, { anything: true });
|
|
273
272
|
expect(result.valid).toBe(true);
|
|
274
273
|
});
|
|
274
|
+
|
|
275
|
+
it('GIVEN_repo_without_local_schema_files_WHEN_validating_THEN_uses_bundled_schema_contracts', async () => {
|
|
276
|
+
const { SchemaRegistry } = await import('../src/core/schemas.js');
|
|
277
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-schema-fallback-'));
|
|
278
|
+
const registry = new SchemaRegistry(repoRoot);
|
|
279
|
+
const result = await registry.validate('adapters.schema.json' as any, {});
|
|
280
|
+
|
|
281
|
+
expect(result.valid).toBe(true);
|
|
282
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
283
|
+
});
|
|
275
284
|
});
|
|
@@ -9,6 +9,7 @@ vi.mock('../src/core/git.js', () => ({
|
|
|
9
9
|
|
|
10
10
|
import { runGit } from '../src/core/git.js';
|
|
11
11
|
import { FeatureDeletionService } from '../src/application/services/feature-deletion-service.js';
|
|
12
|
+
import type { FeatureDeletionServicePort } from '../src/application/services/feature-deletion-service.js';
|
|
12
13
|
import { ERROR_CODES } from '../src/core/error-codes.js';
|
|
13
14
|
import type { RuntimeSessionsSnapshot } from '../src/core/runtime-sessions.js';
|
|
14
15
|
|
|
@@ -409,7 +410,7 @@ describe('FeatureDeletionService readLockResourcesFromIndex and normalizeFeature
|
|
|
409
410
|
locksRelease: vi.fn(async () => {})
|
|
410
411
|
};
|
|
411
412
|
|
|
412
|
-
const service = new FeatureDeletionService(port);
|
|
413
|
+
const service = new FeatureDeletionService(port as unknown as FeatureDeletionServicePort);
|
|
413
414
|
const result = await service.featureDelete('feature_lock_lease', {
|
|
414
415
|
dryRun: false,
|
|
415
416
|
confirm: true,
|
|
@@ -25,7 +25,7 @@ function makePort(overrides: Partial<FeatureLifecycleServicePort> = {}): Feature
|
|
|
25
25
|
repoEnsureWorktree: vi.fn(async () => ({ data: { worktree_path_abs: `/tmp/worktrees/f1` } })),
|
|
26
26
|
makeDefaultState: vi.fn(() => ({ feature_id: 'f1', status: 'planning', version: 0 })),
|
|
27
27
|
writeState: vi.fn(async () => {}),
|
|
28
|
-
withIndexLock: vi.fn(async (op: () => Promise<unknown>) => await op())
|
|
28
|
+
withIndexLock: vi.fn(async (op: () => Promise<unknown>) => await op()) as unknown as <T>(operation: () => Promise<T>) => Promise<T>,
|
|
29
29
|
readIndex: vi.fn(async () => ({ active: [], blocked: [], version: 0, updated_at: '' })),
|
|
30
30
|
writeIndex: vi.fn(async () => {}),
|
|
31
31
|
featureStateGet: vi.fn(async () => ({ data: { front_matter: { version: 1, status: 'planning' }, body: '' } })),
|
|
@@ -4,10 +4,30 @@ import path from 'node:path';
|
|
|
4
4
|
import { stableHash } from '../src/core/fs.js';
|
|
5
5
|
import { runCommand } from '../src/core/git.js';
|
|
6
6
|
|
|
7
|
+
async function copyTemplateSubtree(templateRoot, tempRoot, dirName) {
|
|
8
|
+
const sourceDir = path.join(templateRoot, dirName);
|
|
9
|
+
const destinationDir = path.join(tempRoot, dirName);
|
|
10
|
+
|
|
11
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
12
|
+
try {
|
|
13
|
+
await fs.cp(sourceDir, destinationDir, {
|
|
14
|
+
recursive: true,
|
|
15
|
+
filter: (entryPath) => !path.basename(entryPath).startsWith('.tmp-tools.md-')
|
|
16
|
+
});
|
|
17
|
+
return;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const errno = error as NodeJS.ErrnoException;
|
|
20
|
+
if (errno?.code !== 'ENOENT' || attempt === 2) {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
7
27
|
export async function makeTempRepo(templateRoot) {
|
|
8
28
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-test-'));
|
|
9
|
-
await
|
|
10
|
-
await
|
|
29
|
+
await copyTemplateSubtree(templateRoot, tempRoot, 'agentic');
|
|
30
|
+
await copyTemplateSubtree(templateRoot, tempRoot, 'apps');
|
|
11
31
|
await fs.writeFile(path.join(tempRoot, 'README.md'), '# temp repo\n', 'utf8');
|
|
12
32
|
|
|
13
33
|
// Runtime artifacts are generated under .aop and should not come from the template copy.
|