@dtlabs/ai-toolkit 0.12.0 → 0.13.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/CHANGELOG.md +28 -0
- package/bin/cli.js +480 -7
- package/lib/agent-registry.js +1018 -0
- package/lib/execution-ledger.js +97 -3
- package/package.json +1 -1
- package/src/claude/agents/{concurrency-safety-assessment.md → gaia-concurrency-safety-assessment.md} +1 -1
- package/src/claude/agents/{define-feature.md → gaia-define-feature.md} +1 -1
- package/src/claude/agents/{dependency-injection-refactoring.md → gaia-dependency-injection-refactoring.md} +1 -1
- package/src/claude/agents/{dependency-supply-chain-security.md → gaia-dependency-supply-chain-security.md} +1 -1
- package/src/claude/agents/{developer-backend.md → gaia-developer-backend.md} +1 -1
- package/src/claude/agents/{developer-frontend.md → gaia-developer-frontend.md} +1 -1
- package/src/claude/agents/{developer-testing.md → gaia-developer-testing.md} +1 -1
- package/src/claude/agents/{domain-model-refactoring.md → gaia-domain-model-refactoring.md} +1 -1
- package/src/claude/agents/{generate-requirements.md → gaia-generate-requirements.md} +1 -1
- package/src/claude/agents/{generate-tech-spec.md → gaia-generate-tech-spec.md} +1 -1
- package/src/claude/agents/{generate-work-breakdown.md → gaia-generate-work-breakdown.md} +5 -5
- package/src/claude/agents/{generic-software-assessment.md → gaia-generic-software-assessment.md} +1 -1
- package/src/claude/agents/{god-class-decomposition.md → gaia-god-class-decomposition.md} +1 -1
- package/src/claude/agents/{init-agents-md.md → gaia-init-agents-md.md} +1 -1
- package/src/claude/agents/{install-toolkit.md → gaia-install-toolkit.md} +1 -1
- package/src/claude/agents/{intervention-documentation-standard.md → gaia-intervention-documentation-standard.md} +1 -1
- package/src/claude/agents/{layered-architecture-assessment.md → gaia-layered-architecture-assessment.md} +1 -1
- package/src/claude/agents/{review-solution.md → gaia-review-solution.md} +1 -1
- package/src/claude/agents/{security-hardening.md → gaia-security-hardening.md} +1 -1
- package/src/claude/agents/{validate-feature-docs.md → gaia-validate-feature-docs.md} +2 -2
- package/src/claude/agents/{validate-work-breakdown-semantic.md → gaia-validate-work-breakdown-semantic.md} +1 -1
- package/src/claude/commands/next-task.md +4 -4
- package/src/claude/skills/assess-codebase/SKILL.md +1 -1
- package/src/claude/skills/define-feature/SKILL.md +2 -2
- package/src/claude/skills/hi-gaia/SKILL.md +1 -1
- package/src/claude/skills/implement-feature/SKILL.md +186 -8
- package/src/claude/skills/init-agents/SKILL.md +2 -2
- package/src/claude/skills/install-toolkit/SKILL.md +2 -2
- package/src/claude/workflows/pm-phase1.js +76 -5
- package/src/claude/workflows/pm-phase2.js +63 -2
- package/src/claude/workflows/pm-phase3.js +66 -9
|
@@ -0,0 +1,1018 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
// ── Resolution status vocabulary ──────────────────────────────────────────────
|
|
9
|
+
//
|
|
10
|
+
// resolveAgent() returns one of:
|
|
11
|
+
// "verified" — agent present, manifest has fileHashes, hash matches on disk
|
|
12
|
+
// "hash-unverifiable" — agent present, manifest lacks fileHashes entry (v0.12.0 manifest)
|
|
13
|
+
// "hash-mismatch" — agent present, computed hash differs from manifest record
|
|
14
|
+
// "not-found" — canonical ID not in catalog
|
|
15
|
+
// "not-installed" — ID in catalog but file absent from manifest or absent on disk
|
|
16
|
+
// "manifest-missing" — no manifest found or manifest unreadable / corrupt
|
|
17
|
+
// "conflict" — ambiguous installation (both local + global manifests present)
|
|
18
|
+
// or scope collision (same native name in both observable scopes)
|
|
19
|
+
//
|
|
20
|
+
// listRegisteredAgents() uses the doctor-agents vocabulary (per AC-13):
|
|
21
|
+
// "verified" — as above
|
|
22
|
+
// "conflict" — scope collision or hash-mismatch; detail in integrityDetail
|
|
23
|
+
// "hash-unverifiable" — as above
|
|
24
|
+
// "unobservable" — plugin / session scope; filesystem enumeration impossible
|
|
25
|
+
// "not-installed" — as above
|
|
26
|
+
// "not-applicable" — platform not supported
|
|
27
|
+
|
|
28
|
+
// ── Work Breakdown agent_type inventory (AC-23, AC-24, AC-26, AC-37) ──────────
|
|
29
|
+
//
|
|
30
|
+
// Maps every valid Work Breakdown agentType value to the canonical catalog ID.
|
|
31
|
+
// Two tiers:
|
|
32
|
+
// WB_AGENT_TYPE_MAP — current supported values (non-deprecated)
|
|
33
|
+
// LEGACY_AGENT_TYPE_MAP — deprecated aliases still accepted for backward-compat
|
|
34
|
+
//
|
|
35
|
+
// resolveWorkBreakdownAgentType() consults both maps. Unknown values throw a
|
|
36
|
+
// structured UNKNOWN_AGENT_TYPE error so the pipeline hard-stops instead of
|
|
37
|
+
// passing an unresolved string to the platform (AC-25).
|
|
38
|
+
|
|
39
|
+
const WB_AGENT_TYPE_MAP = {
|
|
40
|
+
'developer-backend': 'gaia.agent.developer.backend',
|
|
41
|
+
'developer-frontend': 'gaia.agent.developer.frontend',
|
|
42
|
+
'developer-testing': 'gaia.agent.developer.testing',
|
|
43
|
+
'review-solution': 'gaia.agent.review.solution',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Legacy agent_type values still accepted for existing Work Breakdowns (AC-24, AC-37).
|
|
47
|
+
// developer-database has never had a separate agent file; it always resolved to
|
|
48
|
+
// developer-backend. Generating new WBs with developer-database is disallowed
|
|
49
|
+
// (generate-work-breakdown.md no longer lists it as a valid value).
|
|
50
|
+
const LEGACY_AGENT_TYPE_MAP = {
|
|
51
|
+
'developer-database': {
|
|
52
|
+
canonicalId: 'gaia.agent.developer.backend',
|
|
53
|
+
deprecated: true,
|
|
54
|
+
deprecationMessage: 'agent_type "developer-database" is deprecated. ' +
|
|
55
|
+
'Use "developer-backend" in new Work Breakdowns. ' +
|
|
56
|
+
'Existing WBs continue to resolve without changes required.',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a Work Breakdown agent_type value to a canonical catalog ID.
|
|
62
|
+
*
|
|
63
|
+
* Accepts current supported values (WB_AGENT_TYPE_MAP) and deprecated legacy
|
|
64
|
+
* values (LEGACY_AGENT_TYPE_MAP). Unknown values throw a structured error so
|
|
65
|
+
* the pipeline does not pass unresolved strings to the platform (AC-25).
|
|
66
|
+
*
|
|
67
|
+
* @param {string} agentType - the agent_type value from a Work Breakdown task
|
|
68
|
+
* @returns {{ canonicalId: string, deprecated: boolean, deprecationMessage?: string }}
|
|
69
|
+
* @throws {{ code: 'UNKNOWN_AGENT_TYPE', agentType: string, message: string }}
|
|
70
|
+
*/
|
|
71
|
+
function resolveWorkBreakdownAgentType(agentType) {
|
|
72
|
+
if (Object.prototype.hasOwnProperty.call(WB_AGENT_TYPE_MAP, agentType)) {
|
|
73
|
+
return { canonicalId: WB_AGENT_TYPE_MAP[agentType], deprecated: false };
|
|
74
|
+
}
|
|
75
|
+
if (Object.prototype.hasOwnProperty.call(LEGACY_AGENT_TYPE_MAP, agentType)) {
|
|
76
|
+
var legacy = LEGACY_AGENT_TYPE_MAP[agentType];
|
|
77
|
+
return {
|
|
78
|
+
canonicalId: legacy.canonicalId,
|
|
79
|
+
deprecated: true,
|
|
80
|
+
deprecationMessage: legacy.deprecationMessage,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
var allowed = Object.keys(WB_AGENT_TYPE_MAP).concat(Object.keys(LEGACY_AGENT_TYPE_MAP)).join(', ');
|
|
84
|
+
var err = new Error(
|
|
85
|
+
'resolveWorkBreakdownAgentType: unknown agent_type "' + agentType + '". ' +
|
|
86
|
+
'Allowed values: ' + allowed + '. ' +
|
|
87
|
+
'The pipeline cannot start until all agent_type values are known to the registry.'
|
|
88
|
+
);
|
|
89
|
+
err.code = 'UNKNOWN_AGENT_TYPE';
|
|
90
|
+
err.agentType = agentType;
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Canonical catalog (Phase A — transitional legacy native names) ─────────────
|
|
95
|
+
//
|
|
96
|
+
// Each entry maps one toolkit agent / orchestrator to a globally unique, namespaced
|
|
97
|
+
// canonical ID. The "claude" key in nativeNames is the Phase A legacy name;
|
|
98
|
+
// Phase B will rename each source file atomically and update these entries.
|
|
99
|
+
//
|
|
100
|
+
// Fields follow §4.1 "Agent Catalog (Registry Data)" from the FTR-017 Tech-Spec.
|
|
101
|
+
|
|
102
|
+
const CATALOG = [
|
|
103
|
+
// ── Developer agents ──────────────────────────────────────────────────────
|
|
104
|
+
{
|
|
105
|
+
agentId: 'gaia.agent.developer.backend',
|
|
106
|
+
nativeNames: { claude: 'gaia-developer-backend' },
|
|
107
|
+
relativeInstallPath: '.claude/agents/gaia-developer-backend.md',
|
|
108
|
+
role: 'developer',
|
|
109
|
+
type: 'backend',
|
|
110
|
+
authorisedPhases: ['implementation'],
|
|
111
|
+
allowedPipelines: ['implement-feature'],
|
|
112
|
+
minimumToolkitVersion: '0.13.0',
|
|
113
|
+
deprecated: false,
|
|
114
|
+
deprecationTarget: null,
|
|
115
|
+
platforms: ['claude'],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
agentId: 'gaia.agent.developer.frontend',
|
|
119
|
+
nativeNames: { claude: 'gaia-developer-frontend' },
|
|
120
|
+
relativeInstallPath: '.claude/agents/gaia-developer-frontend.md',
|
|
121
|
+
role: 'developer',
|
|
122
|
+
type: 'frontend',
|
|
123
|
+
authorisedPhases: ['implementation'],
|
|
124
|
+
allowedPipelines: ['implement-feature'],
|
|
125
|
+
minimumToolkitVersion: '0.13.0',
|
|
126
|
+
deprecated: false,
|
|
127
|
+
deprecationTarget: null,
|
|
128
|
+
platforms: ['claude'],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
agentId: 'gaia.agent.developer.testing',
|
|
132
|
+
nativeNames: { claude: 'gaia-developer-testing' },
|
|
133
|
+
relativeInstallPath: '.claude/agents/gaia-developer-testing.md',
|
|
134
|
+
role: 'developer',
|
|
135
|
+
type: 'testing',
|
|
136
|
+
authorisedPhases: ['implementation'],
|
|
137
|
+
allowedPipelines: ['implement-feature'],
|
|
138
|
+
minimumToolkitVersion: '0.13.0',
|
|
139
|
+
deprecated: false,
|
|
140
|
+
deprecationTarget: null,
|
|
141
|
+
platforms: ['claude'],
|
|
142
|
+
},
|
|
143
|
+
// ── Reviewer agents ───────────────────────────────────────────────────────
|
|
144
|
+
{
|
|
145
|
+
agentId: 'gaia.agent.review.solution',
|
|
146
|
+
nativeNames: { claude: 'gaia-review-solution' },
|
|
147
|
+
relativeInstallPath: '.claude/agents/gaia-review-solution.md',
|
|
148
|
+
role: 'reviewer',
|
|
149
|
+
type: 'solution',
|
|
150
|
+
authorisedPhases: ['review'],
|
|
151
|
+
allowedPipelines: ['implement-feature'],
|
|
152
|
+
minimumToolkitVersion: '0.13.0',
|
|
153
|
+
deprecated: false,
|
|
154
|
+
deprecationTarget: null,
|
|
155
|
+
platforms: ['claude'],
|
|
156
|
+
},
|
|
157
|
+
// ── Planner agents ────────────────────────────────────────────────────────
|
|
158
|
+
{
|
|
159
|
+
agentId: 'gaia.agent.planner.requirements',
|
|
160
|
+
nativeNames: { claude: 'gaia-generate-requirements' },
|
|
161
|
+
relativeInstallPath: '.claude/agents/gaia-generate-requirements.md',
|
|
162
|
+
role: 'planner',
|
|
163
|
+
type: 'requirements',
|
|
164
|
+
authorisedPhases: ['planning'],
|
|
165
|
+
allowedPipelines: ['implement-feature'],
|
|
166
|
+
minimumToolkitVersion: '0.13.0',
|
|
167
|
+
deprecated: false,
|
|
168
|
+
deprecationTarget: null,
|
|
169
|
+
platforms: ['claude'],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
agentId: 'gaia.agent.planner.tech-spec',
|
|
173
|
+
nativeNames: { claude: 'gaia-generate-tech-spec' },
|
|
174
|
+
relativeInstallPath: '.claude/agents/gaia-generate-tech-spec.md',
|
|
175
|
+
role: 'planner',
|
|
176
|
+
type: 'tech-spec',
|
|
177
|
+
authorisedPhases: ['planning'],
|
|
178
|
+
allowedPipelines: ['implement-feature'],
|
|
179
|
+
minimumToolkitVersion: '0.13.0',
|
|
180
|
+
deprecated: false,
|
|
181
|
+
deprecationTarget: null,
|
|
182
|
+
platforms: ['claude'],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
agentId: 'gaia.agent.planner.work-breakdown',
|
|
186
|
+
nativeNames: { claude: 'gaia-generate-work-breakdown' },
|
|
187
|
+
relativeInstallPath: '.claude/agents/gaia-generate-work-breakdown.md',
|
|
188
|
+
role: 'planner',
|
|
189
|
+
type: 'work-breakdown',
|
|
190
|
+
authorisedPhases: ['planning'],
|
|
191
|
+
allowedPipelines: ['implement-feature'],
|
|
192
|
+
minimumToolkitVersion: '0.13.0',
|
|
193
|
+
deprecated: false,
|
|
194
|
+
deprecationTarget: null,
|
|
195
|
+
platforms: ['claude'],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
agentId: 'gaia.agent.planner.validate-feature-docs',
|
|
199
|
+
nativeNames: { claude: 'gaia-validate-feature-docs' },
|
|
200
|
+
relativeInstallPath: '.claude/agents/gaia-validate-feature-docs.md',
|
|
201
|
+
role: 'planner',
|
|
202
|
+
type: 'validation',
|
|
203
|
+
authorisedPhases: ['planning'],
|
|
204
|
+
allowedPipelines: ['implement-feature'],
|
|
205
|
+
minimumToolkitVersion: '0.13.0',
|
|
206
|
+
deprecated: false,
|
|
207
|
+
deprecationTarget: null,
|
|
208
|
+
platforms: ['claude'],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
agentId: 'gaia.agent.planner.validate-work-breakdown',
|
|
212
|
+
nativeNames: { claude: 'gaia-validate-work-breakdown-semantic' },
|
|
213
|
+
relativeInstallPath: '.claude/agents/gaia-validate-work-breakdown-semantic.md',
|
|
214
|
+
role: 'planner',
|
|
215
|
+
type: 'validation',
|
|
216
|
+
authorisedPhases: ['planning'],
|
|
217
|
+
allowedPipelines: ['implement-feature'],
|
|
218
|
+
minimumToolkitVersion: '0.13.0',
|
|
219
|
+
deprecated: false,
|
|
220
|
+
deprecationTarget: null,
|
|
221
|
+
platforms: ['claude'],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
agentId: 'gaia.agent.planner.define-feature',
|
|
225
|
+
nativeNames: { claude: 'gaia-define-feature' },
|
|
226
|
+
relativeInstallPath: '.claude/agents/gaia-define-feature.md',
|
|
227
|
+
role: 'planner',
|
|
228
|
+
type: 'definition',
|
|
229
|
+
authorisedPhases: ['planning'],
|
|
230
|
+
allowedPipelines: ['implement-feature'],
|
|
231
|
+
minimumToolkitVersion: '0.13.0',
|
|
232
|
+
deprecated: false,
|
|
233
|
+
deprecationTarget: null,
|
|
234
|
+
platforms: ['claude'],
|
|
235
|
+
},
|
|
236
|
+
// ── Tooling agents ────────────────────────────────────────────────────────
|
|
237
|
+
{
|
|
238
|
+
agentId: 'gaia.agent.tooling.init-agents-md',
|
|
239
|
+
nativeNames: { claude: 'gaia-init-agents-md' },
|
|
240
|
+
relativeInstallPath: '.claude/agents/gaia-init-agents-md.md',
|
|
241
|
+
role: 'tooling',
|
|
242
|
+
type: 'init',
|
|
243
|
+
authorisedPhases: ['setup'],
|
|
244
|
+
allowedPipelines: ['init'],
|
|
245
|
+
minimumToolkitVersion: '0.13.0',
|
|
246
|
+
deprecated: false,
|
|
247
|
+
deprecationTarget: null,
|
|
248
|
+
platforms: ['claude'],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
agentId: 'gaia.agent.tooling.install-toolkit',
|
|
252
|
+
nativeNames: { claude: 'gaia-install-toolkit' },
|
|
253
|
+
relativeInstallPath: '.claude/agents/gaia-install-toolkit.md',
|
|
254
|
+
role: 'tooling',
|
|
255
|
+
type: 'install',
|
|
256
|
+
authorisedPhases: ['setup'],
|
|
257
|
+
allowedPipelines: ['install'],
|
|
258
|
+
minimumToolkitVersion: '0.13.0',
|
|
259
|
+
deprecated: false,
|
|
260
|
+
deprecationTarget: null,
|
|
261
|
+
platforms: ['claude'],
|
|
262
|
+
},
|
|
263
|
+
// ── Assessment agents ─────────────────────────────────────────────────────
|
|
264
|
+
{
|
|
265
|
+
agentId: 'gaia.agent.assessment.generic',
|
|
266
|
+
nativeNames: { claude: 'gaia-generic-software-assessment' },
|
|
267
|
+
relativeInstallPath: '.claude/agents/gaia-generic-software-assessment.md',
|
|
268
|
+
role: 'assessor',
|
|
269
|
+
type: 'generic',
|
|
270
|
+
authorisedPhases: ['assessment'],
|
|
271
|
+
allowedPipelines: ['assess-codebase'],
|
|
272
|
+
minimumToolkitVersion: '0.13.0',
|
|
273
|
+
deprecated: false,
|
|
274
|
+
deprecationTarget: null,
|
|
275
|
+
platforms: ['claude'],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
agentId: 'gaia.agent.assessment.layered-architecture',
|
|
279
|
+
nativeNames: { claude: 'gaia-layered-architecture-assessment' },
|
|
280
|
+
relativeInstallPath: '.claude/agents/gaia-layered-architecture-assessment.md',
|
|
281
|
+
role: 'assessor',
|
|
282
|
+
type: 'architecture',
|
|
283
|
+
authorisedPhases: ['assessment'],
|
|
284
|
+
allowedPipelines: ['assess-codebase'],
|
|
285
|
+
minimumToolkitVersion: '0.13.0',
|
|
286
|
+
deprecated: false,
|
|
287
|
+
deprecationTarget: null,
|
|
288
|
+
platforms: ['claude'],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
agentId: 'gaia.agent.assessment.concurrency',
|
|
292
|
+
nativeNames: { claude: 'gaia-concurrency-safety-assessment' },
|
|
293
|
+
relativeInstallPath: '.claude/agents/gaia-concurrency-safety-assessment.md',
|
|
294
|
+
role: 'assessor',
|
|
295
|
+
type: 'concurrency',
|
|
296
|
+
authorisedPhases: ['assessment'],
|
|
297
|
+
allowedPipelines: ['assess-codebase'],
|
|
298
|
+
minimumToolkitVersion: '0.13.0',
|
|
299
|
+
deprecated: false,
|
|
300
|
+
deprecationTarget: null,
|
|
301
|
+
platforms: ['claude'],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
agentId: 'gaia.agent.assessment.intervention-documentation',
|
|
305
|
+
nativeNames: { claude: 'gaia-intervention-documentation-standard' },
|
|
306
|
+
relativeInstallPath: '.claude/agents/gaia-intervention-documentation-standard.md',
|
|
307
|
+
role: 'assessor',
|
|
308
|
+
type: 'documentation',
|
|
309
|
+
authorisedPhases: ['assessment'],
|
|
310
|
+
allowedPipelines: ['assess-codebase'],
|
|
311
|
+
minimumToolkitVersion: '0.13.0',
|
|
312
|
+
deprecated: false,
|
|
313
|
+
deprecationTarget: null,
|
|
314
|
+
platforms: ['claude'],
|
|
315
|
+
},
|
|
316
|
+
// ── Refactoring agents ────────────────────────────────────────────────────
|
|
317
|
+
{
|
|
318
|
+
agentId: 'gaia.agent.refactoring.dependency-injection',
|
|
319
|
+
nativeNames: { claude: 'gaia-dependency-injection-refactoring' },
|
|
320
|
+
relativeInstallPath: '.claude/agents/gaia-dependency-injection-refactoring.md',
|
|
321
|
+
role: 'refactoring',
|
|
322
|
+
type: 'dependency-injection',
|
|
323
|
+
authorisedPhases: ['implementation'],
|
|
324
|
+
allowedPipelines: ['implement-feature'],
|
|
325
|
+
minimumToolkitVersion: '0.13.0',
|
|
326
|
+
deprecated: false,
|
|
327
|
+
deprecationTarget: null,
|
|
328
|
+
platforms: ['claude'],
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
agentId: 'gaia.agent.refactoring.domain-model',
|
|
332
|
+
nativeNames: { claude: 'gaia-domain-model-refactoring' },
|
|
333
|
+
relativeInstallPath: '.claude/agents/gaia-domain-model-refactoring.md',
|
|
334
|
+
role: 'refactoring',
|
|
335
|
+
type: 'domain-model',
|
|
336
|
+
authorisedPhases: ['implementation'],
|
|
337
|
+
allowedPipelines: ['implement-feature'],
|
|
338
|
+
minimumToolkitVersion: '0.13.0',
|
|
339
|
+
deprecated: false,
|
|
340
|
+
deprecationTarget: null,
|
|
341
|
+
platforms: ['claude'],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
agentId: 'gaia.agent.refactoring.god-class',
|
|
345
|
+
nativeNames: { claude: 'gaia-god-class-decomposition' },
|
|
346
|
+
relativeInstallPath: '.claude/agents/gaia-god-class-decomposition.md',
|
|
347
|
+
role: 'refactoring',
|
|
348
|
+
type: 'god-class',
|
|
349
|
+
authorisedPhases: ['implementation'],
|
|
350
|
+
allowedPipelines: ['implement-feature'],
|
|
351
|
+
minimumToolkitVersion: '0.13.0',
|
|
352
|
+
deprecated: false,
|
|
353
|
+
deprecationTarget: null,
|
|
354
|
+
platforms: ['claude'],
|
|
355
|
+
},
|
|
356
|
+
// ── Security agents ───────────────────────────────────────────────────────
|
|
357
|
+
{
|
|
358
|
+
agentId: 'gaia.agent.security.supply-chain',
|
|
359
|
+
nativeNames: { claude: 'gaia-dependency-supply-chain-security' },
|
|
360
|
+
relativeInstallPath: '.claude/agents/gaia-dependency-supply-chain-security.md',
|
|
361
|
+
role: 'security',
|
|
362
|
+
type: 'supply-chain',
|
|
363
|
+
authorisedPhases: ['implementation'],
|
|
364
|
+
allowedPipelines: ['implement-feature'],
|
|
365
|
+
minimumToolkitVersion: '0.13.0',
|
|
366
|
+
deprecated: false,
|
|
367
|
+
deprecationTarget: null,
|
|
368
|
+
platforms: ['claude'],
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
agentId: 'gaia.agent.security.hardening',
|
|
372
|
+
nativeNames: { claude: 'gaia-security-hardening' },
|
|
373
|
+
relativeInstallPath: '.claude/agents/gaia-security-hardening.md',
|
|
374
|
+
role: 'security',
|
|
375
|
+
type: 'hardening',
|
|
376
|
+
authorisedPhases: ['implementation'],
|
|
377
|
+
allowedPipelines: ['implement-feature'],
|
|
378
|
+
minimumToolkitVersion: '0.13.0',
|
|
379
|
+
deprecated: false,
|
|
380
|
+
deprecationTarget: null,
|
|
381
|
+
platforms: ['claude'],
|
|
382
|
+
},
|
|
383
|
+
// ── Orchestrators (workflow scripts) ──────────────────────────────────────
|
|
384
|
+
{
|
|
385
|
+
agentId: 'gaia.orchestrator.feature.phase1',
|
|
386
|
+
nativeNames: { claude: 'pm-phase1' },
|
|
387
|
+
relativeInstallPath: '.claude/workflows/pm-phase1.js',
|
|
388
|
+
role: 'orchestrator',
|
|
389
|
+
type: 'feature-phase1',
|
|
390
|
+
authorisedPhases: ['planning'],
|
|
391
|
+
allowedPipelines: ['implement-feature'],
|
|
392
|
+
minimumToolkitVersion: '0.13.0',
|
|
393
|
+
deprecated: false,
|
|
394
|
+
deprecationTarget: null,
|
|
395
|
+
platforms: ['claude'],
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
agentId: 'gaia.orchestrator.feature.phase2',
|
|
399
|
+
nativeNames: { claude: 'pm-phase2' },
|
|
400
|
+
relativeInstallPath: '.claude/workflows/pm-phase2.js',
|
|
401
|
+
role: 'orchestrator',
|
|
402
|
+
type: 'feature-phase2',
|
|
403
|
+
authorisedPhases: ['planning'],
|
|
404
|
+
allowedPipelines: ['implement-feature'],
|
|
405
|
+
minimumToolkitVersion: '0.13.0',
|
|
406
|
+
deprecated: false,
|
|
407
|
+
deprecationTarget: null,
|
|
408
|
+
platforms: ['claude'],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
agentId: 'gaia.orchestrator.feature.phase3',
|
|
412
|
+
nativeNames: { claude: 'pm-phase3' },
|
|
413
|
+
relativeInstallPath: '.claude/workflows/pm-phase3.js',
|
|
414
|
+
role: 'orchestrator',
|
|
415
|
+
type: 'feature-phase3',
|
|
416
|
+
authorisedPhases: ['implementation'],
|
|
417
|
+
allowedPipelines: ['implement-feature'],
|
|
418
|
+
minimumToolkitVersion: '0.13.0',
|
|
419
|
+
deprecated: false,
|
|
420
|
+
deprecationTarget: null,
|
|
421
|
+
platforms: ['claude'],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
agentId: 'gaia.orchestrator.assessment.phase1',
|
|
425
|
+
nativeNames: { claude: 'am-phase1' },
|
|
426
|
+
relativeInstallPath: '.claude/workflows/am-phase1.js',
|
|
427
|
+
role: 'orchestrator',
|
|
428
|
+
type: 'assessment-phase1',
|
|
429
|
+
authorisedPhases: ['assessment'],
|
|
430
|
+
allowedPipelines: ['assess-codebase'],
|
|
431
|
+
minimumToolkitVersion: '0.13.0',
|
|
432
|
+
deprecated: false,
|
|
433
|
+
deprecationTarget: null,
|
|
434
|
+
platforms: ['claude'],
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
agentId: 'gaia.orchestrator.assessment.phase2',
|
|
438
|
+
nativeNames: { claude: 'am-phase2' },
|
|
439
|
+
relativeInstallPath: '.claude/workflows/am-phase2.js',
|
|
440
|
+
role: 'orchestrator',
|
|
441
|
+
type: 'assessment-phase2',
|
|
442
|
+
authorisedPhases: ['assessment'],
|
|
443
|
+
allowedPipelines: ['assess-codebase'],
|
|
444
|
+
minimumToolkitVersion: '0.13.0',
|
|
445
|
+
deprecated: false,
|
|
446
|
+
deprecationTarget: null,
|
|
447
|
+
platforms: ['claude'],
|
|
448
|
+
},
|
|
449
|
+
];
|
|
450
|
+
|
|
451
|
+
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
452
|
+
|
|
453
|
+
function _sha256Hex(buf) {
|
|
454
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function _readManifestSync(manifestPath) {
|
|
458
|
+
let raw;
|
|
459
|
+
try {
|
|
460
|
+
raw = fs.readFileSync(manifestPath, 'utf8');
|
|
461
|
+
} catch (_) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
return JSON.parse(raw);
|
|
466
|
+
} catch (_) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ── Provenance guard — internal resolution engine ─────────────────────────────
|
|
472
|
+
//
|
|
473
|
+
// Deterministic, pure JavaScript. No LLM, no network, no Claude API imports.
|
|
474
|
+
// Implements the verification chain specified in §2.3 "Agent Resolution with
|
|
475
|
+
// Hash Verification" of the FTR-017 Tech-Spec:
|
|
476
|
+
// 1. Catalog lookup → not-found on unknown ID
|
|
477
|
+
// 2. Platform check → not-applicable when platform unsupported
|
|
478
|
+
// 3. Effective installation determination (local vs global; both → conflict)
|
|
479
|
+
// 4. Manifest read → manifest-missing on absent or corrupt file
|
|
480
|
+
// 5. Manifest files-list membership check → not-installed when absent
|
|
481
|
+
// 6. On-disk file existence check → not-installed when absent
|
|
482
|
+
// 7. Scope collision check (same native name in both observable scopes)
|
|
483
|
+
// 8. Hash verification:
|
|
484
|
+
// no fileHashes in manifest → hash-unverifiable
|
|
485
|
+
// hash present but mismatch → hash-mismatch
|
|
486
|
+
// hash matches → verified
|
|
487
|
+
//
|
|
488
|
+
// homeDir is injected rather than sourced from os.homedir() so that test suites
|
|
489
|
+
// can pass an isolated temporary directory and never touch the real user home
|
|
490
|
+
// (AC-28 requirement).
|
|
491
|
+
|
|
492
|
+
function _provenanceGuard(projectDir, agentId, platform, homeDir) {
|
|
493
|
+
// Step 1 — catalog lookup
|
|
494
|
+
var entry = null;
|
|
495
|
+
for (var i = 0; i < CATALOG.length; i++) {
|
|
496
|
+
if (CATALOG[i].agentId === agentId) { entry = CATALOG[i]; break; }
|
|
497
|
+
}
|
|
498
|
+
if (!entry) {
|
|
499
|
+
return {
|
|
500
|
+
agentId: agentId,
|
|
501
|
+
platform: platform,
|
|
502
|
+
status: 'not-found',
|
|
503
|
+
error: 'Agent ID "' + agentId + '" is not in the canonical catalog.',
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Step 2 — platform support
|
|
508
|
+
if (!entry.platforms || entry.platforms.indexOf(platform) === -1) {
|
|
509
|
+
return {
|
|
510
|
+
agentId: agentId,
|
|
511
|
+
platform: platform,
|
|
512
|
+
nativeName: null,
|
|
513
|
+
status: 'not-applicable',
|
|
514
|
+
error: 'Agent "' + agentId + '" does not support platform "' + platform + '".',
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
var nativeName = entry.nativeNames[platform];
|
|
519
|
+
var relPath = entry.relativeInstallPath;
|
|
520
|
+
|
|
521
|
+
// Step 3 — determine effective installation
|
|
522
|
+
var projectManifestPath = path.join(projectDir, '.claude', '.ai-toolkit-manifest.json');
|
|
523
|
+
var globalManifestPath = path.join(homeDir, '.claude', '.ai-toolkit-manifest.json');
|
|
524
|
+
|
|
525
|
+
var hasProject = fs.existsSync(projectManifestPath);
|
|
526
|
+
var hasGlobal = fs.existsSync(globalManifestPath);
|
|
527
|
+
|
|
528
|
+
if (hasProject && hasGlobal) {
|
|
529
|
+
return {
|
|
530
|
+
agentId: agentId,
|
|
531
|
+
platform: platform,
|
|
532
|
+
nativeName: nativeName,
|
|
533
|
+
status: 'conflict',
|
|
534
|
+
error: 'Both local and global toolkit installations found; installation is ambiguous. ' +
|
|
535
|
+
'Remove one installation or reinstall to a single scope.',
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (!hasProject && !hasGlobal) {
|
|
540
|
+
return {
|
|
541
|
+
agentId: agentId,
|
|
542
|
+
platform: platform,
|
|
543
|
+
nativeName: nativeName,
|
|
544
|
+
status: 'manifest-missing',
|
|
545
|
+
error: 'No toolkit installation found; manifest absent at both project and global scope.',
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
var installRoot = hasProject ? projectDir : homeDir;
|
|
550
|
+
var scope = hasProject ? 'project' : 'global';
|
|
551
|
+
var manifestPath = hasProject ? projectManifestPath : globalManifestPath;
|
|
552
|
+
|
|
553
|
+
// Step 4 — manifest read
|
|
554
|
+
var manifest = _readManifestSync(manifestPath);
|
|
555
|
+
if (!manifest) {
|
|
556
|
+
return {
|
|
557
|
+
agentId: agentId,
|
|
558
|
+
platform: platform,
|
|
559
|
+
nativeName: nativeName,
|
|
560
|
+
scope: scope,
|
|
561
|
+
manifestPath: manifestPath,
|
|
562
|
+
status: 'manifest-missing',
|
|
563
|
+
error: 'Manifest at "' + manifestPath + '" is missing or could not be parsed.',
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
var toolkitVersion = manifest.version || 'unknown';
|
|
568
|
+
|
|
569
|
+
// Step 5 — manifest files-list membership
|
|
570
|
+
var files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
571
|
+
if (files.indexOf(relPath) === -1) {
|
|
572
|
+
return {
|
|
573
|
+
agentId: agentId,
|
|
574
|
+
platform: platform,
|
|
575
|
+
nativeName: nativeName,
|
|
576
|
+
scope: scope,
|
|
577
|
+
toolkitVersion: toolkitVersion,
|
|
578
|
+
manifestPath: manifestPath,
|
|
579
|
+
status: 'not-installed',
|
|
580
|
+
error: 'Agent file "' + relPath + '" is not declared in the manifest files list.',
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Step 6 — on-disk existence
|
|
585
|
+
var agentAbsPath = path.join(installRoot, relPath);
|
|
586
|
+
if (!fs.existsSync(agentAbsPath)) {
|
|
587
|
+
return {
|
|
588
|
+
agentId: agentId,
|
|
589
|
+
platform: platform,
|
|
590
|
+
nativeName: nativeName,
|
|
591
|
+
scope: scope,
|
|
592
|
+
path: agentAbsPath,
|
|
593
|
+
toolkitVersion: toolkitVersion,
|
|
594
|
+
manifestPath: manifestPath,
|
|
595
|
+
status: 'not-installed',
|
|
596
|
+
error: 'Agent file not found on disk: ' + agentAbsPath,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Step 7 — scope collision: check the OTHER observable scope for the same native name
|
|
601
|
+
var otherInstallRoot = hasProject ? homeDir : projectDir;
|
|
602
|
+
var otherAgentPath = path.join(otherInstallRoot, relPath);
|
|
603
|
+
if (fs.existsSync(otherAgentPath)) {
|
|
604
|
+
return {
|
|
605
|
+
agentId: agentId,
|
|
606
|
+
platform: platform,
|
|
607
|
+
nativeName: nativeName,
|
|
608
|
+
scope: scope,
|
|
609
|
+
path: agentAbsPath,
|
|
610
|
+
toolkitVersion: toolkitVersion,
|
|
611
|
+
manifestPath: manifestPath,
|
|
612
|
+
status: 'conflict',
|
|
613
|
+
conflictPaths: [agentAbsPath, otherAgentPath],
|
|
614
|
+
error: 'Agent file "' + nativeName + '" found in both observable scopes: ' +
|
|
615
|
+
agentAbsPath + ' and ' + otherAgentPath + '.',
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Step 8 — hash verification (provenance guard)
|
|
620
|
+
if (!manifest.fileHashes) {
|
|
621
|
+
// v0.12.0 manifest — no integrity data
|
|
622
|
+
return {
|
|
623
|
+
agentId: agentId,
|
|
624
|
+
platform: platform,
|
|
625
|
+
nativeName: nativeName,
|
|
626
|
+
scope: scope,
|
|
627
|
+
path: agentAbsPath,
|
|
628
|
+
toolkitVersion: toolkitVersion,
|
|
629
|
+
manifestPath: manifestPath,
|
|
630
|
+
sha256: null,
|
|
631
|
+
status: 'hash-unverifiable',
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
var recordedHash = manifest.fileHashes[relPath];
|
|
636
|
+
if (!recordedHash) {
|
|
637
|
+
return {
|
|
638
|
+
agentId: agentId,
|
|
639
|
+
platform: platform,
|
|
640
|
+
nativeName: nativeName,
|
|
641
|
+
scope: scope,
|
|
642
|
+
path: agentAbsPath,
|
|
643
|
+
toolkitVersion: toolkitVersion,
|
|
644
|
+
manifestPath: manifestPath,
|
|
645
|
+
sha256: null,
|
|
646
|
+
status: 'hash-unverifiable',
|
|
647
|
+
error: 'No hash entry in manifest fileHashes for "' + relPath + '". ' +
|
|
648
|
+
'Reinstall or upgrade the toolkit runtime to generate integrity hashes.',
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Recompute SHA-256 from disk on every call to detect post-install modification.
|
|
653
|
+
var content = fs.readFileSync(agentAbsPath);
|
|
654
|
+
var computed = 'sha256:' + _sha256Hex(content);
|
|
655
|
+
|
|
656
|
+
if (computed !== recordedHash) {
|
|
657
|
+
return {
|
|
658
|
+
agentId: agentId,
|
|
659
|
+
platform: platform,
|
|
660
|
+
nativeName: nativeName,
|
|
661
|
+
scope: scope,
|
|
662
|
+
path: agentAbsPath,
|
|
663
|
+
toolkitVersion: toolkitVersion,
|
|
664
|
+
manifestPath: manifestPath,
|
|
665
|
+
sha256: recordedHash,
|
|
666
|
+
status: 'hash-mismatch',
|
|
667
|
+
error: 'SHA-256 on disk does not match manifest record. ' +
|
|
668
|
+
'Computed: ' + computed + '. Recorded: ' + recordedHash + '.',
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return {
|
|
673
|
+
agentId: agentId,
|
|
674
|
+
platform: platform,
|
|
675
|
+
nativeName: nativeName,
|
|
676
|
+
scope: scope,
|
|
677
|
+
path: agentAbsPath,
|
|
678
|
+
toolkitVersion: toolkitVersion,
|
|
679
|
+
manifestPath: manifestPath,
|
|
680
|
+
sha256: computed,
|
|
681
|
+
status: 'verified',
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Resolve an agent by canonical ID with optional verification.
|
|
689
|
+
*
|
|
690
|
+
* @param {object} opts
|
|
691
|
+
* @param {string} opts.projectDir - project root directory
|
|
692
|
+
* @param {string} opts.agentId - canonical ID (e.g. "gaia.agent.developer.backend")
|
|
693
|
+
* @param {string} [opts.platform] - target platform (default: "claude")
|
|
694
|
+
* @param {boolean} [opts.requireVerified] - when true, throws if status is not "verified"
|
|
695
|
+
* @param {string} [opts.homeDir] - override home directory (for test isolation, AC-28)
|
|
696
|
+
* @returns {Promise<object>} resolution record with status and identity fields
|
|
697
|
+
* @throws when requireVerified is true and the resolved status is not "verified"
|
|
698
|
+
*/
|
|
699
|
+
async function resolveAgent(opts) {
|
|
700
|
+
var projectDir = opts.projectDir;
|
|
701
|
+
var agentId = opts.agentId;
|
|
702
|
+
var platform = opts.platform != null ? opts.platform : 'claude';
|
|
703
|
+
var requireVerified = opts.requireVerified != null ? opts.requireVerified : false;
|
|
704
|
+
var homeDir = opts.homeDir != null ? opts.homeDir : os.homedir();
|
|
705
|
+
|
|
706
|
+
var rec = _provenanceGuard(projectDir, agentId, platform, homeDir);
|
|
707
|
+
|
|
708
|
+
if (requireVerified && rec.status !== 'verified') {
|
|
709
|
+
var msg = 'agent-registry: resolution failed — status "' + rec.status + '" for ' + agentId;
|
|
710
|
+
if (rec.error) msg += ': ' + rec.error;
|
|
711
|
+
var err = new Error(msg);
|
|
712
|
+
err.code = 'RESOLUTION_FAILED';
|
|
713
|
+
err.record = rec;
|
|
714
|
+
throw err;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return rec;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Validate a set of agent references against the catalog and manifest.
|
|
722
|
+
*
|
|
723
|
+
* When agentIds is omitted all catalog agents are validated.
|
|
724
|
+
* An ID not present in the catalog is reported as an error immediately,
|
|
725
|
+
* without attempting filesystem resolution.
|
|
726
|
+
*
|
|
727
|
+
* @param {string} projectDir
|
|
728
|
+
* @param {string[]} [agentIds] - subset of canonical IDs to validate; defaults to all
|
|
729
|
+
* @param {string} [homeDir] - override home directory (for test isolation, AC-28)
|
|
730
|
+
* @returns {Promise<object[]>} array of error records (empty array = all valid)
|
|
731
|
+
*/
|
|
732
|
+
async function validateAgentSet(projectDir, agentIds, homeDir) {
|
|
733
|
+
var ids = Array.isArray(agentIds) ? agentIds : CATALOG.map(function (e) { return e.agentId; });
|
|
734
|
+
var errors = [];
|
|
735
|
+
|
|
736
|
+
for (var i = 0; i < ids.length; i++) {
|
|
737
|
+
var id = ids[i];
|
|
738
|
+
|
|
739
|
+
var inCatalog = false;
|
|
740
|
+
for (var j = 0; j < CATALOG.length; j++) {
|
|
741
|
+
if (CATALOG[j].agentId === id) { inCatalog = true; break; }
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (!inCatalog) {
|
|
745
|
+
errors.push({
|
|
746
|
+
agentId: id,
|
|
747
|
+
status: 'not-found',
|
|
748
|
+
error: 'Agent ID "' + id + '" is not in the canonical catalog.',
|
|
749
|
+
});
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
var rec = await resolveAgent({
|
|
754
|
+
projectDir: projectDir,
|
|
755
|
+
agentId: id,
|
|
756
|
+
platform: 'claude',
|
|
757
|
+
requireVerified: false,
|
|
758
|
+
homeDir: homeDir,
|
|
759
|
+
});
|
|
760
|
+
if (rec.status !== 'verified' && rec.status !== 'hash-unverifiable') {
|
|
761
|
+
errors.push({
|
|
762
|
+
agentId: id,
|
|
763
|
+
status: rec.status,
|
|
764
|
+
error: rec.error || 'Agent not available.',
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return errors;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* List all registered toolkit agents with availability information.
|
|
774
|
+
*
|
|
775
|
+
* Uses the doctor-agents status vocabulary (per AC-13):
|
|
776
|
+
* "verified" | "conflict" | "hash-unverifiable" | "not-installed" | "not-applicable"
|
|
777
|
+
* A hash-mismatch is surfaced as status "conflict" with integrityDetail: "hash-mismatch".
|
|
778
|
+
*
|
|
779
|
+
* @param {string} projectDir
|
|
780
|
+
* @param {string} [homeDir] - override home directory (for test isolation, AC-28)
|
|
781
|
+
* @returns {object[]} agent records with status
|
|
782
|
+
*/
|
|
783
|
+
function listRegisteredAgents(projectDir, homeDir) {
|
|
784
|
+
if (homeDir == null) homeDir = os.homedir();
|
|
785
|
+
var projectManifestPath = path.join(projectDir, '.claude', '.ai-toolkit-manifest.json');
|
|
786
|
+
var globalManifestPath = path.join(homeDir, '.claude', '.ai-toolkit-manifest.json');
|
|
787
|
+
|
|
788
|
+
var hasProject = fs.existsSync(projectManifestPath);
|
|
789
|
+
var hasGlobal = fs.existsSync(globalManifestPath);
|
|
790
|
+
|
|
791
|
+
var bothPresent = hasProject && hasGlobal;
|
|
792
|
+
var manifest = null;
|
|
793
|
+
var scope = null;
|
|
794
|
+
var installRoot = null;
|
|
795
|
+
var manifestPath = null;
|
|
796
|
+
|
|
797
|
+
if (!bothPresent) {
|
|
798
|
+
if (hasProject) {
|
|
799
|
+
manifest = _readManifestSync(projectManifestPath);
|
|
800
|
+
scope = 'project';
|
|
801
|
+
installRoot = projectDir;
|
|
802
|
+
manifestPath = projectManifestPath;
|
|
803
|
+
} else if (hasGlobal) {
|
|
804
|
+
manifest = _readManifestSync(globalManifestPath);
|
|
805
|
+
scope = 'global';
|
|
806
|
+
installRoot = homeDir;
|
|
807
|
+
manifestPath = globalManifestPath;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return CATALOG.map(function (entry) {
|
|
812
|
+
var nativeName = entry.nativeNames.claude;
|
|
813
|
+
|
|
814
|
+
var base = {
|
|
815
|
+
agentId: entry.agentId,
|
|
816
|
+
nativeName: nativeName,
|
|
817
|
+
role: entry.role,
|
|
818
|
+
type: entry.type,
|
|
819
|
+
platforms: entry.platforms,
|
|
820
|
+
deprecated: entry.deprecated,
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
if (bothPresent) {
|
|
824
|
+
return Object.assign({}, base, {
|
|
825
|
+
status: 'conflict',
|
|
826
|
+
error: 'Both local and global toolkit installations found; installation is ambiguous.',
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!manifest) {
|
|
831
|
+
return Object.assign({}, base, {
|
|
832
|
+
status: 'not-installed',
|
|
833
|
+
scope: null,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
var relPath = entry.relativeInstallPath;
|
|
838
|
+
var toolkitVersion = manifest.version || 'unknown';
|
|
839
|
+
var files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
840
|
+
|
|
841
|
+
if (files.indexOf(relPath) === -1) {
|
|
842
|
+
return Object.assign({}, base, {
|
|
843
|
+
status: 'not-installed',
|
|
844
|
+
scope: scope,
|
|
845
|
+
toolkitVersion: toolkitVersion,
|
|
846
|
+
manifestPath: manifestPath,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
var absPath = path.join(installRoot, relPath);
|
|
851
|
+
if (!fs.existsSync(absPath)) {
|
|
852
|
+
return Object.assign({}, base, {
|
|
853
|
+
status: 'not-installed',
|
|
854
|
+
scope: scope,
|
|
855
|
+
path: absPath,
|
|
856
|
+
toolkitVersion: toolkitVersion,
|
|
857
|
+
manifestPath: manifestPath,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (!manifest.fileHashes) {
|
|
862
|
+
return Object.assign({}, base, {
|
|
863
|
+
status: 'hash-unverifiable',
|
|
864
|
+
scope: scope,
|
|
865
|
+
path: absPath,
|
|
866
|
+
toolkitVersion: toolkitVersion,
|
|
867
|
+
manifestPath: manifestPath,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
var recordedHash = manifest.fileHashes[relPath];
|
|
872
|
+
if (!recordedHash) {
|
|
873
|
+
return Object.assign({}, base, {
|
|
874
|
+
status: 'hash-unverifiable',
|
|
875
|
+
scope: scope,
|
|
876
|
+
path: absPath,
|
|
877
|
+
toolkitVersion: toolkitVersion,
|
|
878
|
+
manifestPath: manifestPath,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
var content = fs.readFileSync(absPath);
|
|
883
|
+
var computed = 'sha256:' + _sha256Hex(content);
|
|
884
|
+
|
|
885
|
+
if (computed !== recordedHash) {
|
|
886
|
+
return Object.assign({}, base, {
|
|
887
|
+
status: 'conflict',
|
|
888
|
+
integrityDetail: 'hash-mismatch',
|
|
889
|
+
scope: scope,
|
|
890
|
+
path: absPath,
|
|
891
|
+
sha256: recordedHash,
|
|
892
|
+
toolkitVersion: toolkitVersion,
|
|
893
|
+
manifestPath: manifestPath,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return Object.assign({}, base, {
|
|
898
|
+
status: 'verified',
|
|
899
|
+
scope: scope,
|
|
900
|
+
path: absPath,
|
|
901
|
+
sha256: computed,
|
|
902
|
+
toolkitVersion: toolkitVersion,
|
|
903
|
+
manifestPath: manifestPath,
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// ── Platform adapter interface (contractual) ──────────────────────────────────
|
|
909
|
+
//
|
|
910
|
+
// A platform adapter handles agent resolution for one specific runtime platform
|
|
911
|
+
// (Claude Code, GitHub Copilot Codex, GitHub Copilot, etc.).
|
|
912
|
+
//
|
|
913
|
+
// The Claude adapter (CLAUDE_ADAPTER) is the only operational implementation.
|
|
914
|
+
// The Codex and Copilot adapters (CODEX_ADAPTER, COPILOT_ADAPTER) are
|
|
915
|
+
// contractual stubs only (AC-32): they declare the required interface so that
|
|
916
|
+
// future implementors have an exact contract, but each stub's resolve() method
|
|
917
|
+
// throws a "not implemented" error and must never be used for real dispatch.
|
|
918
|
+
//
|
|
919
|
+
// Contract: every adapter must expose
|
|
920
|
+
// platformId : string
|
|
921
|
+
// resolve : async function(AdapterResolveOpts) → AdapterResolutionRecord
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Options passed to every platform adapter's resolve() method.
|
|
925
|
+
*
|
|
926
|
+
* @typedef {object} AdapterResolveOpts
|
|
927
|
+
* @property {string} projectDir - absolute path to the project root
|
|
928
|
+
* @property {string} agentId - canonical agent ID (e.g. "gaia.agent.developer.backend")
|
|
929
|
+
* @property {boolean} [requireVerified] - when true the adapter must throw if status is not "verified"
|
|
930
|
+
* @property {string} [homeDir] - override home directory; used by tests for scope isolation (AC-28)
|
|
931
|
+
*/
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Resolution record returned by every platform adapter's resolve() method.
|
|
935
|
+
* Mirrors the shape returned by resolveAgent() — the Claude adapter's operational output.
|
|
936
|
+
*
|
|
937
|
+
* @typedef {object} AdapterResolutionRecord
|
|
938
|
+
* @property {string} agentId - canonical agent ID
|
|
939
|
+
* @property {string} platform - platform identifier (e.g. "claude", "codex", "copilot")
|
|
940
|
+
* @property {string|null} nativeName - platform-specific agent name; null when not applicable
|
|
941
|
+
* @property {string} [scope] - "project" or "global"; absent when not installed
|
|
942
|
+
* @property {string} [path] - absolute path to agent file; absent when not found
|
|
943
|
+
* @property {string} [toolkitVersion] - toolkit version recorded in the manifest
|
|
944
|
+
* @property {string} [manifestPath] - absolute path to the manifest file
|
|
945
|
+
* @property {string|null} [sha256] - "sha256:<hex>" or null when not verifiable
|
|
946
|
+
* @property {string} status - "verified" | "hash-unverifiable" | "hash-mismatch" |
|
|
947
|
+
* "not-found" | "not-installed" | "manifest-missing" |
|
|
948
|
+
* "conflict" | "not-applicable"
|
|
949
|
+
* @property {string} [error] - human-readable detail when status is not "verified"
|
|
950
|
+
*/
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Interface contract that every platform adapter must satisfy.
|
|
954
|
+
* New adapters must expose platformId (string) and resolve (async function).
|
|
955
|
+
*
|
|
956
|
+
* @typedef {object} PlatformAdapter
|
|
957
|
+
* @property {string} platformId - platform identifier
|
|
958
|
+
* @property {function(AdapterResolveOpts): Promise<AdapterResolutionRecord>} resolve - resolve one agent
|
|
959
|
+
*/
|
|
960
|
+
|
|
961
|
+
// ── Claude adapter (operational) ─────────────────────────────────────────────
|
|
962
|
+
//
|
|
963
|
+
// The Claude adapter is the only working PlatformAdapter implementation in
|
|
964
|
+
// FTR-017. It delegates to resolveAgent() and always forces platform: "claude".
|
|
965
|
+
|
|
966
|
+
/** @type {PlatformAdapter} */
|
|
967
|
+
var CLAUDE_ADAPTER = {
|
|
968
|
+
platformId: 'claude',
|
|
969
|
+
/** @param {AdapterResolveOpts} opts @returns {Promise<AdapterResolutionRecord>} */
|
|
970
|
+
resolve: function (opts) {
|
|
971
|
+
return resolveAgent(Object.assign({}, opts, { platform: 'claude' }));
|
|
972
|
+
},
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// ── Codex adapter (contractual stub only — AC-32) ─────────────────────────────
|
|
976
|
+
//
|
|
977
|
+
// This object declares the PlatformAdapter contract for GitHub Copilot Codex.
|
|
978
|
+
// It is NOT a working implementation. resolve() always throws.
|
|
979
|
+
// A future Codex adapter must expose the same platformId + resolve() members
|
|
980
|
+
// and return an AdapterResolutionRecord on every non-error code path.
|
|
981
|
+
|
|
982
|
+
/** @type {PlatformAdapter} */
|
|
983
|
+
var CODEX_ADAPTER = {
|
|
984
|
+
platformId: 'codex',
|
|
985
|
+
/** @param {AdapterResolveOpts} _opts @returns {Promise<AdapterResolutionRecord>} */
|
|
986
|
+
resolve: async function (_opts) {
|
|
987
|
+
throw new Error('codex adapter not implemented — contractual stub (AC-32)');
|
|
988
|
+
},
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// ── Copilot adapter (contractual stub only — AC-32) ───────────────────────────
|
|
992
|
+
//
|
|
993
|
+
// This object declares the PlatformAdapter contract for GitHub Copilot.
|
|
994
|
+
// It is NOT a working implementation. resolve() always throws.
|
|
995
|
+
// A future Copilot adapter must expose the same platformId + resolve() members
|
|
996
|
+
// and return an AdapterResolutionRecord on every non-error code path.
|
|
997
|
+
|
|
998
|
+
/** @type {PlatformAdapter} */
|
|
999
|
+
var COPILOT_ADAPTER = {
|
|
1000
|
+
platformId: 'copilot',
|
|
1001
|
+
/** @param {AdapterResolveOpts} _opts @returns {Promise<AdapterResolutionRecord>} */
|
|
1002
|
+
resolve: async function (_opts) {
|
|
1003
|
+
throw new Error('copilot adapter not implemented — contractual stub (AC-32)');
|
|
1004
|
+
},
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
module.exports = {
|
|
1008
|
+
CATALOG,
|
|
1009
|
+
WB_AGENT_TYPE_MAP,
|
|
1010
|
+
LEGACY_AGENT_TYPE_MAP,
|
|
1011
|
+
resolveWorkBreakdownAgentType,
|
|
1012
|
+
resolveAgent,
|
|
1013
|
+
validateAgentSet,
|
|
1014
|
+
listRegisteredAgents,
|
|
1015
|
+
CLAUDE_ADAPTER,
|
|
1016
|
+
CODEX_ADAPTER,
|
|
1017
|
+
COPILOT_ADAPTER,
|
|
1018
|
+
};
|