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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fsSync from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
import type { SchemaValidator } from '../../core/schemas.js';
|
|
7
|
+
|
|
8
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
export interface LoadPolicyResult {
|
|
11
|
+
userPolicy: Record<string, unknown>;
|
|
12
|
+
mergedPolicy: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveBundledDefaultsPath(startDir: string): string | null {
|
|
16
|
+
let current = path.resolve(startDir);
|
|
17
|
+
while (true) {
|
|
18
|
+
const candidate = path.join(current, 'agentic', 'orchestrator', 'defaults', 'policy.defaults.yaml');
|
|
19
|
+
if (fsSync.existsSync(candidate)) {
|
|
20
|
+
return candidate;
|
|
21
|
+
}
|
|
22
|
+
const parent = path.dirname(current);
|
|
23
|
+
if (parent === current) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
current = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const BUNDLED_DEFAULTS_PATH = resolveBundledDefaultsPath(MODULE_DIR);
|
|
31
|
+
|
|
32
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
33
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function clone(value: unknown): unknown {
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map(clone);
|
|
39
|
+
}
|
|
40
|
+
if (isObject(value)) {
|
|
41
|
+
const out: Record<string, unknown> = {};
|
|
42
|
+
for (const key of Object.keys(value)) {
|
|
43
|
+
out[key] = clone(value[key]);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Deep-merges `user` over `defaults` according to ADP-3 semantics:
|
|
52
|
+
* - scalar in user → replaces default scalar
|
|
53
|
+
* - object in both → recursive merge
|
|
54
|
+
* - array in user → replaces default array wholesale
|
|
55
|
+
* - undefined user fields → inherit from defaults
|
|
56
|
+
*/
|
|
57
|
+
export function mergePolicy(defaults: unknown, user: unknown): unknown {
|
|
58
|
+
if (Array.isArray(user)) {
|
|
59
|
+
return user.map(clone);
|
|
60
|
+
}
|
|
61
|
+
if (isObject(defaults) && isObject(user)) {
|
|
62
|
+
const out: Record<string, unknown> = clone(defaults) as Record<string, unknown>;
|
|
63
|
+
for (const key of Object.keys(user)) {
|
|
64
|
+
const userVal = user[key];
|
|
65
|
+
if (userVal === undefined) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
out[key] = key in out ? mergePolicy(out[key], userVal) : clone(userVal);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
return clone(user);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Loads and composes a canonical policy by:
|
|
77
|
+
* 1. Reading bundled `policy.defaults.yaml`
|
|
78
|
+
* 2. Reading the user's `policy.yaml` at `policyPath`
|
|
79
|
+
* 3. Deep-merging user overrides over defaults (ADP-3 semantics)
|
|
80
|
+
* 4. Validating the merged result against `policy.schema.json`
|
|
81
|
+
*
|
|
82
|
+
* Throws with a descriptive error if defaults cannot be located, if either
|
|
83
|
+
* file cannot be read, or if the merged result fails schema validation.
|
|
84
|
+
*/
|
|
85
|
+
export async function loadComposedPolicy(
|
|
86
|
+
_repoRoot: string,
|
|
87
|
+
policyPath: string,
|
|
88
|
+
schemaRegistry: SchemaValidator
|
|
89
|
+
): Promise<LoadPolicyResult> {
|
|
90
|
+
if (!BUNDLED_DEFAULTS_PATH) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
'policy_defaults_not_found: bundled defaults artifact could not be located; ' +
|
|
93
|
+
'ensure agentic/orchestrator/defaults/policy.defaults.yaml exists in the AOP installation'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const defaultsText = await fs.readFile(BUNDLED_DEFAULTS_PATH, 'utf8');
|
|
98
|
+
const defaults = YAML.parse(defaultsText) as Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
const userText = await fs.readFile(policyPath, 'utf8');
|
|
101
|
+
const userPolicy = (YAML.parse(userText) as Record<string, unknown> | null) ?? {};
|
|
102
|
+
|
|
103
|
+
const mergedPolicy = mergePolicy(defaults, userPolicy) as Record<string, unknown>;
|
|
104
|
+
|
|
105
|
+
const validation = await schemaRegistry.validate('policy.schema.json', mergedPolicy);
|
|
106
|
+
if (!validation.valid) {
|
|
107
|
+
const messages = validation.errors.map((e) => e.message ?? String(e)).join('; ');
|
|
108
|
+
throw new Error(
|
|
109
|
+
`invalid_policy_yaml: ${messages} ` +
|
|
110
|
+
`(merged from ${policyPath} over bundled defaults; check policy.yaml for invalid overrides)`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { userPolicy, mergedPolicy };
|
|
115
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
2
5
|
import { runCli } from '../interfaces/cli/bootstrap.js';
|
|
3
6
|
import type { RuntimeContext } from './types.js';
|
|
4
7
|
|
|
@@ -9,8 +12,39 @@ export async function main(
|
|
|
9
12
|
return await runCli(argv, runtime);
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
function normalizePathCandidates(value: string): Set<string> {
|
|
16
|
+
const candidates = new Set<string>();
|
|
17
|
+
const resolved = path.resolve(value);
|
|
18
|
+
candidates.add(resolved);
|
|
19
|
+
try {
|
|
20
|
+
candidates.add(fs.realpathSync.native(resolved));
|
|
21
|
+
} catch {
|
|
22
|
+
// Keep resolved path fallback when realpath is unavailable.
|
|
23
|
+
}
|
|
24
|
+
return candidates;
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
export function isDirectExecution(importMetaUrl: string, argv1: string | undefined): boolean {
|
|
13
|
-
|
|
28
|
+
if (!argv1) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let importMetaPath: string;
|
|
33
|
+
try {
|
|
34
|
+
importMetaPath = fileURLToPath(importMetaUrl);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const modulePathCandidates = normalizePathCandidates(importMetaPath);
|
|
40
|
+
const argvPathCandidates = normalizePathCandidates(argv1);
|
|
41
|
+
|
|
42
|
+
for (const candidate of modulePathCandidates) {
|
|
43
|
+
if (argvPathCandidates.has(candidate)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
14
48
|
}
|
|
15
49
|
|
|
16
50
|
export function runAsEntrypoint(
|
|
@@ -71,7 +71,8 @@ const COMMAND_HELP: Record<CliCommand, CommandHelp> = {
|
|
|
71
71
|
description: 'Initialise agentic orchestrator configuration in the current directory.',
|
|
72
72
|
flags: [
|
|
73
73
|
{ flag: '--auto', description: 'Run non-interactively with defaults' },
|
|
74
|
-
{ flag: '--force', description: 'Overwrite existing configuration' }
|
|
74
|
+
{ flag: '--force', description: 'Overwrite existing configuration' },
|
|
75
|
+
{ flag: '--advanced-policy', description: 'Generate full explicit policy.yaml with all advanced controls (default: lean policy)' }
|
|
75
76
|
]
|
|
76
77
|
},
|
|
77
78
|
[CliCommand.Dashboard]: {
|
|
@@ -7,14 +7,20 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import YAML from 'yaml';
|
|
9
9
|
import { SchemaRegistry } from '../core/schemas.js';
|
|
10
|
+
import { loadComposedPolicy } from '../application/services/policy-loader-service.js';
|
|
11
|
+
import { AGENT_PROVIDER_SLOT, SCM_PROVIDER_SLOT, globalAdapterRegistry } from '../application/adapters/adapter-registry.js';
|
|
10
12
|
|
|
11
13
|
const execFileAsync = promisify(execFile);
|
|
12
14
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
15
|
const TEMPLATE_ROOT = path.resolve(MODULE_DIR, '../../../../agentic/orchestrator');
|
|
16
|
+
const DEFAULT_AGENT_PROVIDER = 'custom';
|
|
17
|
+
const DEFAULT_AGENT_MODEL = 'local-default';
|
|
18
|
+
const DEFAULT_SCM_PROVIDER = 'github';
|
|
14
19
|
|
|
15
20
|
export interface InitOptions {
|
|
16
21
|
auto?: boolean;
|
|
17
22
|
force?: boolean;
|
|
23
|
+
advanced_policy?: boolean;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
type TestFramework = 'vitest' | 'jest' | 'pytest' | 'maven' | 'gradle';
|
|
@@ -34,6 +40,9 @@ interface WizardConfig {
|
|
|
34
40
|
maxParallelGateRuns: number;
|
|
35
41
|
dashboardPort: number;
|
|
36
42
|
framework: TestFramework;
|
|
43
|
+
defaultProvider: string;
|
|
44
|
+
defaultModel: string;
|
|
45
|
+
scmProvider: string;
|
|
37
46
|
notifications: {
|
|
38
47
|
desktop: boolean;
|
|
39
48
|
slack: boolean;
|
|
@@ -159,7 +168,34 @@ function escapeYamlString(value: string): string {
|
|
|
159
168
|
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
160
169
|
}
|
|
161
170
|
|
|
162
|
-
function
|
|
171
|
+
function generateLeanPolicyYaml(wizard: WizardConfig): string {
|
|
172
|
+
const notificationsEnabled = wizard.notifications.desktop || wizard.notifications.slack || wizard.notifications.webhook;
|
|
173
|
+
return `version: 1
|
|
174
|
+
worktree:
|
|
175
|
+
base_branch: ${wizard.baseBranch}
|
|
176
|
+
supervisor:
|
|
177
|
+
max_parallel_gate_runs: ${wizard.maxParallelGateRuns}
|
|
178
|
+
dashboard:
|
|
179
|
+
enabled: true
|
|
180
|
+
port: ${wizard.dashboardPort}
|
|
181
|
+
merge_policy:
|
|
182
|
+
require_user_approval: true
|
|
183
|
+
notifications:
|
|
184
|
+
enabled: ${notificationsEnabled}
|
|
185
|
+
channels:
|
|
186
|
+
desktop:
|
|
187
|
+
enabled: ${wizard.notifications.desktop}
|
|
188
|
+
slack:
|
|
189
|
+
enabled: ${wizard.notifications.slack}
|
|
190
|
+
webhook: "${escapeYamlString(wizard.notifications.slackWebhook)}"
|
|
191
|
+
channel: "#aop-alerts"
|
|
192
|
+
webhook:
|
|
193
|
+
enabled: ${wizard.notifications.webhook}
|
|
194
|
+
url: "${escapeYamlString(wizard.notifications.webhookUrl)}"
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function generateFullPolicyYaml(context: GitContext, wizard: WizardConfig): string {
|
|
163
199
|
const notificationsEnabled = wizard.notifications.desktop || wizard.notifications.slack || wizard.notifications.webhook;
|
|
164
200
|
return `version: 1
|
|
165
201
|
commit_policy:
|
|
@@ -186,7 +222,6 @@ locks:
|
|
|
186
222
|
multiplier: 2
|
|
187
223
|
jitter_ms: 150
|
|
188
224
|
protected_areas:
|
|
189
|
-
- agentic/orchestrator/schemas
|
|
190
225
|
- agentic/orchestrator/policy.yaml
|
|
191
226
|
required_modes:
|
|
192
227
|
- fast
|
|
@@ -353,7 +388,7 @@ capabilities:
|
|
|
353
388
|
`;
|
|
354
389
|
}
|
|
355
390
|
|
|
356
|
-
function generateAgentsYaml(): string {
|
|
391
|
+
function generateAgentsYaml(wizard: WizardConfig): string {
|
|
357
392
|
return `version: 1
|
|
358
393
|
roles:
|
|
359
394
|
planner:
|
|
@@ -364,17 +399,25 @@ roles:
|
|
|
364
399
|
system_prompt_path: agentic/orchestrator/prompts/qa.system.md
|
|
365
400
|
missing_prompt_behavior: ignore
|
|
366
401
|
runtime:
|
|
367
|
-
default_provider:
|
|
368
|
-
default_model:
|
|
402
|
+
default_provider: ${wizard.defaultProvider}
|
|
403
|
+
default_model: ${wizard.defaultModel}
|
|
369
404
|
provider_config_env: AOP_PROVIDER_CONFIG_ENV
|
|
370
405
|
role_provider_overrides: {}
|
|
371
406
|
`;
|
|
372
407
|
}
|
|
373
408
|
|
|
374
|
-
function generateAdaptersYaml(): string {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
409
|
+
function generateAdaptersYaml(wizard: WizardConfig): string {
|
|
410
|
+
const notificationChannel = wizard.notifications.desktop
|
|
411
|
+
? 'desktop'
|
|
412
|
+
: wizard.notifications.slack
|
|
413
|
+
? 'slack'
|
|
414
|
+
: wizard.notifications.webhook
|
|
415
|
+
? 'webhook'
|
|
416
|
+
: 'desktop';
|
|
417
|
+
const activityDetector = resolveActivityDetector(wizard.defaultProvider);
|
|
418
|
+
return `notification-channel: ${notificationChannel}
|
|
419
|
+
activity-detector: ${activityDetector}
|
|
420
|
+
scm-provider: ${wizard.scmProvider}
|
|
378
421
|
`;
|
|
379
422
|
}
|
|
380
423
|
|
|
@@ -410,18 +453,56 @@ function parseNotificationChannels(raw: string): { desktop: boolean; slack: bool
|
|
|
410
453
|
};
|
|
411
454
|
}
|
|
412
455
|
|
|
456
|
+
function parseAdapterName(raw: string, supportedNames: ReadonlySet<string>, fallback: string): string {
|
|
457
|
+
const value = raw.trim();
|
|
458
|
+
if (supportedNames.has(value)) {
|
|
459
|
+
return value;
|
|
460
|
+
}
|
|
461
|
+
return fallback;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function resolveActivityDetector(defaultProvider: string): string {
|
|
465
|
+
if (defaultProvider === 'claude') {
|
|
466
|
+
return 'claude-jsonl';
|
|
467
|
+
}
|
|
468
|
+
if (defaultProvider === 'codex') {
|
|
469
|
+
return 'codex-rpc';
|
|
470
|
+
}
|
|
471
|
+
return 'process-heuristic';
|
|
472
|
+
}
|
|
473
|
+
|
|
413
474
|
async function askWithDefault(prompt: PromptSession, label: string, defaultValue: string): Promise<string> {
|
|
414
475
|
const raw = (await prompt.question(`${label} [${defaultValue}]: `)).trim();
|
|
415
476
|
return raw.length > 0 ? raw : defaultValue;
|
|
416
477
|
}
|
|
417
478
|
|
|
418
479
|
async function collectWizardConfig(
|
|
419
|
-
defaults: {
|
|
480
|
+
defaults: {
|
|
481
|
+
branch: string;
|
|
482
|
+
framework: TestFramework;
|
|
483
|
+
defaultProvider: string;
|
|
484
|
+
defaultModel: string;
|
|
485
|
+
defaultScmProvider: string;
|
|
486
|
+
},
|
|
420
487
|
promptFactory: () => PromptSession
|
|
421
488
|
): Promise<WizardConfig> {
|
|
422
489
|
const prompt = promptFactory();
|
|
423
490
|
try {
|
|
491
|
+
const supportedProviders = new Set(globalAdapterRegistry.list(AGENT_PROVIDER_SLOT.name).map((adapter) => adapter.name));
|
|
492
|
+
const supportedScmProviders = new Set(globalAdapterRegistry.list(SCM_PROVIDER_SLOT.name).map((adapter) => adapter.name));
|
|
493
|
+
|
|
424
494
|
const baseBranch = await askWithDefault(prompt, 'Worktree base branch', defaults.branch);
|
|
495
|
+
const defaultProviderRaw = await askWithDefault(
|
|
496
|
+
prompt,
|
|
497
|
+
`Default agent provider (${Array.from(supportedProviders).join('|')})`,
|
|
498
|
+
defaults.defaultProvider
|
|
499
|
+
);
|
|
500
|
+
const defaultModel = await askWithDefault(prompt, 'Default model', defaults.defaultModel);
|
|
501
|
+
const scmProviderRaw = await askWithDefault(
|
|
502
|
+
prompt,
|
|
503
|
+
`SCM provider (${Array.from(supportedScmProviders).join('|')})`,
|
|
504
|
+
defaults.defaultScmProvider
|
|
505
|
+
);
|
|
425
506
|
const maxParallelGateRunsRaw = await askWithDefault(prompt, 'Max parallel gate runs', '3');
|
|
426
507
|
const dashboardPortRaw = await askWithDefault(prompt, 'Dashboard port', '3000');
|
|
427
508
|
const channelsRaw = await askWithDefault(
|
|
@@ -448,6 +529,9 @@ async function collectWizardConfig(
|
|
|
448
529
|
|
|
449
530
|
return {
|
|
450
531
|
baseBranch,
|
|
532
|
+
defaultProvider: parseAdapterName(defaultProviderRaw, supportedProviders, defaults.defaultProvider),
|
|
533
|
+
defaultModel,
|
|
534
|
+
scmProvider: parseAdapterName(scmProviderRaw, supportedScmProviders, defaults.defaultScmProvider),
|
|
451
535
|
maxParallelGateRuns: parseInteger(maxParallelGateRunsRaw, 3, 1, 64),
|
|
452
536
|
dashboardPort: parseInteger(dashboardPortRaw, 3000, 1, 65535),
|
|
453
537
|
framework: parseFramework(frameworkRaw, defaults.framework),
|
|
@@ -465,7 +549,7 @@ async function collectWizardConfig(
|
|
|
465
549
|
}
|
|
466
550
|
|
|
467
551
|
async function loadTemplateFiles(
|
|
468
|
-
subdir: 'prompts'
|
|
552
|
+
subdir: 'prompts',
|
|
469
553
|
matcher: (filename: string) => boolean
|
|
470
554
|
): Promise<Array<{ rel: string; content: string }>> {
|
|
471
555
|
const sourceDir = path.join(TEMPLATE_ROOT, subdir);
|
|
@@ -499,6 +583,9 @@ export class InitCommandHandler {
|
|
|
499
583
|
const wizard = options.auto
|
|
500
584
|
? {
|
|
501
585
|
baseBranch: gitContext.defaultBranch,
|
|
586
|
+
defaultProvider: DEFAULT_AGENT_PROVIDER,
|
|
587
|
+
defaultModel: DEFAULT_AGENT_MODEL,
|
|
588
|
+
scmProvider: DEFAULT_SCM_PROVIDER,
|
|
502
589
|
maxParallelGateRuns: 3,
|
|
503
590
|
dashboardPort: 3000,
|
|
504
591
|
framework,
|
|
@@ -513,7 +600,10 @@ export class InitCommandHandler {
|
|
|
513
600
|
: await collectWizardConfig(
|
|
514
601
|
{
|
|
515
602
|
branch: gitContext.defaultBranch,
|
|
516
|
-
framework
|
|
603
|
+
framework,
|
|
604
|
+
defaultProvider: DEFAULT_AGENT_PROVIDER,
|
|
605
|
+
defaultModel: DEFAULT_AGENT_MODEL,
|
|
606
|
+
defaultScmProvider: DEFAULT_SCM_PROVIDER
|
|
517
607
|
},
|
|
518
608
|
this.promptFactory
|
|
519
609
|
);
|
|
@@ -521,19 +611,20 @@ export class InitCommandHandler {
|
|
|
521
611
|
const orchestratorDir = path.join(this.repoRoot, 'agentic', 'orchestrator');
|
|
522
612
|
await fs.mkdir(orchestratorDir, { recursive: true });
|
|
523
613
|
|
|
614
|
+
const policyContent = options.advanced_policy
|
|
615
|
+
? generateFullPolicyYaml({ ...gitContext, defaultBranch: wizard.baseBranch }, wizard)
|
|
616
|
+
: generateLeanPolicyYaml(wizard);
|
|
617
|
+
|
|
524
618
|
const configFiles: Array<{ rel: string; content: string }> = [
|
|
525
619
|
{
|
|
526
620
|
rel: 'agentic/orchestrator/policy.yaml',
|
|
527
|
-
content:
|
|
621
|
+
content: policyContent
|
|
528
622
|
},
|
|
529
623
|
{ rel: 'agentic/orchestrator/gates.yaml', content: generateGatesYaml(wizard.framework) },
|
|
530
|
-
{ rel: 'agentic/orchestrator/agents.yaml', content: generateAgentsYaml() },
|
|
531
|
-
{ rel: 'agentic/orchestrator/adapters.yaml', content: generateAdaptersYaml() }
|
|
532
|
-
];
|
|
533
|
-
const templateFiles = [
|
|
534
|
-
...(await loadTemplateFiles('prompts', (filename) => filename.endsWith('.md'))),
|
|
535
|
-
...(await loadTemplateFiles('schemas', (filename) => filename.endsWith('.json')))
|
|
624
|
+
{ rel: 'agentic/orchestrator/agents.yaml', content: generateAgentsYaml(wizard) },
|
|
625
|
+
{ rel: 'agentic/orchestrator/adapters.yaml', content: generateAdaptersYaml(wizard) }
|
|
536
626
|
];
|
|
627
|
+
const templateFiles = await loadTemplateFiles('prompts', (filename) => filename.endsWith('.md'));
|
|
537
628
|
const writableFiles = [...configFiles, ...templateFiles];
|
|
538
629
|
|
|
539
630
|
const created: string[] = [];
|
|
@@ -566,14 +657,25 @@ export class InitCommandHandler {
|
|
|
566
657
|
|
|
567
658
|
const validationWarnings: string[] = [];
|
|
568
659
|
const schemaRegistry = new SchemaRegistry(this.repoRoot);
|
|
569
|
-
|
|
570
|
-
|
|
660
|
+
|
|
661
|
+
// Validate policy.yaml via composition loader (merges user policy over bundled defaults
|
|
662
|
+
// then validates the merged result against the full schema).
|
|
663
|
+
const policyRel = 'agentic/orchestrator/policy.yaml';
|
|
664
|
+
if (!skipped.includes(policyRel)) {
|
|
665
|
+
try {
|
|
666
|
+
await loadComposedPolicy(this.repoRoot, path.join(this.repoRoot, policyRel), schemaRegistry);
|
|
667
|
+
} catch (err) {
|
|
668
|
+
validationWarnings.push(`Failed to validate ${policyRel}: ${String(err)}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const nonPolicyTargets: Array<{ schema: string; rel: string }> = [
|
|
571
673
|
{ schema: 'gates.schema.json', rel: 'agentic/orchestrator/gates.yaml' },
|
|
572
674
|
{ schema: 'agents.schema.json', rel: 'agentic/orchestrator/agents.yaml' },
|
|
573
675
|
{ schema: 'adapters.schema.json', rel: 'agentic/orchestrator/adapters.yaml' }
|
|
574
676
|
];
|
|
575
677
|
|
|
576
|
-
for (const { schema, rel } of
|
|
678
|
+
for (const { schema, rel } of nonPolicyTargets) {
|
|
577
679
|
if (skipped.includes(rel)) {
|
|
578
680
|
continue;
|
|
579
681
|
}
|
|
@@ -590,6 +692,17 @@ export class InitCommandHandler {
|
|
|
590
692
|
}
|
|
591
693
|
}
|
|
592
694
|
|
|
695
|
+
const policyMode = options.advanced_policy ? 'full (advanced)' : 'lean (common controls only)';
|
|
696
|
+
const nextSteps = [
|
|
697
|
+
`Generated policy.yaml in ${policyMode} mode`,
|
|
698
|
+
'Review generated files in agentic/orchestrator/{policy,gates,agents,adapters}.yaml',
|
|
699
|
+
'Verify prompts under agentic/orchestrator/prompts/',
|
|
700
|
+
'Run: aop run -fi <spec.md> to start orchestrating features'
|
|
701
|
+
];
|
|
702
|
+
if (!options.advanced_policy) {
|
|
703
|
+
nextSteps.push('To generate a full explicit policy with all advanced controls, re-run: aop init --advanced-policy --force');
|
|
704
|
+
}
|
|
705
|
+
|
|
593
706
|
return {
|
|
594
707
|
ok: true,
|
|
595
708
|
data: {
|
|
@@ -598,11 +711,7 @@ export class InitCommandHandler {
|
|
|
598
711
|
updated,
|
|
599
712
|
skipped,
|
|
600
713
|
validation_warnings: validationWarnings,
|
|
601
|
-
next_steps:
|
|
602
|
-
'Review generated files in agentic/orchestrator/{policy,gates,agents,adapters}.yaml',
|
|
603
|
-
'Verify prompts under agentic/orchestrator/prompts/ and schemas under agentic/orchestrator/schemas/',
|
|
604
|
-
'Run: aop run -fi <spec.md> to start orchestrating features'
|
|
605
|
-
]
|
|
714
|
+
next_steps: nextSteps
|
|
606
715
|
}
|
|
607
716
|
};
|
|
608
717
|
}
|
|
@@ -54,10 +54,7 @@ export class RetryCommandHandler {
|
|
|
54
54
|
|
|
55
55
|
const force = options.force === true;
|
|
56
56
|
let retryCountReset = false;
|
|
57
|
-
let retryExecuted: boolean;
|
|
58
57
|
let forcedRetryExecuted = false;
|
|
59
|
-
let retryMode: string | null;
|
|
60
|
-
let retryOutcome: string | null;
|
|
61
58
|
|
|
62
59
|
const current = await callCliTool(this.toolClient, this.runId, TOOLS.FEATURE_STATE_GET, {
|
|
63
60
|
feature_id: featureId
|
|
@@ -118,10 +115,10 @@ export class RetryCommandHandler {
|
|
|
118
115
|
profile: null,
|
|
119
116
|
mode: inferredMode
|
|
120
117
|
});
|
|
121
|
-
|
|
118
|
+
const retryExecuted = true;
|
|
119
|
+
const retryMode = inferredMode;
|
|
122
120
|
const gateData = gate.data as { overall?: string };
|
|
123
|
-
retryOutcome = typeof gateData.overall === 'string' ? gateData.overall : null;
|
|
124
|
-
retryExecuted = true;
|
|
121
|
+
const retryOutcome = typeof gateData.overall === 'string' ? gateData.overall : null;
|
|
125
122
|
|
|
126
123
|
return {
|
|
127
124
|
ok: true,
|
|
@@ -27,7 +27,6 @@ export class NodeCommandRunner implements CommandRunner {
|
|
|
27
27
|
async run(cmd: string, args: string[], options: RunCommandOptions = {}): Promise<CommandResult> {
|
|
28
28
|
return await new Promise((resolve) => {
|
|
29
29
|
let child;
|
|
30
|
-
let spawnError: Error | null = null;
|
|
31
30
|
|
|
32
31
|
try {
|
|
33
32
|
child = spawn(cmd, args, {
|
|
@@ -36,12 +35,11 @@ export class NodeCommandRunner implements CommandRunner {
|
|
|
36
35
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
37
36
|
});
|
|
38
37
|
} catch (err) {
|
|
39
|
-
spawnError = err as Error;
|
|
40
38
|
return resolve({
|
|
41
39
|
code: 127,
|
|
42
40
|
signal: null,
|
|
43
41
|
stdout: '',
|
|
44
|
-
stderr:
|
|
42
|
+
stderr: (err as Error).message,
|
|
45
43
|
timeout: false
|
|
46
44
|
});
|
|
47
45
|
}
|
|
@@ -32,6 +32,7 @@ import { CollisionQueueService } from '../application/services/collision-queue-s
|
|
|
32
32
|
import { FeatureDeletionService, type FeatureDeleteResult } from '../application/services/feature-deletion-service.js';
|
|
33
33
|
import { CostTrackingService } from '../application/services/cost-tracking-service.js';
|
|
34
34
|
import { PerformanceAnalyticsService, type FeatureOutcome } from '../application/services/performance-analytics-service.js';
|
|
35
|
+
import { loadComposedPolicy } from '../application/services/policy-loader-service.js';
|
|
35
36
|
import {
|
|
36
37
|
ACTIVITY_DETECTOR_SLOT,
|
|
37
38
|
NOTIFICATION_CHANNEL_SLOT,
|
|
@@ -344,12 +345,8 @@ export class AopKernel {
|
|
|
344
345
|
throw new Error(`invalid_gates_yaml:${JSON.stringify(gates.validation.errors)}`);
|
|
345
346
|
}
|
|
346
347
|
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
throw new Error(`invalid_policy_yaml:${JSON.stringify(policy.validation.errors)}`);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const parsedPolicy = policy.parsed as PolicyConfigSnapshot;
|
|
348
|
+
const { mergedPolicy } = await loadComposedPolicy(this.repoRoot, policyPath, this.schemaRegistry);
|
|
349
|
+
const parsedPolicy = mergedPolicy as PolicyConfigSnapshot;
|
|
353
350
|
const implementation = readObjectField(parsedPolicy, 'implementation');
|
|
354
351
|
const testing = readObjectField(parsedPolicy, 'testing');
|
|
355
352
|
|
|
@@ -1,5 +1,7 @@
|
|
|
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 YAML from 'yaml';
|
|
4
6
|
import Ajv2020Import from 'ajv/dist/2020.js';
|
|
5
7
|
import type { AnySchema, ErrorObject, ValidateFunction } from 'ajv';
|
|
@@ -30,6 +32,26 @@ type AjvLike = {
|
|
|
30
32
|
|
|
31
33
|
const RFC3339_DATE_TIME =
|
|
32
34
|
/^(\d{4})-(\d{2})-(\d{2})T([0-2]\d):([0-5]\d):([0-5]\d)(\.\d{1,9})?(Z|([+-])([0-2]\d):([0-5]\d))$/;
|
|
35
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
37
|
+
function resolveBundledSchemaRoot(startDir: string): string | null {
|
|
38
|
+
let current = path.resolve(startDir);
|
|
39
|
+
|
|
40
|
+
while (true) {
|
|
41
|
+
const candidate = path.join(current, 'agentic', 'orchestrator', 'schemas', 'policy.schema.json');
|
|
42
|
+
if (fsSync.existsSync(candidate)) {
|
|
43
|
+
return path.dirname(candidate);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const parent = path.dirname(current);
|
|
47
|
+
if (parent === current) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
current = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const BUNDLED_SCHEMA_ROOT = resolveBundledSchemaRoot(MODULE_DIR);
|
|
33
55
|
|
|
34
56
|
function isStrictRfc3339DateTime(value: string): boolean {
|
|
35
57
|
if (typeof value !== 'string') {
|
|
@@ -72,11 +94,13 @@ function isStrictRfc3339DateTime(value: string): boolean {
|
|
|
72
94
|
|
|
73
95
|
export class SchemaRegistry implements SchemaValidator {
|
|
74
96
|
readonly repoRoot: string;
|
|
97
|
+
readonly schemaRoot: string;
|
|
75
98
|
private readonly ajv: AjvLike;
|
|
76
99
|
private readonly validators: Map<SchemaId, ValidateFunction>;
|
|
77
100
|
|
|
78
101
|
constructor(repoRoot: string) {
|
|
79
102
|
this.repoRoot = repoRoot;
|
|
103
|
+
this.schemaRoot = this.resolveSchemaRoot();
|
|
80
104
|
const Ajv2020Ctor = Ajv2020Import as unknown as new (options?: Record<string, unknown>) => AjvLike;
|
|
81
105
|
this.ajv = new Ajv2020Ctor({ allErrors: true, strict: false });
|
|
82
106
|
this.ajv.addFormat?.('date-time', {
|
|
@@ -86,8 +110,19 @@ export class SchemaRegistry implements SchemaValidator {
|
|
|
86
110
|
this.validators = new Map();
|
|
87
111
|
}
|
|
88
112
|
|
|
113
|
+
private resolveSchemaRoot(): string {
|
|
114
|
+
const repoSchemaRoot = path.join(this.repoRoot, 'agentic', 'orchestrator', 'schemas');
|
|
115
|
+
if (fsSync.existsSync(path.join(repoSchemaRoot, 'policy.schema.json'))) {
|
|
116
|
+
return repoSchemaRoot;
|
|
117
|
+
}
|
|
118
|
+
if (BUNDLED_SCHEMA_ROOT) {
|
|
119
|
+
return BUNDLED_SCHEMA_ROOT;
|
|
120
|
+
}
|
|
121
|
+
return repoSchemaRoot;
|
|
122
|
+
}
|
|
123
|
+
|
|
89
124
|
schemaPath(name: SchemaId): string {
|
|
90
|
-
return path.join(this.
|
|
125
|
+
return path.join(this.schemaRoot, name);
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
async loadSchema(name: SchemaId): Promise<ValidateFunction> {
|
|
@@ -157,7 +157,7 @@ export async function runCli(
|
|
|
157
157
|
|
|
158
158
|
if (options.command === CliCommand.Init) {
|
|
159
159
|
const handler = new InitCommandHandler(repoRoot);
|
|
160
|
-
const payload = await handler.execute({ auto: options.auto, force: options.force });
|
|
160
|
+
const payload = await handler.execute({ auto: options.auto, force: options.force, advanced_policy: options.advanced_policy });
|
|
161
161
|
printPayload(payload);
|
|
162
162
|
return 0;
|
|
163
163
|
}
|