@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.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +60 -0
  2. package/.claude-plugin/plugin.json +36 -0
  3. package/CHANGELOG.md +74 -2
  4. package/LICENSE +184 -21
  5. package/README.md +90 -39
  6. package/RELEASE_NOTES.md +27 -0
  7. package/adapters/README.md +7 -0
  8. package/adapters/workbuddy/README.md +43 -0
  9. package/agents/README.md +2 -1
  10. package/agents/euclid/constraints.md +2 -1
  11. package/agents/euclid/responsibilities.md +1 -1
  12. package/agents/euclid/role.md +1 -1
  13. package/agents/euclid/system-prompt.md +5 -2
  14. package/agents/euclid/workflow.md +3 -3
  15. package/bin/archsight-aios.mjs +489 -11
  16. package/docs/PUBLIC_DISCOVERY.md +193 -0
  17. package/docs/quickstart.md +2 -1
  18. package/gemini-extension.json +6 -0
  19. package/governance/README.md +3 -0
  20. package/governance/arbitration-protocol.md +153 -0
  21. package/package.json +52 -31
  22. package/runtime/README.md +7 -0
  23. package/runtime/agent-routing.md +41 -17
  24. package/runtime/archsight-aios.manifest.json +166 -61
  25. package/runtime/capability-adapters.json +27 -0
  26. package/runtime/capability-registry.json +468 -0
  27. package/runtime/capability-registry.schema.json +135 -0
  28. package/runtime/skill-routing.md +26 -13
  29. package/scripts/validate-skills.mjs +134 -0
  30. package/skills/README.md +25 -9
  31. package/skills/aios-arch/SKILL.md +62 -24
  32. package/skills/aios-ceo/SKILL.md +11 -8
  33. package/skills/aios-commercial-contract/SKILL.md +89 -0
  34. package/skills/aios-commercial-contract/agents/openai.yaml +4 -0
  35. package/skills/aios-commercial-tender/SKILL.md +89 -0
  36. package/skills/aios-commercial-tender/agents/openai.yaml +4 -0
  37. package/skills/aios-commercial-variation/SKILL.md +88 -0
  38. package/skills/aios-commercial-variation/agents/openai.yaml +4 -0
  39. package/skills/aios-construction-daily/SKILL.md +86 -0
  40. package/skills/aios-construction-daily/agents/openai.yaml +4 -0
  41. package/skills/aios-construction-meeting/SKILL.md +86 -0
  42. package/skills/aios-construction-meeting/agents/openai.yaml +4 -0
  43. package/skills/aios-construction-scheme/SKILL.md +79 -0
  44. package/skills/aios-construction-scheme/agents/openai.yaml +4 -0
  45. package/skills/aios-exec/SKILL.md +11 -8
  46. package/skills/aios-knowledge/SKILL.md +12 -9
  47. package/skills/aios-plan/SKILL.md +38 -28
  48. package/skills/aios-review/SKILL.md +12 -9
  49. package/skills/aios-runtime/SKILL.md +14 -11
  50. package/skills/aios-structural/SKILL.md +67 -0
  51. package/skills/aios-structural/agents/openai.yaml +4 -0
  52. package/templates/project-ai/.ai/ARCHSIGHT_AIOS_RULES.md +13 -10
  53. package/templates/project-ai/.ai/agent-routing.md +17 -12
  54. package/templates/project-ai/.ai/skills.md +28 -11
  55. package/templates/project-ai/.ai/workflows.md +13 -9
  56. package/workflows/README.md +5 -0
  57. package/workflows/architecture-review.md +44 -22
  58. package/workflows/feature-development.md +25 -19
  59. package/workflows/rag-pipeline.md +9 -5
  60. package/workflows/site-daily-loop.md +101 -0
@@ -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
- const skillsRoot = path.join(repoRoot, "skills");
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") {