@archsight/aios 1.0.1 → 1.2.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/.claude-plugin/marketplace.json +60 -0
- package/.claude-plugin/plugin.json +36 -0
- package/CHANGELOG.md +74 -2
- package/LICENSE +184 -21
- package/README.md +90 -39
- package/RELEASE_NOTES.md +27 -0
- package/adapters/README.md +7 -0
- package/adapters/workbuddy/README.md +43 -0
- package/agents/README.md +2 -1
- package/agents/euclid/constraints.md +2 -1
- package/agents/euclid/responsibilities.md +1 -1
- package/agents/euclid/role.md +1 -1
- package/agents/euclid/system-prompt.md +5 -2
- package/agents/euclid/workflow.md +3 -3
- package/bin/archsight-aios.mjs +489 -11
- package/docs/PUBLIC_DISCOVERY.md +193 -0
- package/docs/quickstart.md +2 -1
- package/gemini-extension.json +6 -0
- package/governance/README.md +3 -0
- package/governance/arbitration-protocol.md +153 -0
- package/package.json +52 -31
- package/runtime/README.md +7 -0
- package/runtime/agent-routing.md +41 -17
- package/runtime/archsight-aios.manifest.json +166 -61
- package/runtime/capability-adapters.json +27 -0
- package/runtime/capability-registry.json +468 -0
- package/runtime/capability-registry.schema.json +135 -0
- package/runtime/skill-routing.md +26 -13
- package/scripts/validate-skills.mjs +134 -0
- package/skills/README.md +25 -9
- package/skills/aios-arch/SKILL.md +62 -24
- package/skills/aios-ceo/SKILL.md +11 -8
- package/skills/aios-commercial-contract/SKILL.md +89 -0
- package/skills/aios-commercial-contract/agents/openai.yaml +4 -0
- package/skills/aios-commercial-tender/SKILL.md +89 -0
- package/skills/aios-commercial-tender/agents/openai.yaml +4 -0
- package/skills/aios-commercial-variation/SKILL.md +88 -0
- package/skills/aios-commercial-variation/agents/openai.yaml +4 -0
- package/skills/aios-construction-daily/SKILL.md +86 -0
- package/skills/aios-construction-daily/agents/openai.yaml +4 -0
- package/skills/aios-construction-meeting/SKILL.md +86 -0
- package/skills/aios-construction-meeting/agents/openai.yaml +4 -0
- package/skills/aios-construction-scheme/SKILL.md +79 -0
- package/skills/aios-construction-scheme/agents/openai.yaml +4 -0
- package/skills/aios-exec/SKILL.md +11 -8
- package/skills/aios-knowledge/SKILL.md +12 -9
- package/skills/aios-plan/SKILL.md +38 -28
- package/skills/aios-review/SKILL.md +12 -9
- package/skills/aios-runtime/SKILL.md +14 -11
- package/skills/aios-structural/SKILL.md +67 -0
- package/skills/aios-structural/agents/openai.yaml +4 -0
- package/templates/project-ai/.ai/ARCHSIGHT_AIOS_RULES.md +13 -10
- package/templates/project-ai/.ai/agent-routing.md +17 -12
- package/templates/project-ai/.ai/skills.md +28 -11
- package/templates/project-ai/.ai/workflows.md +13 -9
- package/workflows/README.md +5 -0
- package/workflows/architecture-review.md +44 -22
- package/workflows/feature-development.md +25 -19
- package/workflows/rag-pipeline.md +9 -5
- package/workflows/site-daily-loop.md +101 -0
package/bin/archsight-aios.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -17,6 +18,7 @@ const antigravityPluginName = "archsight-aios";
|
|
|
17
18
|
const assetDirs = [
|
|
18
19
|
"skills",
|
|
19
20
|
"workflows",
|
|
21
|
+
"adapters",
|
|
20
22
|
"templates",
|
|
21
23
|
"runtime",
|
|
22
24
|
"agents",
|
|
@@ -43,13 +45,20 @@ const skillAliases = {
|
|
|
43
45
|
"aios-bim-domain-modeling",
|
|
44
46
|
"archsight-bim-domain-modeling"
|
|
45
47
|
],
|
|
48
|
+
"aios-structural": ["aios-structural-review", "archsight-structural-review"],
|
|
46
49
|
"aios-runtime": [
|
|
47
50
|
"aios-runtime-design",
|
|
48
51
|
"archsight-runtime-design",
|
|
49
52
|
"aios-ai-runtime-design",
|
|
50
53
|
"archsight-ai-runtime-design"
|
|
51
54
|
],
|
|
52
|
-
"aios-exec": ["aios-controlled-execution", "archsight-controlled-execution"]
|
|
55
|
+
"aios-exec": ["aios-controlled-execution", "archsight-controlled-execution"],
|
|
56
|
+
"aios-commercial-tender": ["aios-tender", "archsight-tender"],
|
|
57
|
+
"aios-commercial-contract": ["aios-contract", "archsight-contract"],
|
|
58
|
+
"aios-commercial-variation": ["aios-variation", "archsight-variation"],
|
|
59
|
+
"aios-construction-daily": ["aios-daily", "archsight-daily"],
|
|
60
|
+
"aios-construction-meeting": ["aios-meeting", "archsight-meeting"],
|
|
61
|
+
"aios-construction-scheme": ["aios-scheme", "archsight-scheme"]
|
|
53
62
|
};
|
|
54
63
|
|
|
55
64
|
function usage() {
|
|
@@ -58,10 +67,11 @@ function usage() {
|
|
|
58
67
|
"",
|
|
59
68
|
"Usage:",
|
|
60
69
|
" archsight-aios help",
|
|
61
|
-
" archsight-aios install --target <codex|agents|gemini|antigravity|all> --scope user",
|
|
70
|
+
" archsight-aios install --target <codex|agents|gemini|antigravity|workbuddy|all> --scope user",
|
|
62
71
|
" archsight-aios doctor",
|
|
63
72
|
" archsight-aios init [--cwd <path>] [--mode <auto|full|linked|ai-only>] [--profile <name>]",
|
|
64
73
|
" archsight-aios validate [--cwd <path>] [--profile <name>] [--temp]",
|
|
74
|
+
" archsight-aios capability:call --capability <id> --agent <id> --skill <id> --input <json-file>",
|
|
65
75
|
" archsight-aios hermes:validate",
|
|
66
76
|
" archsight-aios hermes:sync-dry-run",
|
|
67
77
|
" archsight-aios hermes:detect-drift",
|
|
@@ -72,11 +82,13 @@ function usage() {
|
|
|
72
82
|
" doctor Check repository assets and user-level installation.",
|
|
73
83
|
" init Add AI rules and .ai governance files to a project.",
|
|
74
84
|
" validate Validate the project AI template output.",
|
|
85
|
+
" capability:call Authorize and call a registered local Capability adapter.",
|
|
75
86
|
" hermes:* Validate or dry-run Hermes runtime prompt sync.",
|
|
76
87
|
"",
|
|
77
88
|
"Examples:",
|
|
78
89
|
" npx @archsight/aios install --target codex --scope user",
|
|
79
90
|
" npx @archsight/aios install --target agents --scope user",
|
|
91
|
+
" npx @archsight/aios install --target workbuddy --scope user",
|
|
80
92
|
" npx @archsight/aios init",
|
|
81
93
|
" npx @archsight/aios validate --temp",
|
|
82
94
|
" npx @archsight/aios doctor"
|
|
@@ -94,7 +106,15 @@ function parseArgs(argv) {
|
|
|
94
106
|
profile: undefined,
|
|
95
107
|
cwd: process.cwd(),
|
|
96
108
|
help,
|
|
97
|
-
temp: false
|
|
109
|
+
temp: false,
|
|
110
|
+
capability: undefined,
|
|
111
|
+
agent: undefined,
|
|
112
|
+
skill: undefined,
|
|
113
|
+
input: undefined,
|
|
114
|
+
mcpCwd: undefined,
|
|
115
|
+
mcpCommand: undefined,
|
|
116
|
+
mcpArgs: [],
|
|
117
|
+
timeoutMs: undefined
|
|
98
118
|
};
|
|
99
119
|
|
|
100
120
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -111,6 +131,22 @@ function parseArgs(argv) {
|
|
|
111
131
|
options.profile = rest[++i];
|
|
112
132
|
} else if (arg === "--temp") {
|
|
113
133
|
options.temp = true;
|
|
134
|
+
} else if (arg === "--capability") {
|
|
135
|
+
options.capability = rest[++i];
|
|
136
|
+
} else if (arg === "--agent") {
|
|
137
|
+
options.agent = rest[++i];
|
|
138
|
+
} else if (arg === "--skill") {
|
|
139
|
+
options.skill = rest[++i];
|
|
140
|
+
} else if (arg === "--input") {
|
|
141
|
+
options.input = path.resolve(rest[++i]);
|
|
142
|
+
} else if (arg === "--mcp-cwd") {
|
|
143
|
+
options.mcpCwd = path.resolve(rest[++i]);
|
|
144
|
+
} else if (arg === "--mcp-command") {
|
|
145
|
+
options.mcpCommand = rest[++i];
|
|
146
|
+
} else if (arg === "--mcp-arg") {
|
|
147
|
+
options.mcpArgs.push(rest[++i]);
|
|
148
|
+
} else if (arg === "--timeout-ms") {
|
|
149
|
+
options.timeoutMs = Number(rest[++i]);
|
|
114
150
|
} else if (arg === "--help" || arg === "-h") {
|
|
115
151
|
options.help = true;
|
|
116
152
|
} else {
|
|
@@ -187,12 +223,7 @@ async function listAiosSkills() {
|
|
|
187
223
|
return manifest.skills.map((skill) => skill.id).sort();
|
|
188
224
|
}
|
|
189
225
|
|
|
190
|
-
|
|
191
|
-
const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
|
|
192
|
-
return entries
|
|
193
|
-
.filter((entry) => entry.isDirectory() && entry.name.startsWith("aios-"))
|
|
194
|
-
.map((entry) => entry.name)
|
|
195
|
-
.sort();
|
|
226
|
+
return listRepositoryAiosSkills();
|
|
196
227
|
}
|
|
197
228
|
|
|
198
229
|
async function listAiosWorkflowPaths() {
|
|
@@ -202,6 +233,22 @@ async function listAiosWorkflowPaths() {
|
|
|
202
233
|
.sort();
|
|
203
234
|
}
|
|
204
235
|
|
|
236
|
+
async function listRepositoryAiosSkills() {
|
|
237
|
+
const entries = await fs.readdir(path.join(repoRoot, "skills"), { withFileTypes: true });
|
|
238
|
+
return entries
|
|
239
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith("aios-"))
|
|
240
|
+
.map((entry) => entry.name)
|
|
241
|
+
.sort();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function listRepositoryWorkflowIds() {
|
|
245
|
+
const entries = await fs.readdir(path.join(repoRoot, "workflows"), { withFileTypes: true });
|
|
246
|
+
return entries
|
|
247
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md")
|
|
248
|
+
.map((entry) => entry.name.replace(/\.md$/, ""))
|
|
249
|
+
.sort();
|
|
250
|
+
}
|
|
251
|
+
|
|
205
252
|
async function readManifest() {
|
|
206
253
|
const manifestPath = path.join(repoRoot, "runtime", "archsight-aios.manifest.json");
|
|
207
254
|
const raw = await fs.readFile(manifestPath, "utf8");
|
|
@@ -213,6 +260,373 @@ async function readJson(filePath) {
|
|
|
213
260
|
return JSON.parse(raw);
|
|
214
261
|
}
|
|
215
262
|
|
|
263
|
+
async function readCapabilityRegistry() {
|
|
264
|
+
const manifest = await readManifest();
|
|
265
|
+
const registryPath = path.join(repoRoot, manifest.capabilityRegistry.registryPath);
|
|
266
|
+
return readJson(registryPath);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function readCapabilityAdapters() {
|
|
270
|
+
const manifest = await readManifest();
|
|
271
|
+
const adapterPath = manifest.capabilityRegistry?.adapterPath;
|
|
272
|
+
if (!adapterPath) {
|
|
273
|
+
return { schema: 1, adapters: [] };
|
|
274
|
+
}
|
|
275
|
+
return readJson(path.join(repoRoot, adapterPath));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function jsonTypeMatches(schemaType, value) {
|
|
279
|
+
if (schemaType === "object") {
|
|
280
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
281
|
+
}
|
|
282
|
+
if (schemaType === "array") {
|
|
283
|
+
return Array.isArray(value);
|
|
284
|
+
}
|
|
285
|
+
if (schemaType === "integer") {
|
|
286
|
+
return Number.isInteger(value);
|
|
287
|
+
}
|
|
288
|
+
if (schemaType === "number") {
|
|
289
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
290
|
+
}
|
|
291
|
+
if (schemaType === "string") {
|
|
292
|
+
return typeof value === "string";
|
|
293
|
+
}
|
|
294
|
+
if (schemaType === "boolean") {
|
|
295
|
+
return typeof value === "boolean";
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function valueAtPath(value, fieldPath) {
|
|
301
|
+
return fieldPath.split(".").reduce((current, key) => {
|
|
302
|
+
if (current === null || typeof current !== "object") {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
return current[key];
|
|
306
|
+
}, value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function validateJsonSchemaSubset(schema, value, label = "$", errors = []) {
|
|
310
|
+
if (!schema || typeof schema !== "object") {
|
|
311
|
+
return errors;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (schema.type && !jsonTypeMatches(schema.type, value)) {
|
|
315
|
+
errors.push(`${label} expected ${schema.type}`);
|
|
316
|
+
return errors;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
320
|
+
errors.push(`${label} expected one of ${schema.enum.join(", ")}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (typeof schema.minimum === "number" && typeof value === "number" && value < schema.minimum) {
|
|
324
|
+
errors.push(`${label} must be >= ${schema.minimum}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (schema.type === "object" && value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
328
|
+
for (const requiredField of schema.required ?? []) {
|
|
329
|
+
if (!Object.hasOwn(value, requiredField)) {
|
|
330
|
+
errors.push(`${label}.${requiredField} is required`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (const [property, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
335
|
+
if (Object.hasOwn(value, property)) {
|
|
336
|
+
validateJsonSchemaSubset(propertySchema, value[property], `${label}.${property}`, errors);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
342
|
+
value.forEach((item, index) => validateJsonSchemaSubset(schema.items, item, `${label}[${index}]`, errors));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return errors;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function findCapability(registry, capabilityId) {
|
|
349
|
+
return (registry.capabilities ?? []).find((capability) => capability.id === capabilityId);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function findCapabilityAdapter(adapters, capabilityId) {
|
|
353
|
+
return (adapters.adapters ?? []).find((adapter) => (adapter.capabilityIds ?? []).includes(capabilityId));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function authorizeCapability(capability, options) {
|
|
357
|
+
if (!options.agent) {
|
|
358
|
+
throw new Error("--agent is required for Capability calls");
|
|
359
|
+
}
|
|
360
|
+
if (!options.skill) {
|
|
361
|
+
throw new Error("--skill is required for Capability calls");
|
|
362
|
+
}
|
|
363
|
+
if (!capability.ownerAgents.includes(options.agent)) {
|
|
364
|
+
throw new Error(`Capability denied: agent ${options.agent} cannot call ${capability.id}`);
|
|
365
|
+
}
|
|
366
|
+
if ((capability.allowedSkills ?? []).length > 0 && !capability.allowedSkills.includes(options.skill)) {
|
|
367
|
+
throw new Error(`Capability denied: skill ${options.skill} cannot call ${capability.id}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function normalizeExpectedValue(rawValue) {
|
|
372
|
+
const trimmed = rawValue.trim();
|
|
373
|
+
if (trimmed === "true") {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (trimmed === "false") {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
380
|
+
return Number(trimmed);
|
|
381
|
+
}
|
|
382
|
+
return trimmed.replace(/^["']|["']$/g, "");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function evaluateRuleCondition(condition, result) {
|
|
386
|
+
const match = condition.match(/^([A-Za-z0-9_.]+)\s*==\s*(.+)$/);
|
|
387
|
+
if (!match) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
const actual = valueAtPath(result, match[1]);
|
|
391
|
+
const expected = normalizeExpectedValue(match[2]);
|
|
392
|
+
return actual === expected;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function evaluateCapabilityDecision(capability, result, validationErrors) {
|
|
396
|
+
const missingEvidence = (capability.evidenceContract?.requiredFields ?? [])
|
|
397
|
+
.filter((field) => valueAtPath(result, field) === undefined);
|
|
398
|
+
const matchedRules = (capability.blockingRules ?? [])
|
|
399
|
+
.filter((rule) => evaluateRuleCondition(rule.when, result));
|
|
400
|
+
|
|
401
|
+
if (validationErrors.length > 0 || missingEvidence.length > 0) {
|
|
402
|
+
return {
|
|
403
|
+
action: "hold",
|
|
404
|
+
severity: "P1",
|
|
405
|
+
matchedRules,
|
|
406
|
+
missingEvidence,
|
|
407
|
+
validationErrors
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (matchedRules.length === 0) {
|
|
412
|
+
return {
|
|
413
|
+
action: "proceed",
|
|
414
|
+
severity: "none",
|
|
415
|
+
matchedRules,
|
|
416
|
+
missingEvidence,
|
|
417
|
+
validationErrors
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const actionRank = { block: 4, human_escalation: 3, hold: 2, revise: 1 };
|
|
422
|
+
const severityRank = { P0: 3, P1: 2, P2: 1 };
|
|
423
|
+
const sorted = [...matchedRules].sort((left, right) => {
|
|
424
|
+
const severityDiff = (severityRank[right.severity] ?? 0) - (severityRank[left.severity] ?? 0);
|
|
425
|
+
if (severityDiff !== 0) {
|
|
426
|
+
return severityDiff;
|
|
427
|
+
}
|
|
428
|
+
return (actionRank[right.action] ?? 0) - (actionRank[left.action] ?? 0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
action: sorted[0].action,
|
|
433
|
+
severity: sorted[0].severity,
|
|
434
|
+
matchedRules,
|
|
435
|
+
missingEvidence,
|
|
436
|
+
validationErrors
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function defaultAdapterCwd(adapter, options) {
|
|
441
|
+
if (options.mcpCwd) {
|
|
442
|
+
return options.mcpCwd;
|
|
443
|
+
}
|
|
444
|
+
if (adapter.cwdEnv && process.env[adapter.cwdEnv]) {
|
|
445
|
+
return path.resolve(process.env[adapter.cwdEnv]);
|
|
446
|
+
}
|
|
447
|
+
if (adapter.defaultSiblingDir) {
|
|
448
|
+
return path.resolve(repoRoot, "..", adapter.defaultSiblingDir);
|
|
449
|
+
}
|
|
450
|
+
return repoRoot;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function resolveCapabilityAdapter(adapter, capabilityId, options) {
|
|
454
|
+
return {
|
|
455
|
+
command: options.mcpCommand || adapter.command,
|
|
456
|
+
args: options.mcpArgs.length > 0 ? options.mcpArgs : adapter.args ?? [],
|
|
457
|
+
cwd: defaultAdapterCwd(adapter, options),
|
|
458
|
+
toolName: adapter.toolNameMap?.[capabilityId] ?? capabilityId.split(".").at(-1),
|
|
459
|
+
timeoutMs: Number(options.timeoutMs || adapter.timeoutMs || 30000)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function callMcpStdio({ command, args, cwd, toolName, input, timeoutMs }) {
|
|
464
|
+
return new Promise((resolve, reject) => {
|
|
465
|
+
const child = spawn(command, args, {
|
|
466
|
+
cwd,
|
|
467
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
468
|
+
windowsHide: true
|
|
469
|
+
});
|
|
470
|
+
let stdout = "";
|
|
471
|
+
let stderr = "";
|
|
472
|
+
let settled = false;
|
|
473
|
+
|
|
474
|
+
function settle(callback, value) {
|
|
475
|
+
if (settled) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
settled = true;
|
|
479
|
+
clearTimeout(timer);
|
|
480
|
+
callback(value);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const timer = setTimeout(() => {
|
|
484
|
+
child.kill();
|
|
485
|
+
settle(reject, new Error(`MCP call timed out after ${timeoutMs}ms`));
|
|
486
|
+
}, timeoutMs);
|
|
487
|
+
|
|
488
|
+
child.stdout.on("data", (chunk) => {
|
|
489
|
+
stdout += chunk.toString("utf8");
|
|
490
|
+
});
|
|
491
|
+
child.stderr.on("data", (chunk) => {
|
|
492
|
+
stderr += chunk.toString("utf8");
|
|
493
|
+
});
|
|
494
|
+
child.on("error", (error) => {
|
|
495
|
+
settle(reject, error);
|
|
496
|
+
});
|
|
497
|
+
child.on("close", (exitCode) => {
|
|
498
|
+
if (exitCode !== 0) {
|
|
499
|
+
settle(reject, new Error(`MCP server exited with ${exitCode}: ${stderr.trim()}`));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
const responses = stdout
|
|
505
|
+
.split(/\r?\n/)
|
|
506
|
+
.filter((line) => line.trim().length > 0)
|
|
507
|
+
.map((line) => JSON.parse(line));
|
|
508
|
+
const callResponse = responses.find((response) => response.id === 2);
|
|
509
|
+
if (!callResponse) {
|
|
510
|
+
throw new Error("MCP tools/call response was not returned");
|
|
511
|
+
}
|
|
512
|
+
if (callResponse.error) {
|
|
513
|
+
throw new Error(`MCP tools/call failed: ${callResponse.error.message}`);
|
|
514
|
+
}
|
|
515
|
+
settle(resolve, {
|
|
516
|
+
initialize: responses.find((response) => response.id === 1)?.result,
|
|
517
|
+
call: callResponse.result,
|
|
518
|
+
stderr
|
|
519
|
+
});
|
|
520
|
+
} catch (error) {
|
|
521
|
+
settle(reject, new Error(`${error.message}. stdout: ${stdout.trim()}`));
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const initialize = {
|
|
526
|
+
jsonrpc: "2.0",
|
|
527
|
+
id: 1,
|
|
528
|
+
method: "initialize",
|
|
529
|
+
params: {
|
|
530
|
+
protocolVersion: "2025-06-18",
|
|
531
|
+
capabilities: {},
|
|
532
|
+
clientInfo: { name: "archsight-aios", version: "1.2.0" }
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
const callTool = {
|
|
536
|
+
jsonrpc: "2.0",
|
|
537
|
+
id: 2,
|
|
538
|
+
method: "tools/call",
|
|
539
|
+
params: {
|
|
540
|
+
name: toolName,
|
|
541
|
+
arguments: input
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
child.stdin.write(`${JSON.stringify(initialize)}\n`);
|
|
545
|
+
child.stdin.write(`${JSON.stringify(callTool)}\n`);
|
|
546
|
+
child.stdin.end();
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function capabilityCall(options) {
|
|
551
|
+
if (!options.capability) {
|
|
552
|
+
throw new Error("--capability is required");
|
|
553
|
+
}
|
|
554
|
+
if (!options.input) {
|
|
555
|
+
throw new Error("--input is required");
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const registry = await readCapabilityRegistry();
|
|
559
|
+
const capability = findCapability(registry, options.capability);
|
|
560
|
+
if (!capability) {
|
|
561
|
+
throw new Error(`Unknown Capability: ${options.capability}`);
|
|
562
|
+
}
|
|
563
|
+
authorizeCapability(capability, options);
|
|
564
|
+
|
|
565
|
+
const adapters = await readCapabilityAdapters();
|
|
566
|
+
const adapter = findCapabilityAdapter(adapters, capability.id);
|
|
567
|
+
if (!adapter) {
|
|
568
|
+
throw new Error(`No local adapter registered for Capability: ${capability.id}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const input = await readJson(options.input);
|
|
572
|
+
const inputErrors = validateJsonSchemaSubset(capability.inputSchema, input, "$.input");
|
|
573
|
+
if (inputErrors.length > 0) {
|
|
574
|
+
throw new Error(`Capability input validation failed: ${inputErrors.join("; ")}`);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const resolvedAdapter = resolveCapabilityAdapter(adapter, capability.id, options);
|
|
578
|
+
if (!resolvedAdapter.command) {
|
|
579
|
+
throw new Error(`Capability adapter ${adapter.id} does not define a command`);
|
|
580
|
+
}
|
|
581
|
+
if (!(await exists(resolvedAdapter.cwd))) {
|
|
582
|
+
throw new Error(`MCP adapter cwd not found: ${resolvedAdapter.cwd}`);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const mcp = await callMcpStdio({
|
|
586
|
+
command: resolvedAdapter.command,
|
|
587
|
+
args: resolvedAdapter.args,
|
|
588
|
+
cwd: resolvedAdapter.cwd,
|
|
589
|
+
toolName: resolvedAdapter.toolName,
|
|
590
|
+
input,
|
|
591
|
+
timeoutMs: resolvedAdapter.timeoutMs
|
|
592
|
+
});
|
|
593
|
+
const structuredContent = mcp.call?.structuredContent;
|
|
594
|
+
if (!structuredContent || typeof structuredContent !== "object") {
|
|
595
|
+
throw new Error("MCP tool result did not include structuredContent");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const outputErrors = validateJsonSchemaSubset(capability.outputSchema, structuredContent, "$.toolResult");
|
|
599
|
+
const decision = evaluateCapabilityDecision(capability, structuredContent, outputErrors);
|
|
600
|
+
const envelope = {
|
|
601
|
+
schema: 1,
|
|
602
|
+
capabilityId: capability.id,
|
|
603
|
+
agent: options.agent,
|
|
604
|
+
skill: options.skill,
|
|
605
|
+
authorized: true,
|
|
606
|
+
inputValidated: true,
|
|
607
|
+
adapter: {
|
|
608
|
+
id: adapter.id,
|
|
609
|
+
transport: adapter.transport,
|
|
610
|
+
cwd: resolvedAdapter.cwd,
|
|
611
|
+
command: resolvedAdapter.command,
|
|
612
|
+
args: resolvedAdapter.args,
|
|
613
|
+
toolName: resolvedAdapter.toolName
|
|
614
|
+
},
|
|
615
|
+
toolResult: structuredContent,
|
|
616
|
+
decision,
|
|
617
|
+
evidence: {
|
|
618
|
+
level: capability.authorityLevel,
|
|
619
|
+
source: "mcp.structuredContent",
|
|
620
|
+
serverInfo: mcp.initialize?.serverInfo
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
625
|
+
if (["block", "hold", "human_escalation"].includes(decision.action)) {
|
|
626
|
+
process.exitCode = 2;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
216
630
|
function routeAgentName(manifest, agentId) {
|
|
217
631
|
return manifest.agents.find((agent) => agent.id === agentId)?.displayName ?? agentId;
|
|
218
632
|
}
|
|
@@ -286,6 +700,10 @@ function antigravityPluginRoot() {
|
|
|
286
700
|
return path.join(antigravityPluginsRoot(), antigravityPluginName);
|
|
287
701
|
}
|
|
288
702
|
|
|
703
|
+
function workBuddySkillsRoot() {
|
|
704
|
+
return path.join(home, ".workbuddy", "skills");
|
|
705
|
+
}
|
|
706
|
+
|
|
289
707
|
async function hasAntigravity2Config() {
|
|
290
708
|
if (!(await exists(path.join(home, ".gemini", "config")))) {
|
|
291
709
|
return false;
|
|
@@ -481,7 +899,7 @@ async function install(options) {
|
|
|
481
899
|
throw new Error("Only --scope user is supported in this release.");
|
|
482
900
|
}
|
|
483
901
|
|
|
484
|
-
const validTargets = new Set(["codex", "agents", "gemini", "antigravity", "all"]);
|
|
902
|
+
const validTargets = new Set(["codex", "agents", "gemini", "antigravity", "workbuddy", "all"]);
|
|
485
903
|
if (!validTargets.has(options.target)) {
|
|
486
904
|
throw new Error(`Unsupported target: ${options.target}`);
|
|
487
905
|
}
|
|
@@ -489,7 +907,7 @@ async function install(options) {
|
|
|
489
907
|
const skillNames = await listAiosSkills();
|
|
490
908
|
const workflowPaths = await listAiosWorkflowPaths();
|
|
491
909
|
const targets = options.target === "all"
|
|
492
|
-
? ["codex", "gemini", "antigravity"]
|
|
910
|
+
? ["codex", "gemini", "antigravity", "workbuddy"]
|
|
493
911
|
: [options.target];
|
|
494
912
|
|
|
495
913
|
const installed = [];
|
|
@@ -531,6 +949,12 @@ async function install(options) {
|
|
|
531
949
|
}
|
|
532
950
|
}
|
|
533
951
|
|
|
952
|
+
if (targets.includes("workbuddy")) {
|
|
953
|
+
await removeLegacySkillDirs(workBuddySkillsRoot(), skillNames);
|
|
954
|
+
await installSkillsTo(workBuddySkillsRoot(), skillNames);
|
|
955
|
+
installed.push("workbuddy skills");
|
|
956
|
+
}
|
|
957
|
+
|
|
534
958
|
console.log(`Installed: ${installed.join(", ")}`);
|
|
535
959
|
console.log(`Skills: ${skillNames.join(", ")}`);
|
|
536
960
|
console.log(`Workflows: ${workflowPaths.map((workflowPath) => path.basename(workflowPath)).join(", ")}`);
|
|
@@ -561,13 +985,26 @@ async function doctor() {
|
|
|
561
985
|
const manifestPath = path.join(repoRoot, "runtime", "archsight-aios.manifest.json");
|
|
562
986
|
const skillRoutingPath = path.join(repoRoot, "runtime", "skill-routing.md");
|
|
563
987
|
const packageJson = await readJson(path.join(repoRoot, "package.json"));
|
|
988
|
+
const repositorySkillIds = await listRepositoryAiosSkills();
|
|
989
|
+
const repositoryWorkflowIds = await listRepositoryWorkflowIds();
|
|
564
990
|
|
|
565
991
|
await check("manifest", manifestPath);
|
|
566
992
|
checkCondition("manifest schema", manifest.schema === 1, "schema === 1");
|
|
567
993
|
checkCondition("manifest name", manifest.name === "archsight-aios", "name === archsight-aios");
|
|
568
994
|
checkCondition("package name", packageJson.name === "@archsight/aios", "name === @archsight/aios");
|
|
569
995
|
checkCondition("package bin", packageJson.bin?.["archsight-aios"] === "./bin/archsight-aios.mjs", "bin.archsight-aios");
|
|
996
|
+
checkCondition(
|
|
997
|
+
"manifest covers skill directories",
|
|
998
|
+
JSON.stringify([...skillIds].sort()) === JSON.stringify(repositorySkillIds),
|
|
999
|
+
`manifest=${JSON.stringify([...skillIds].sort())} repo=${JSON.stringify(repositorySkillIds)}`
|
|
1000
|
+
);
|
|
1001
|
+
checkCondition(
|
|
1002
|
+
"manifest covers workflow files",
|
|
1003
|
+
JSON.stringify([...workflowIds].sort()) === JSON.stringify(repositoryWorkflowIds),
|
|
1004
|
+
`manifest=${JSON.stringify([...workflowIds].sort())} repo=${JSON.stringify(repositoryWorkflowIds)}`
|
|
1005
|
+
);
|
|
570
1006
|
checkCondition("codex workflows target", manifest.installTargets?.codexWorkflows === "~/.codex/workflows/aios", "codexWorkflows");
|
|
1007
|
+
checkCondition("workbuddy skills target", manifest.installTargets?.workBuddySkills === "~/.workbuddy/skills", "workBuddySkills");
|
|
571
1008
|
checkCondition("antigravity plugin target", manifest.installTargets?.antigravityPlugin === "~/.gemini/config/plugins/archsight-aios", "antigravityPlugin");
|
|
572
1009
|
checkCondition("antigravity legacy skills target", manifest.installTargets?.antigravityLegacySkills === "~/.gemini/antigravity/skills", "antigravityLegacySkills");
|
|
573
1010
|
|
|
@@ -624,6 +1061,44 @@ async function doctor() {
|
|
|
624
1061
|
await check("hermes sync record template", path.join(repoRoot, manifest.hermes.syncRecordTemplatePath));
|
|
625
1062
|
}
|
|
626
1063
|
|
|
1064
|
+
if (manifest.capabilityRegistry) {
|
|
1065
|
+
const registryPath = path.join(repoRoot, manifest.capabilityRegistry.registryPath);
|
|
1066
|
+
await check("capability registry schema", path.join(repoRoot, manifest.capabilityRegistry.schemaPath));
|
|
1067
|
+
await check("capability registry", registryPath);
|
|
1068
|
+
if (manifest.capabilityRegistry.adapterPath) {
|
|
1069
|
+
await check("capability adapters", path.join(repoRoot, manifest.capabilityRegistry.adapterPath));
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const registry = await readJson(registryPath);
|
|
1073
|
+
checkCondition("capability registry schema version", registry.schema === 1, "schema === 1");
|
|
1074
|
+
checkCondition("capability registry has capabilities", registry.capabilities?.length > 0, "capabilities.length > 0");
|
|
1075
|
+
|
|
1076
|
+
const capabilityIds = new Set();
|
|
1077
|
+
for (const capability of registry.capabilities ?? []) {
|
|
1078
|
+
checkCondition(`capability id unique ${capability.id}`, !capabilityIds.has(capability.id), capability.id);
|
|
1079
|
+
capabilityIds.add(capability.id);
|
|
1080
|
+
checkCondition(`capability authority ${capability.id}`, ["L1", "L2", "L3"].includes(capability.authorityLevel), capability.authorityLevel);
|
|
1081
|
+
checkCondition(`capability has blocking rules ${capability.id}`, capability.blockingRules?.length > 0, "blockingRules.length > 0");
|
|
1082
|
+
for (const agentId of capability.ownerAgents ?? []) {
|
|
1083
|
+
checkCondition(`capability owner exists ${capability.id}/${agentId}`, agentIds.has(agentId), agentId);
|
|
1084
|
+
}
|
|
1085
|
+
for (const skillId of capability.allowedSkills ?? []) {
|
|
1086
|
+
checkCondition(`capability skill exists ${capability.id}/${skillId}`, skillIds.has(skillId), skillId);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (manifest.capabilityRegistry.adapterPath) {
|
|
1091
|
+
const adapters = await readJson(path.join(repoRoot, manifest.capabilityRegistry.adapterPath));
|
|
1092
|
+
checkCondition("capability adapters schema version", adapters.schema === 1, "schema === 1");
|
|
1093
|
+
for (const adapter of adapters.adapters ?? []) {
|
|
1094
|
+
checkCondition(`capability adapter transport ${adapter.id}`, adapter.transport === "stdio-mcp", adapter.transport);
|
|
1095
|
+
for (const capabilityId of adapter.capabilityIds ?? []) {
|
|
1096
|
+
checkCondition(`capability adapter target exists ${adapter.id}/${capabilityId}`, capabilityIds.has(capabilityId), capabilityId);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
627
1102
|
await check("gemini support assets", geminiRoot);
|
|
628
1103
|
await check("gemini support skills", path.join(geminiRoot, "skills"));
|
|
629
1104
|
await check("gemini support workflows", path.join(geminiRoot, "workflows"));
|
|
@@ -651,6 +1126,7 @@ async function doctor() {
|
|
|
651
1126
|
const sourceDir = expectedSkillDir(skill);
|
|
652
1127
|
await check(`gemini support skill ${skillName}`, path.join(geminiRoot, sourceDir, "SKILL.md"));
|
|
653
1128
|
await check(`codex skill ${skillName}`, path.join(home, ".codex", "skills", skillName, "SKILL.md"));
|
|
1129
|
+
await check(`workbuddy skill ${skillName}`, path.join(workBuddySkillsRoot(), skillName, "SKILL.md"));
|
|
654
1130
|
if (useAntigravityLegacy) {
|
|
655
1131
|
await check(`antigravity 1.x legacy skill ${skillName}`, path.join(antigravityLegacySkillsRoot(), skillName, "SKILL.md"));
|
|
656
1132
|
}
|
|
@@ -942,6 +1418,8 @@ async function main() {
|
|
|
942
1418
|
await initProject(options);
|
|
943
1419
|
} else if (options.command === "validate") {
|
|
944
1420
|
await validateProjectTemplate(options);
|
|
1421
|
+
} else if (options.command === "capability:call") {
|
|
1422
|
+
await capabilityCall(options);
|
|
945
1423
|
} else if (options.command === "hermes:validate") {
|
|
946
1424
|
await validateHermesRegistry();
|
|
947
1425
|
} else if (options.command === "hermes:sync-dry-run") {
|