@ikon85/agent-workflow-kit 0.32.1 → 0.34.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.
- package/.agents/skills/ask-matt/SKILL.md +4 -3
- package/.agents/skills/audit-skills/SKILL.md +6 -0
- package/.agents/skills/board-to-waves/SKILL.md +6 -2
- package/.agents/skills/code-review/SKILL.md +6 -0
- package/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +11 -1
- package/.agents/skills/codex-adapter-sync/SKILL.md +23 -19
- package/.agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +6 -0
- package/.agents/skills/improve-codebase-architecture/SKILL.md +10 -1
- package/.agents/skills/kit-update/SKILL.md +13 -0
- package/.agents/skills/orchestrate-wave/SKILL.md +54 -54
- package/.agents/skills/orchestrate-wave/references/builder-contract.md +16 -3
- package/.agents/skills/orchestrate-wave/references/dispatch-subagents.md +91 -0
- package/.agents/skills/orchestrate-wave/references/dispatch-workflow.md +66 -0
- package/.agents/skills/orchestrate-wave/references/report-contracts.md +42 -0
- package/.agents/skills/research/SKILL.md +6 -0
- package/.agents/skills/scale-check/SKILL.md +9 -7
- package/.agents/skills/setup-workflow/SKILL.md +47 -1
- package/.agents/skills/setup-workflow/board-sync.md +7 -2
- package/.agents/skills/setup-workflow/workflow-overview.md +1 -2
- package/.agents/skills/to-issues/SKILL.md +66 -8
- package/.agents/skills/to-waves/SKILL.md +24 -12
- package/.claude/skills/ask-matt/SKILL.md +4 -3
- package/.claude/skills/audit-skills/SKILL.md +6 -0
- package/.claude/skills/board-to-waves/SKILL.md +6 -2
- package/.claude/skills/code-review/SKILL.md +6 -0
- package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +8 -0
- package/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +6 -0
- package/.claude/skills/improve-codebase-architecture/SKILL.md +6 -0
- package/.claude/skills/kit-update/SKILL.md +13 -0
- package/.claude/skills/orchestrate-wave/SKILL.md +54 -54
- package/.claude/skills/orchestrate-wave/references/builder-contract.md +16 -3
- package/.claude/skills/orchestrate-wave/references/dispatch-subagents.md +91 -0
- package/.claude/skills/orchestrate-wave/references/dispatch-workflow.md +66 -0
- package/.claude/skills/orchestrate-wave/references/report-contracts.md +42 -0
- package/.claude/skills/research/SKILL.md +6 -0
- package/.claude/skills/scale-check/SKILL.md +9 -7
- package/.claude/skills/setup-workflow/SKILL.md +47 -1
- package/.claude/skills/setup-workflow/board-sync.md +7 -2
- package/.claude/skills/setup-workflow/workflow-overview.md +1 -2
- package/.claude/skills/to-issues/SKILL.md +66 -8
- package/.claude/skills/to-waves/SKILL.md +24 -12
- package/README.md +126 -11
- package/agent-workflow-kit.package.json +291 -39
- package/docs/adr/0002-capability-gated-orchestration.md +70 -0
- package/docs/adr/0005-to-issues-is-the-planning-facade.md +42 -0
- package/docs/adr/0006-routing-knowledge-access-and-policy-are-separate.md +91 -0
- package/docs/agents/skills/local-ci.md +89 -0
- package/docs/agents/wave-anchor-template.md +2 -2
- package/docs/methodology.html +1 -1
- package/docs/methodology.svg +1 -1
- package/docs/workflow.html +2 -2
- package/docs/workflow.png +0 -0
- package/package.json +1 -1
- package/scripts/codex-exec.sh +47 -8
- package/scripts/codex-exec.test.mjs +56 -0
- package/scripts/test_codex_adapter_sync_contract.py +30 -15
- package/scripts/test_orchestrate_wave_contract.py +209 -0
- package/scripts/test_program_planning_contract.py +70 -0
- package/scripts/test_skill_portability_lint.py +54 -0
- package/scripts/test_worktree_setup_base_guard.py +140 -0
- package/scripts/worktree-lifecycle/setup.py +40 -0
- package/src/cli.mjs +109 -2
- package/src/commands/init.mjs +17 -1
- package/src/commands/routing-policy-update.mjs +204 -0
- package/src/commands/update.mjs +22 -0
- package/src/lib/agentSurfaceRegistry.mjs +60 -0
- package/src/lib/bundle.mjs +34 -0
- package/src/lib/capabilityMatrix.mjs +179 -0
- package/src/lib/dispatchReceipt.mjs +162 -0
- package/src/lib/frontendWorkloads.mjs +170 -0
- package/src/lib/reconcileReconReports.mjs +111 -0
- package/src/lib/reportValidator.mjs +186 -0
- package/src/lib/routeDispatcher.mjs +158 -0
- package/src/lib/routingAccessGraph.mjs +105 -0
- package/src/lib/routingAdapters/claude.mjs +62 -0
- package/src/lib/routingAdapters/codex.mjs +136 -0
- package/src/lib/routingCatalog.mjs +123 -0
- package/src/lib/routingEvidenceCache.mjs +222 -0
- package/src/lib/routingIntent.mjs +93 -0
- package/src/lib/routingPolicy.mjs +67 -0
- package/src/lib/routingProfile.mjs +334 -0
- package/src/lib/routingResolver.mjs +176 -0
- package/src/lib/routingSources/artificialAnalysis.mjs +129 -0
- package/src/lib/routingSources/benchlm.mjs +151 -0
- package/src/lib/routingSources/codeArena.mjs +162 -0
- package/src/lib/routingSources/deepswe.mjs +106 -0
- package/src/lib/routingSources/openhands.mjs +102 -0
- package/src/lib/routingSources/openhandsFrontend.mjs +135 -0
- package/src/lib/waveClaim.mjs +134 -0
package/src/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { diff } from './commands/diff.mjs';
|
|
|
8
8
|
import { uninstall } from './commands/uninstall.mjs';
|
|
9
9
|
import { setOwnership } from './commands/own.mjs';
|
|
10
10
|
import { CONSUMER_ORIGIN, KIT_ORIGIN } from './lib/manifest.mjs';
|
|
11
|
+
import { currentAgentSurface } from './lib/agentSurfaceRegistry.mjs';
|
|
11
12
|
import { createCommandAdapter } from '../scripts/release-state.mjs';
|
|
12
13
|
import { installedIdentityFromDir } from '../scripts/release-parity.mjs';
|
|
13
14
|
|
|
@@ -29,12 +30,18 @@ p.intro('agent-workflow-kit');
|
|
|
29
30
|
|
|
30
31
|
try {
|
|
31
32
|
if (cmd === 'init') {
|
|
32
|
-
const r = await init({
|
|
33
|
+
const r = await init({
|
|
34
|
+
kitRoot: KIT_ROOT,
|
|
35
|
+
consumerRoot,
|
|
36
|
+
force,
|
|
37
|
+
routingProfile: routingProfileOptions(),
|
|
38
|
+
});
|
|
33
39
|
p.note(
|
|
34
40
|
`copied ${r.copied.length} · seeded ${r.seeded.length} stub(s)` +
|
|
35
41
|
(r.skipped.length ? `\nskipped (pre-existing, use --force): ${r.skipped.join(', ')}` : ''),
|
|
36
42
|
'init'
|
|
37
43
|
);
|
|
44
|
+
printRoutingProfile(r.routingProfile);
|
|
38
45
|
p.outro('Next: run /setup-workflow to fill the project layer + board profile. ' +
|
|
39
46
|
'To enable the drift-guard hook, add .claude/hooks/drift-guard.py to your settings.json hooks.');
|
|
40
47
|
} else if (cmd === 'diff') {
|
|
@@ -45,9 +52,15 @@ try {
|
|
|
45
52
|
const decide = (action, path) => decideUpdate(action, path, yes);
|
|
46
53
|
const releaseIdentities = await readUpdateRelease();
|
|
47
54
|
const r = await update({
|
|
48
|
-
kitRoot: KIT_ROOT,
|
|
55
|
+
kitRoot: KIT_ROOT,
|
|
56
|
+
consumerRoot,
|
|
57
|
+
now: stamp(),
|
|
58
|
+
decide,
|
|
59
|
+
releaseIdentities,
|
|
60
|
+
routingProfile: routingProfileOptions(),
|
|
49
61
|
});
|
|
50
62
|
printPlan(r);
|
|
63
|
+
printRoutingProfile(r.routingProfile);
|
|
51
64
|
for (const c of r.conflicts) p.note(c.diff || '(binary/!text)', `conflict (not applied): ${c.path}`);
|
|
52
65
|
if (r.state === 'failed') throw new Error(renderUpdateFailure(r));
|
|
53
66
|
if (r.state === 'conflicted') {
|
|
@@ -126,6 +139,100 @@ async function decideUpdate(action, path, yes) {
|
|
|
126
139
|
throw new Error(`unknown update decision action: ${action}`);
|
|
127
140
|
}
|
|
128
141
|
|
|
142
|
+
function routingProfileOptions() {
|
|
143
|
+
return {
|
|
144
|
+
currentSurface: currentAgentSurface(),
|
|
145
|
+
prompt: yes ? undefined : promptRoutingProfile,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function printRoutingProfile(result) {
|
|
150
|
+
if (!result) return;
|
|
151
|
+
const suffix = result.reasons?.length ? ` · ${result.reasons.join(', ')}` : '';
|
|
152
|
+
p.note(`${result.status}${suffix}`, 'routing profile');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function promptRoutingProfile(question) {
|
|
156
|
+
if (question.kind === 'surfaces') {
|
|
157
|
+
return ensurePrompt(await p.multiselect({
|
|
158
|
+
message: question.message,
|
|
159
|
+
options: question.options.map(({ id, label }) => ({ value: id, label })),
|
|
160
|
+
initialValues: question.preselected,
|
|
161
|
+
required: true,
|
|
162
|
+
}), 'surface selection');
|
|
163
|
+
}
|
|
164
|
+
if (question.kind === 'autonomy') {
|
|
165
|
+
return ensurePrompt(await p.select({
|
|
166
|
+
message: question.message,
|
|
167
|
+
options: question.options,
|
|
168
|
+
}), 'switching choice');
|
|
169
|
+
}
|
|
170
|
+
if (question.kind === 'activation') {
|
|
171
|
+
const draft = question.advancedDraft ? ' · advanced draft ready' : '';
|
|
172
|
+
return ensurePrompt(await p.select({
|
|
173
|
+
message: `${question.message}${draft}`,
|
|
174
|
+
options: [
|
|
175
|
+
{ value: 'approve', label: 'Approve' },
|
|
176
|
+
{ value: 'safe-current-surface', label: 'Safe current surface' },
|
|
177
|
+
{ value: 'back', label: 'Back' },
|
|
178
|
+
{ value: 'advanced', label: 'Advanced' },
|
|
179
|
+
{ value: 'decline', label: 'Decline' },
|
|
180
|
+
],
|
|
181
|
+
}), 'activation choice');
|
|
182
|
+
}
|
|
183
|
+
if (question.kind === 'advanced') {
|
|
184
|
+
const optimization = ensurePrompt(await p.select({
|
|
185
|
+
message: question.message,
|
|
186
|
+
options: [
|
|
187
|
+
{ value: 'balanced', label: 'Balanced' },
|
|
188
|
+
{ value: 'quality', label: 'Quality' },
|
|
189
|
+
{ value: 'cost', label: 'Cost' },
|
|
190
|
+
],
|
|
191
|
+
initialValue: question.draft?.optimization ?? 'balanced',
|
|
192
|
+
}), 'advanced choice');
|
|
193
|
+
return { ...question.draft, optimization };
|
|
194
|
+
}
|
|
195
|
+
if (question.kind === 'reconcile') {
|
|
196
|
+
if (question.delta.type === 'missing-profile' || question.delta.type === 'invalid-profile') {
|
|
197
|
+
return ensurePrompt(await p.select({
|
|
198
|
+
message: question.message,
|
|
199
|
+
options: [
|
|
200
|
+
{ value: 'review', label: 'Review routing choices now' },
|
|
201
|
+
{ value: 'decline', label: 'Not now' },
|
|
202
|
+
],
|
|
203
|
+
}), 'routing migration choice');
|
|
204
|
+
}
|
|
205
|
+
const additions = question.delta.newSurfaces;
|
|
206
|
+
if (additions.length) {
|
|
207
|
+
const removed = question.delta.removedSurfaces.map(({ label }) => label);
|
|
208
|
+
const change = [
|
|
209
|
+
`new: ${additions.map(({ label }) => label).join(', ')}`,
|
|
210
|
+
...(removed.length ? [`unavailable: ${removed.join(', ')}`] : []),
|
|
211
|
+
].join(' · ');
|
|
212
|
+
const addSurfaceIds = ensurePrompt(await p.multiselect({
|
|
213
|
+
message: `Routing choices changed — ${change}`,
|
|
214
|
+
options: additions.map(({ id, label }) => ({ value: id, label })),
|
|
215
|
+
initialValues: [],
|
|
216
|
+
required: false,
|
|
217
|
+
}), 'routing reconcile choice');
|
|
218
|
+
return { action: 'apply', addSurfaceIds };
|
|
219
|
+
}
|
|
220
|
+
const removed = question.delta.removedSurfaces.map(({ label }) => label).join(', ');
|
|
221
|
+
const message = removed
|
|
222
|
+
? `Remove unavailable agent app from routing: ${removed}?`
|
|
223
|
+
: 'Refresh the routing profile registry revision?';
|
|
224
|
+
return ensurePrompt(await p.confirm({ message }), 'routing reconcile choice')
|
|
225
|
+
? { action: 'apply', addSurfaceIds: [] }
|
|
226
|
+
: { action: 'decline' };
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`unknown routing profile question: ${question.kind}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function ensurePrompt(value, label) {
|
|
232
|
+
if (p.isCancel(value)) throw new Error(`${label} cancelled`);
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
|
|
129
236
|
async function readUpdateRelease() {
|
|
130
237
|
const adapter = await createCommandAdapter({
|
|
131
238
|
repoRoot: KIT_ROOT,
|
package/src/commands/init.mjs
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
CONSUMER_ORIGIN,
|
|
13
13
|
PACKAGE_MANIFEST_NAME, CONSUMER_MANIFEST_NAME,
|
|
14
14
|
} from '../lib/manifest.mjs';
|
|
15
|
+
import {
|
|
16
|
+
inspectRoutingProfile, reconcileRoutingProfile, setupRoutingProfile,
|
|
17
|
+
} from '../lib/routingProfile.mjs';
|
|
15
18
|
|
|
16
19
|
const exists = (p) => access(p).then(() => true, () => false);
|
|
17
20
|
|
|
@@ -24,7 +27,7 @@ const exists = (p) => access(p).then(() => true, () => false);
|
|
|
24
27
|
* already exist — idempotent),
|
|
25
28
|
* - never touches board-sync.md / CLAUDE.md / AGENTS.md.
|
|
26
29
|
*/
|
|
27
|
-
export async function init({ kitRoot, consumerRoot, force = false }) {
|
|
30
|
+
export async function init({ kitRoot, consumerRoot, force = false, routingProfile }) {
|
|
28
31
|
const pkg = await readManifest(join(kitRoot, PACKAGE_MANIFEST_NAME));
|
|
29
32
|
if (!pkg) throw new Error('kit package manifest not found');
|
|
30
33
|
const prior = await readManifest(join(consumerRoot, CONSUMER_MANIFEST_NAME));
|
|
@@ -80,5 +83,18 @@ export async function init({ kitRoot, consumerRoot, force = false }) {
|
|
|
80
83
|
result.seeded.push(stub);
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
if (routingProfile) {
|
|
87
|
+
const options = { consumerRoot, ...routingProfile };
|
|
88
|
+
const inspection = await inspectRoutingProfile(options);
|
|
89
|
+
result.routingProfile = prior
|
|
90
|
+
? await reconcileRoutingProfile(options, inspection)
|
|
91
|
+
: (inspection.status === 'still valid'
|
|
92
|
+
? { status: 'still valid' }
|
|
93
|
+
: await setupRoutingProfile({
|
|
94
|
+
...options,
|
|
95
|
+
expectedFingerprint: inspection.fingerprint,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
return result;
|
|
84
100
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildEvidenceCatalogFromSources,
|
|
3
|
+
commitRoutingEvidenceCache,
|
|
4
|
+
routingEvidenceSourcesFromCache,
|
|
5
|
+
validateRoutingEvidenceSourceSnapshot,
|
|
6
|
+
} from '../lib/routingEvidenceCache.mjs';
|
|
7
|
+
|
|
8
|
+
function message(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function observationDiff(previous, next) {
|
|
13
|
+
const before = new Map(previous.map((entry) => [entry.id, entry]));
|
|
14
|
+
const after = new Map(next.map((entry) => [entry.id, entry]));
|
|
15
|
+
const added = [...after.keys()].filter((id) => !before.has(id)).sort();
|
|
16
|
+
const removed = [...before.keys()].filter((id) => !after.has(id)).sort();
|
|
17
|
+
const changed = [...after.keys()].filter(
|
|
18
|
+
(id) => before.has(id)
|
|
19
|
+
&& JSON.stringify(before.get(id)) !== JSON.stringify(after.get(id)),
|
|
20
|
+
).sort();
|
|
21
|
+
return { added, changed, removed };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateSource(source, index) {
|
|
25
|
+
if (!source || typeof source !== 'object') {
|
|
26
|
+
throw new TypeError(`sources[${index}] must be an object`);
|
|
27
|
+
}
|
|
28
|
+
const { adapter, load } = source;
|
|
29
|
+
if (!adapter || typeof adapter !== 'object') {
|
|
30
|
+
throw new TypeError(`sources[${index}].adapter must be an object`);
|
|
31
|
+
}
|
|
32
|
+
for (const field of ['sourceId', 'owner', 'artifactUrl']) {
|
|
33
|
+
if (typeof adapter[field] !== 'string' || adapter[field].trim() === '') {
|
|
34
|
+
throw new TypeError(`sources[${index}].adapter.${field} must be a non-empty string`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (typeof adapter.ingest !== 'function') {
|
|
38
|
+
throw new TypeError(`sources[${index}].adapter.ingest must be a function`);
|
|
39
|
+
}
|
|
40
|
+
if (typeof load !== 'function') {
|
|
41
|
+
throw new TypeError(`sources[${index}].load must be a function`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function modelsFor(observations, catalog) {
|
|
46
|
+
const identities = new Set(
|
|
47
|
+
observations.map(({ providerId, modelId }) => `${providerId}:${modelId}`),
|
|
48
|
+
);
|
|
49
|
+
return catalog.models.filter(
|
|
50
|
+
({ providerId, modelId }) => identities.has(`${providerId}:${modelId}`),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function legacySourceSnapshots(cache) {
|
|
55
|
+
const groups = new Map();
|
|
56
|
+
for (const observation of cache.catalog.observations) {
|
|
57
|
+
const { source, freshness } = observation;
|
|
58
|
+
if (!source?.id || !source?.snapshotHash) continue;
|
|
59
|
+
const existing = groups.get(source.id);
|
|
60
|
+
if (existing && existing.snapshotHash !== source.snapshotHash) {
|
|
61
|
+
throw new TypeError(
|
|
62
|
+
`legacy source ${source.id} contains several snapshot hashes`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const observations = existing?.observations ?? [];
|
|
66
|
+
observations.push(observation);
|
|
67
|
+
groups.set(source.id, {
|
|
68
|
+
sourceId: source.id,
|
|
69
|
+
owner: source.owner,
|
|
70
|
+
artifactUrl: source.url,
|
|
71
|
+
snapshotHash: source.snapshotHash,
|
|
72
|
+
observedAt: freshness.observedAt,
|
|
73
|
+
expiresAt: freshness.expiresAt,
|
|
74
|
+
models: [],
|
|
75
|
+
observations,
|
|
76
|
+
signals: [],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return [...groups.values()].map((source, index) =>
|
|
80
|
+
validateRoutingEvidenceSourceSnapshot({
|
|
81
|
+
...source,
|
|
82
|
+
models: modelsFor(source.observations, cache.catalog),
|
|
83
|
+
}, index));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function cachedSources(cache) {
|
|
87
|
+
const explicit = routingEvidenceSourcesFromCache(cache);
|
|
88
|
+
return explicit.length > 0 ? explicit : legacySourceSnapshots(cache);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function cachedStatus(adapter, prior, reason) {
|
|
92
|
+
return {
|
|
93
|
+
sourceId: adapter.sourceId,
|
|
94
|
+
status: prior ? 'cached' : 'quarantined',
|
|
95
|
+
reason,
|
|
96
|
+
cachedAt: prior?.observedAt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function refreshRoutingEvidence({
|
|
101
|
+
sources,
|
|
102
|
+
currentCache,
|
|
103
|
+
expectedRevision,
|
|
104
|
+
refreshedAt,
|
|
105
|
+
expiresAt,
|
|
106
|
+
onProgress = () => {},
|
|
107
|
+
}) {
|
|
108
|
+
if (!Array.isArray(sources)) throw new TypeError('sources must be an array');
|
|
109
|
+
sources.forEach(validateSource);
|
|
110
|
+
const requestedIds = new Set();
|
|
111
|
+
for (const { adapter } of sources) {
|
|
112
|
+
if (requestedIds.has(adapter.sourceId)) {
|
|
113
|
+
throw new TypeError(`duplicate routing evidence source: ${adapter.sourceId}`);
|
|
114
|
+
}
|
|
115
|
+
requestedIds.add(adapter.sourceId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sourceState = new Map(
|
|
119
|
+
cachedSources(currentCache).map((source) => [source.sourceId, source]),
|
|
120
|
+
);
|
|
121
|
+
const statuses = [];
|
|
122
|
+
const quarantines = [];
|
|
123
|
+
|
|
124
|
+
for (const { adapter, load } of sources) {
|
|
125
|
+
const prior = sourceState.get(adapter.sourceId);
|
|
126
|
+
onProgress({ sourceId: adapter.sourceId, status: 'loading' });
|
|
127
|
+
let loaded;
|
|
128
|
+
try {
|
|
129
|
+
loaded = await load();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const reason = `load failed: ${message(error)}`;
|
|
132
|
+
const status = cachedStatus(adapter, prior, reason);
|
|
133
|
+
if (!prior) quarantines.push({ sourceId: adapter.sourceId, reason });
|
|
134
|
+
statuses.push(status);
|
|
135
|
+
onProgress(status);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const ingested = adapter.ingest({
|
|
141
|
+
payload: loaded.payload,
|
|
142
|
+
snapshotHash: loaded.snapshotHash,
|
|
143
|
+
observedAt: refreshedAt,
|
|
144
|
+
expiresAt,
|
|
145
|
+
});
|
|
146
|
+
if (ingested.sourceId !== adapter.sourceId) {
|
|
147
|
+
throw new TypeError(
|
|
148
|
+
`source identity changed from ${adapter.sourceId} to ${ingested.sourceId}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const candidate = validateRoutingEvidenceSourceSnapshot({
|
|
152
|
+
sourceId: adapter.sourceId,
|
|
153
|
+
owner: adapter.owner,
|
|
154
|
+
artifactUrl: adapter.artifactUrl,
|
|
155
|
+
snapshotHash: loaded.snapshotHash,
|
|
156
|
+
observedAt: refreshedAt,
|
|
157
|
+
expiresAt,
|
|
158
|
+
models: ingested.models,
|
|
159
|
+
observations: ingested.observations,
|
|
160
|
+
signals: ingested.signals ?? [],
|
|
161
|
+
});
|
|
162
|
+
const prospective = new Map(sourceState);
|
|
163
|
+
prospective.set(adapter.sourceId, candidate);
|
|
164
|
+
buildEvidenceCatalogFromSources(
|
|
165
|
+
[...prospective.values()],
|
|
166
|
+
`catalog-r${expectedRevision + 1}`,
|
|
167
|
+
);
|
|
168
|
+
sourceState.set(adapter.sourceId, candidate);
|
|
169
|
+
const status = { sourceId: adapter.sourceId, status: 'live' };
|
|
170
|
+
statuses.push(status);
|
|
171
|
+
onProgress(status);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
const reason = message(error);
|
|
174
|
+
const status = cachedStatus(adapter, prior, reason);
|
|
175
|
+
quarantines.push({ sourceId: adapter.sourceId, reason });
|
|
176
|
+
onProgress({ sourceId: adapter.sourceId, status: 'quarantined', reason });
|
|
177
|
+
statuses.push(status);
|
|
178
|
+
if (prior) onProgress(status);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const nextSources = [...sourceState.values()];
|
|
183
|
+
const nextCatalog = buildEvidenceCatalogFromSources(
|
|
184
|
+
nextSources,
|
|
185
|
+
`catalog-r${expectedRevision + 1}`,
|
|
186
|
+
);
|
|
187
|
+
const cache = commitRoutingEvidenceCache({
|
|
188
|
+
current: currentCache,
|
|
189
|
+
expectedRevision,
|
|
190
|
+
nextCatalog,
|
|
191
|
+
nextSources,
|
|
192
|
+
refreshedAt,
|
|
193
|
+
expiresAt,
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
cache,
|
|
197
|
+
sources: statuses,
|
|
198
|
+
quarantines,
|
|
199
|
+
diff: observationDiff(
|
|
200
|
+
currentCache.catalog.observations,
|
|
201
|
+
nextCatalog.observations,
|
|
202
|
+
),
|
|
203
|
+
};
|
|
204
|
+
}
|
package/src/commands/update.mjs
CHANGED
|
@@ -8,6 +8,9 @@ import { reconcile } from '../lib/updateReconcile.mjs';
|
|
|
8
8
|
import {
|
|
9
9
|
CONSUMER_MANIFEST_NAME, PACKAGE_MANIFEST_NAME, readManifest,
|
|
10
10
|
} from '../lib/manifest.mjs';
|
|
11
|
+
import {
|
|
12
|
+
inspectRoutingProfile, reconcileRoutingProfile,
|
|
13
|
+
} from '../lib/routingProfile.mjs';
|
|
11
14
|
|
|
12
15
|
const RELEASE_NAME = '@ikon85/agent-workflow-kit';
|
|
13
16
|
|
|
@@ -22,6 +25,25 @@ export function renderUpdateFailure(result) {
|
|
|
22
25
|
* checking -> preview/awaiting_decision -> staging -> verifying -> terminal state.
|
|
23
26
|
*/
|
|
24
27
|
export async function update(options) {
|
|
28
|
+
const preflight = options.routingProfile
|
|
29
|
+
? await inspectRoutingProfile({ consumerRoot: options.consumerRoot, ...options.routingProfile })
|
|
30
|
+
: null;
|
|
31
|
+
const result = await updatePackage(options);
|
|
32
|
+
if (!preflight || options.dryRun || result.state !== 'applied') return result;
|
|
33
|
+
const inspection = await inspectRoutingProfile({
|
|
34
|
+
consumerRoot: options.consumerRoot,
|
|
35
|
+
...options.routingProfile,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
...result,
|
|
39
|
+
routingProfile: await reconcileRoutingProfile(
|
|
40
|
+
{ consumerRoot: options.consumerRoot, ...options.routingProfile },
|
|
41
|
+
inspection,
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function updatePackage(options) {
|
|
25
47
|
const {
|
|
26
48
|
kitRoot, consumerRoot, decide = () => false, dryRun = false,
|
|
27
49
|
releaseIdentities, verify = verifyCandidate, activate = activateCandidate,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import { delimiter, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const AGENT_SURFACE_REGISTRY_REVISION = 1;
|
|
5
|
+
|
|
6
|
+
export const AGENT_SURFACE_REGISTRY = Object.freeze([
|
|
7
|
+
Object.freeze({
|
|
8
|
+
id: 'claude-code',
|
|
9
|
+
label: 'Claude Code',
|
|
10
|
+
command: 'claude',
|
|
11
|
+
activeEnvironment: Object.freeze(['CLAUDE_CODE_ENTRYPOINT']),
|
|
12
|
+
adapter: Object.freeze({
|
|
13
|
+
providers: Object.freeze(['anthropic']),
|
|
14
|
+
transports: Object.freeze(['native', 'codex-cli']),
|
|
15
|
+
enforcement: Object.freeze({ model: 'named-agent', effort: 'named-agent' }),
|
|
16
|
+
}),
|
|
17
|
+
}),
|
|
18
|
+
Object.freeze({
|
|
19
|
+
id: 'codex',
|
|
20
|
+
label: 'Codex',
|
|
21
|
+
command: 'codex',
|
|
22
|
+
activeEnvironment: Object.freeze(['CODEX_THREAD_ID']),
|
|
23
|
+
adapter: Object.freeze({
|
|
24
|
+
providers: Object.freeze(['openai']),
|
|
25
|
+
transports: Object.freeze(['native', 'claude-cli']),
|
|
26
|
+
enforcement: Object.freeze({ model: 'per-spawn', effort: 'per-spawn' }),
|
|
27
|
+
}),
|
|
28
|
+
}),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
async function commandOnPath(command, env = process.env) {
|
|
32
|
+
for (const directory of (env.PATH ?? '').split(delimiter).filter(Boolean)) {
|
|
33
|
+
if (await access(join(directory, command)).then(() => true, () => false)) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function detectAgentSurfaces({
|
|
39
|
+
registry = AGENT_SURFACE_REGISTRY,
|
|
40
|
+
commandAvailable = commandOnPath,
|
|
41
|
+
} = {}) {
|
|
42
|
+
return Promise.all(registry.map(async (surface) => Object.freeze({
|
|
43
|
+
id: surface.id,
|
|
44
|
+
label: surface.label,
|
|
45
|
+
detected: await commandAvailable(surface.command),
|
|
46
|
+
adapter: surface.adapter,
|
|
47
|
+
})));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function surfaceById(id, registry = AGENT_SURFACE_REGISTRY) {
|
|
51
|
+
return registry.find((surface) => surface.id === id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function currentAgentSurface({
|
|
55
|
+
registry = AGENT_SURFACE_REGISTRY,
|
|
56
|
+
env = process.env,
|
|
57
|
+
} = {}) {
|
|
58
|
+
return registry.find((surface) =>
|
|
59
|
+
surface.activeEnvironment.some((name) => typeof env[name] === 'string' && env[name] !== ''))?.id;
|
|
60
|
+
}
|
package/src/lib/bundle.mjs
CHANGED
|
@@ -72,6 +72,40 @@ export const HELPER_FILES = [
|
|
|
72
72
|
{ path: 'src/lib/release-preview.mjs', kind: 'script', mode: 0o644 },
|
|
73
73
|
{ path: 'src/lib/release-apply.mjs', kind: 'script', mode: 0o644 },
|
|
74
74
|
{ path: 'scripts/project-release.mjs', kind: 'script', mode: 0o755 },
|
|
75
|
+
// Canonical schema and semantic validator shared across orchestration paths.
|
|
76
|
+
// Library → 0o644; later helper-owning slices append their paths once present.
|
|
77
|
+
{ path: 'src/lib/reportValidator.mjs', kind: 'script', mode: 0o644 },
|
|
78
|
+
// Fail-closed orchestration selector and its host-inventory adapters.
|
|
79
|
+
{ path: 'src/lib/capabilityMatrix.mjs', kind: 'script', mode: 0o644 },
|
|
80
|
+
// Provider-neutral routing runtime: one Evidence catalog, Access graph,
|
|
81
|
+
// Routing policy, resolver, spawn guard, receipt v2, and surface attestations.
|
|
82
|
+
// These modules form one consumer unit; omitting one leaves shipped dispatch
|
|
83
|
+
// prose pointing at a resolver or adapter that cannot execute.
|
|
84
|
+
{ path: 'src/commands/routing-policy-update.mjs', kind: 'script', mode: 0o644 },
|
|
85
|
+
{ path: 'src/lib/agentSurfaceRegistry.mjs', kind: 'script', mode: 0o644 },
|
|
86
|
+
{ path: 'src/lib/dispatchReceipt.mjs', kind: 'script', mode: 0o644 },
|
|
87
|
+
{ path: 'src/lib/frontendWorkloads.mjs', kind: 'script', mode: 0o644 },
|
|
88
|
+
{ path: 'src/lib/routeDispatcher.mjs', kind: 'script', mode: 0o644 },
|
|
89
|
+
{ path: 'src/lib/routingAccessGraph.mjs', kind: 'script', mode: 0o644 },
|
|
90
|
+
{ path: 'src/lib/routingAdapters/claude.mjs', kind: 'script', mode: 0o644 },
|
|
91
|
+
{ path: 'src/lib/routingAdapters/codex.mjs', kind: 'script', mode: 0o644 },
|
|
92
|
+
{ path: 'src/lib/routingCatalog.mjs', kind: 'script', mode: 0o644 },
|
|
93
|
+
{ path: 'src/lib/routingEvidenceCache.mjs', kind: 'script', mode: 0o644 },
|
|
94
|
+
{ path: 'src/lib/routingIntent.mjs', kind: 'script', mode: 0o644 },
|
|
95
|
+
{ path: 'src/lib/routingPolicy.mjs', kind: 'script', mode: 0o644 },
|
|
96
|
+
{ path: 'src/lib/routingProfile.mjs', kind: 'script', mode: 0o644 },
|
|
97
|
+
{ path: 'src/lib/routingResolver.mjs', kind: 'script', mode: 0o644 },
|
|
98
|
+
{ path: 'src/lib/routingSources/artificialAnalysis.mjs', kind: 'script', mode: 0o644 },
|
|
99
|
+
{ path: 'src/lib/routingSources/benchlm.mjs', kind: 'script', mode: 0o644 },
|
|
100
|
+
{ path: 'src/lib/routingSources/codeArena.mjs', kind: 'script', mode: 0o644 },
|
|
101
|
+
{ path: 'src/lib/routingSources/deepswe.mjs', kind: 'script', mode: 0o644 },
|
|
102
|
+
{ path: 'src/lib/routingSources/openhands.mjs', kind: 'script', mode: 0o644 },
|
|
103
|
+
{ path: 'src/lib/routingSources/openhandsFrontend.mjs', kind: 'script', mode: 0o644 },
|
|
104
|
+
// Main-thread recon boundary shared by every orchestration path.
|
|
105
|
+
{ path: 'src/lib/reconcileReconReports.mjs', kind: 'script', mode: 0o644 },
|
|
106
|
+
// Atomic compare-and-set wave claim: two sessions cannot orchestrate the same
|
|
107
|
+
// wave. Library (imported by the Phase-0 claim protocol) → 0o644.
|
|
108
|
+
{ path: 'src/lib/waveClaim.mjs', kind: 'script', mode: 0o644 },
|
|
75
109
|
// GitHub-consumer automation: invokes the existing update command, then owns
|
|
76
110
|
// only the stable tested branch/pull-request upsert.
|
|
77
111
|
{ path: 'scripts/kit-update-pr.mjs', kind: 'script', mode: 0o755 },
|