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.
Files changed (108) hide show
  1. package/.claude/settings.local.json +6 -1
  2. package/README.md +81 -54
  3. package/agentic/orchestrator/defaults/policy.defaults.yaml +212 -0
  4. package/agentic/orchestrator/schemas/adapters.schema.json +17 -4
  5. package/agentic/orchestrator/schemas/agents.schema.json +58 -13
  6. package/agentic/orchestrator/schemas/gates.schema.json +88 -17
  7. package/agentic/orchestrator/schemas/index.schema.json +172 -37
  8. package/agentic/orchestrator/schemas/multi-project.schema.json +85 -17
  9. package/agentic/orchestrator/schemas/plan.schema.json +135 -30
  10. package/agentic/orchestrator/schemas/policy.schema.json +198 -69
  11. package/agentic/orchestrator/schemas/policy.user.schema.json +165 -0
  12. package/agentic/orchestrator/schemas/qa_test_index.schema.json +71 -14
  13. package/agentic/orchestrator/schemas/state.schema.json +196 -39
  14. package/apps/control-plane/scripts/validate-architecture-rules.mjs +5 -3
  15. package/apps/control-plane/scripts/validate-docker-mcp-contract.mjs +0 -3
  16. package/apps/control-plane/scripts/validate-mcp-contracts.ts +2 -6
  17. package/apps/control-plane/src/application/services/policy-loader-service.ts +115 -0
  18. package/apps/control-plane/src/cli/aop.ts +35 -1
  19. package/apps/control-plane/src/cli/cli-argument-parser.ts +4 -0
  20. package/apps/control-plane/src/cli/help-command-handler.ts +2 -1
  21. package/apps/control-plane/src/cli/init-command-handler.ts +136 -27
  22. package/apps/control-plane/src/cli/retry-command-handler.ts +3 -6
  23. package/apps/control-plane/src/cli/types.ts +1 -0
  24. package/apps/control-plane/src/core/git.ts +1 -3
  25. package/apps/control-plane/src/core/kernel.ts +3 -6
  26. package/apps/control-plane/src/core/schemas.ts +36 -1
  27. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +1 -1
  28. package/apps/control-plane/src/mcp/tool-registry-loader.ts +37 -1
  29. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +1 -1
  30. package/apps/control-plane/test/aop.spec.ts +37 -0
  31. package/apps/control-plane/test/batch-operations.spec.ts +5 -3
  32. package/apps/control-plane/test/bootstrap-attach.spec.ts +28 -18
  33. package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +25 -15
  34. package/apps/control-plane/test/bootstrap.spec.ts +30 -22
  35. package/apps/control-plane/test/cli.unit.spec.ts +55 -10
  36. package/apps/control-plane/test/core-utils.spec.ts +12 -3
  37. package/apps/control-plane/test/feature-deletion-service.spec.ts +2 -1
  38. package/apps/control-plane/test/feature-lifecycle.spec.ts +1 -1
  39. package/apps/control-plane/test/helpers.ts +22 -2
  40. package/apps/control-plane/test/init-wizard.spec.ts +160 -7
  41. package/apps/control-plane/test/issue-tracker.spec.ts +3 -3
  42. package/apps/control-plane/test/kernel.spec.ts +62 -0
  43. package/apps/control-plane/test/lock-service.spec.ts +4 -4
  44. package/apps/control-plane/test/policy-loader-service.spec.ts +346 -0
  45. package/apps/control-plane/test/reactions.spec.ts +8 -6
  46. package/apps/control-plane/test/resume-command.spec.ts +31 -15
  47. package/apps/control-plane/test/supervisor.unit.spec.ts +2 -2
  48. package/apps/control-plane/test/tool-registry-loader.spec.ts +12 -0
  49. package/apps/control-plane/vitest.config.ts +1 -1
  50. package/dist/apps/control-plane/application/services/policy-loader-service.d.ts +24 -0
  51. package/dist/apps/control-plane/application/services/policy-loader-service.js +90 -0
  52. package/dist/apps/control-plane/application/services/policy-loader-service.js.map +1 -0
  53. package/dist/apps/control-plane/application/tools/tool-metadata.js +1 -0
  54. package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
  55. package/dist/apps/control-plane/cli/aop.js +33 -1
  56. package/dist/apps/control-plane/cli/aop.js.map +1 -1
  57. package/dist/apps/control-plane/cli/cli-argument-parser.js +4 -0
  58. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  59. package/dist/apps/control-plane/cli/dashboard-command-handler.js +11 -5
  60. package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -1
  61. package/dist/apps/control-plane/cli/help-command-handler.js +5 -4
  62. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
  63. package/dist/apps/control-plane/cli/init-command-handler.d.ts +1 -0
  64. package/dist/apps/control-plane/cli/init-command-handler.js +110 -26
  65. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
  66. package/dist/apps/control-plane/cli/retry-command-handler.js +3 -6
  67. package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -1
  68. package/dist/apps/control-plane/cli/types.d.ts +1 -0
  69. package/dist/apps/control-plane/core/git.js +1 -3
  70. package/dist/apps/control-plane/core/git.js.map +1 -1
  71. package/dist/apps/control-plane/core/kernel.js +3 -5
  72. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  73. package/dist/apps/control-plane/core/schemas.d.ts +2 -0
  74. package/dist/apps/control-plane/core/schemas.js +31 -1
  75. package/dist/apps/control-plane/core/schemas.js.map +1 -1
  76. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +1 -1
  77. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  78. package/dist/apps/control-plane/mcp/tool-registry-loader.d.ts +2 -0
  79. package/dist/apps/control-plane/mcp/tool-registry-loader.js +31 -1
  80. package/dist/apps/control-plane/mcp/tool-registry-loader.js.map +1 -1
  81. package/dist/apps/control-plane/supervisor/build-wave-executor.js +1 -1
  82. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  83. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +1 -1
  84. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  85. package/package.json +18 -17
  86. package/packages/web-dashboard/package.json +1 -1
  87. package/packages/web-dashboard/src/app/api/actions/route.ts +2 -2
  88. package/packages/web-dashboard/src/app/api/events/route.ts +1 -1
  89. package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +1 -1
  90. package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +1 -1
  91. package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +1 -1
  92. package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +2 -2
  93. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +1 -1
  94. package/packages/web-dashboard/src/app/api/projects/route.ts +2 -2
  95. package/packages/web-dashboard/src/app/api/status/route.ts +1 -1
  96. package/packages/web-dashboard/src/app/page.tsx +1 -1
  97. package/packages/web-dashboard/src/lib/aop-client.ts +1 -1
  98. package/packages/web-dashboard/src/lib/orchestrator-tools.ts +1 -1
  99. package/packages/web-dashboard/tsconfig.json +1 -0
  100. package/spec-files/completed/agentic_orchestrator_init_policy_ux_simplification_spec.md +481 -0
  101. package/spec-files/outstanding/agentic_orchestrator_artifact_database_publishing_spec.md +500 -0
  102. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +500 -195
  103. package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +471 -162
  104. package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +532 -268
  105. package/spec-files/outstanding/agentic_orchestrator_productization_commercial_spec.md +645 -0
  106. package/spec-files/progress.md +99 -2
  107. package/tsconfig.base.json +4 -0
  108. /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 path.join(this.repoRoot, 'agentic', 'orchestrator', 'tools');
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 = 0;
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: vi.fn().mockImplementation(() => ({
17
- resolve: specResolverResolveMock
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: vi.fn().mockImplementation(() => ({
42
- execute: vi.fn(async () => ({ ok: true, data: { command: 'init', created: [], updated: [], skipped: [], validation_warnings: [], next_steps: [] } }))
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: vi.fn().mockImplementation(() => ({
48
- execute: vi.fn(async () => ({ ok: true, data: { message: 'started', port: 3000 } }))
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: vi.fn().mockImplementation(() => ({
54
- execute: vi.fn(async () => ({ ok: true, data: { status: 'running' } }))
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: vi.fn().mockImplementation(() => ({
60
- execute: vi.fn(async () => ({ ok: true, data: { resumed: false } }))
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: vi.fn().mockImplementation(() => ({
66
- execute: vi.fn(async () => ({
67
- ok: true,
68
- data: { command: 'attach', feature_id: 'feat_x', status: 'building', role: 'builder', session_id: 'sess-1', attached: true }
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: vi.fn().mockImplementation(() => ({
69
- execute: vi.fn(async () => ({ ok: true, data: { command: 'init', created: [], updated: [], skipped: [], validation_warnings: [], next_steps: [] } }))
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: vi.fn().mockImplementation(() => ({
75
- execute: vi.fn(async () => ({ ok: true, data: { message: 'started', port: 3000 } }))
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: vi.fn().mockImplementation(() => ({
81
- execute: vi.fn(async () => ({ ok: true, data: { status: 'running' } }))
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: vi.fn().mockImplementation(() => ({
87
- execute: vi.fn(async () => ({ ok: true, data: { resumed: false } }))
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: vi.fn().mockImplementation(() => ({
93
- execute: vi.fn(async () => ({ ok: true, data: { command: 'attach' } }))
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: vi.fn().mockImplementation(() => ({
67
- execute: vi.fn(async () => ({
68
- ok: true,
69
- data: {
70
- command: 'init',
71
- created: [],
72
- updated: [],
73
- skipped: [],
74
- validation_warnings: [],
75
- next_steps: []
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: vi.fn().mockImplementation(() => ({
83
- execute: vi.fn(async () => ({ ok: true, data: { message: 'started', port: 3000 } }))
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: vi.fn().mockImplementation(() => ({
89
- execute: vi.fn(async () => ({ ok: true, data: { status: 'running' } }))
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: vi.fn().mockImplementation(() => ({
95
- execute: vi.fn(async () => ({ ok: true, data: { resumed: false } }))
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.mockClear();
184
- recoverFromStateMock.mockClear();
185
- readIndexMock.mockClear();
186
- getRuntimeSessionsMock.mockClear();
187
- featureDiscoverSpecsMock.mockClear();
188
- featureStateGetMock.mockClear();
189
- createToolingRuntimeMock.mockClear();
190
- resolveProviderSelectionMock.mockClear();
191
- supervisorConstructorOptionsMock.mockClear();
192
- supervisorStartMock.mockClear();
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 = (await import('ajv')).default;
266
+ const { Ajv: AjvCtor } = await import('ajv');
267
267
  const ajv = new AjvCtor({ strict: false });
268
268
  const validator = ajv.compile({ type: 'object' });
269
- // @ts-expect-error - access private validators map for cache testing
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 fs.cp(path.join(templateRoot, 'agentic'), path.join(tempRoot, 'agentic'), { recursive: true });
10
- await fs.cp(path.join(templateRoot, 'apps'), path.join(tempRoot, 'apps'), { recursive: true });
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.