@aiwg/cli 2026.9.0 → 2026.9.1

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 (94) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +41 -0
  2. package/bin/aiwg.mjs +15 -3
  3. package/dist/src/api/index.d.ts +3 -0
  4. package/dist/src/api/index.js +3 -0
  5. package/dist/src/channel/manager.mjs +2 -2
  6. package/dist/src/cli/handlers/dataset.js +186 -0
  7. package/dist/src/cli/handlers/help.js +2 -1
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/init.js +1 -0
  10. package/dist/src/cli/handlers/output-mode.js +18 -1
  11. package/dist/src/cli/handlers/run.js +11 -3
  12. package/dist/src/cli/handlers/schema.js +221 -0
  13. package/dist/src/cli/handlers/steward.js +11 -1
  14. package/dist/src/cli/handlers/use.js +6 -2
  15. package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
  16. package/dist/src/cli/router.js +1 -1
  17. package/dist/src/cli/scope-resolver.js +22 -0
  18. package/dist/src/dataset/adapter-sdk.d.ts +41 -0
  19. package/dist/src/dataset/adapter-sdk.js +147 -0
  20. package/dist/src/dataset/adapter-types.d.ts +179 -0
  21. package/dist/src/dataset/adapter-types.js +2 -0
  22. package/dist/src/dataset/adapters.d.ts +104 -0
  23. package/dist/src/dataset/adapters.js +518 -0
  24. package/dist/src/dataset/conformance-types.d.ts +84 -0
  25. package/dist/src/dataset/conformance-types.js +3 -0
  26. package/dist/src/dataset/conformance.d.ts +13 -0
  27. package/dist/src/dataset/conformance.js +90 -0
  28. package/dist/src/dataset/contracts.d.ts +17 -0
  29. package/dist/src/dataset/contracts.js +236 -0
  30. package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
  31. package/dist/src/dataset/file-orchestration-repository.js +68 -0
  32. package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
  33. package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
  34. package/dist/src/dataset/index.d.ts +21 -0
  35. package/dist/src/dataset/index.js +21 -0
  36. package/dist/src/dataset/ledger-types.d.ts +141 -0
  37. package/dist/src/dataset/ledger-types.js +2 -0
  38. package/dist/src/dataset/ledger.d.ts +27 -0
  39. package/dist/src/dataset/ledger.js +100 -0
  40. package/dist/src/dataset/local-execution-backend.d.ts +10 -0
  41. package/dist/src/dataset/local-execution-backend.js +32 -0
  42. package/dist/src/dataset/orchestration-repository.d.ts +29 -0
  43. package/dist/src/dataset/orchestration-repository.js +34 -0
  44. package/dist/src/dataset/orchestration-service.d.ts +56 -0
  45. package/dist/src/dataset/orchestration-service.js +466 -0
  46. package/dist/src/dataset/orchestration-types.d.ts +83 -0
  47. package/dist/src/dataset/orchestration-types.js +2 -0
  48. package/dist/src/dataset/presentation.d.ts +3 -0
  49. package/dist/src/dataset/presentation.js +8 -0
  50. package/dist/src/dataset/projections.d.ts +42 -0
  51. package/dist/src/dataset/projections.js +192 -0
  52. package/dist/src/dataset/schema-governance.d.ts +71 -0
  53. package/dist/src/dataset/schema-governance.js +135 -0
  54. package/dist/src/dataset/standards-types.d.ts +66 -0
  55. package/dist/src/dataset/standards-types.js +10 -0
  56. package/dist/src/dataset/standards.d.ts +13 -0
  57. package/dist/src/dataset/standards.js +291 -0
  58. package/dist/src/dataset/types.d.ts +258 -0
  59. package/dist/src/dataset/types.js +2 -0
  60. package/dist/src/extensions/commands/definitions.js +27 -1
  61. package/dist/src/installation/manager.mjs +5 -1
  62. package/dist/src/output-modes/index.js +4 -0
  63. package/dist/src/output-modes/registry.js +68 -24
  64. package/dist/src/output-modes/runtime.js +10 -8
  65. package/dist/src/plugin/skill-command-translator.js +1 -0
  66. package/dist/src/providers/capability-matrix.yaml +41 -0
  67. package/dist/src/providers/provider-definitions.js +72 -0
  68. package/dist/src/providers/provider-inventory.js +1 -0
  69. package/dist/src/schema/catalog.js +234 -0
  70. package/dist/src/schema/compatibility.js +42 -0
  71. package/dist/src/schema/diagnostics.js +36 -0
  72. package/dist/src/schema/index.js +8 -0
  73. package/dist/src/schema/policy.js +58 -0
  74. package/dist/src/schema/resolver.js +76 -0
  75. package/dist/src/schema/types.js +2 -0
  76. package/dist/src/schema/validator.js +82 -0
  77. package/dist/src/storage/backends/fortemi.js +6 -0
  78. package/dist/src/storage/config.js +18 -4
  79. package/dist/src/storage/fortemi-qualification.js +106 -0
  80. package/dist/src/storage/index.js +1 -0
  81. package/dist/src/storage/types.js +1 -1
  82. package/package.json +3 -1
  83. package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
  84. package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
  85. package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
  86. package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
  87. package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
  88. package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
  89. package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
  90. package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
  91. package/schemas/dataset/run-ledger.v1.schema.json +39 -0
  92. package/schemas/dataset/source-adapter.v1.schema.json +112 -0
  93. package/tools/agents/deploy-agents.mjs +9 -3
  94. package/tools/agents/providers/pi.mjs +164 -0
@@ -470,6 +470,47 @@ providers:
470
470
  deploy_target: mixed
471
471
  aggregated_output: true
472
472
 
473
+ pi:
474
+ display_name: Pi Coding Agent
475
+ aliases:
476
+ - pi-coding-agent
477
+ status: experimental
478
+ daemon_tier: unsupported
479
+ daemon_pty_adapter: false
480
+ artifact_paths:
481
+ agents: .agents/skills/
482
+ commands: .pi/prompts/
483
+ skills: .pi/skills/
484
+ skills_cross_agent: .agents/skills/
485
+ rules: AGENTS.md
486
+ behaviors: .pi/extensions/
487
+ native_features:
488
+ cron: false
489
+ agent_teams: false
490
+ tasks: false
491
+ mcp: false
492
+ behaviors: false
493
+ mission_control: false
494
+ daemon: false
495
+ emulation:
496
+ cron: external-trigger
497
+ agent_teams: aiwg-mc
498
+ tasks: aiwg-mc
499
+ mcp: null
500
+ behaviors: null
501
+ mission_control: aiwg-mc
502
+ daemon: null
503
+ hook_wiring:
504
+ at_link_support: false
505
+ fallback: full-inject
506
+ context_file: AGENTS.md
507
+ interaction:
508
+ structured_questions: none
509
+ fallback: markdown
510
+ notes: Pi does not expose a built-in structured question tool.
511
+ deploy_target: project
512
+ aggregated_output: false
513
+
473
514
  # Feature definitions — documents what each feature key means and
474
515
  # what emulation strategies are available when native support is absent.
475
516
  features:
package/bin/aiwg.mjs CHANGED
@@ -128,6 +128,7 @@ const FAST_HELP_TEXT = `
128
128
  DISPATCH
129
129
  run skill <name> Execute a script-bearing skill
130
130
  run <script-name> Run a user-defined script from .aiwg/aiwg.config
131
+ output-mode <action> Configure composable output language and presentation
131
132
 
132
133
  FEATURES
133
134
  features Show optional feature install status
@@ -183,7 +184,7 @@ const FAST_HELP_TEXT = `
183
184
  --use-stable Switch back to stable npm package
184
185
  ────────────────────────────────────────────────────────
185
186
 
186
- Providers: claude (default), copilot, factory, codex, cursor, opencode, warp, windsurf
187
+ Providers: 12 — claude (default), codex, copilot, cursor, factory, hermes, opencode, openclaw, openhuman, pi, warp, windsurf (alias: devin)
187
188
 
188
189
  Examples:
189
190
  aiwg use sdlc Install SDLC framework
@@ -233,6 +234,12 @@ function maybeWarnUnbuiltDist() {
233
234
  }
234
235
  maybeWarnUnbuiltDist();
235
236
 
237
+ // Propagate the strict no-write contract through helpers that may resolve
238
+ // installation/channel state before the command context exists.
239
+ if (process.argv.slice(2).includes('--dry-run')) {
240
+ process.env['AIWG_CLI_DRY_RUN'] = '1';
241
+ }
242
+
236
243
  // Display a cached notice and schedule its refresh before every eligible CLI
237
244
  // path, including fast help/version, channel recovery, and later preflight
238
245
  // failures. This local-only bootstrap never waits on the registry and failures
@@ -299,7 +306,7 @@ trace('bin:entry');
299
306
  */
300
307
  async function resolveRouterPath() {
301
308
  const { loadConfig } = await import('../dist/src/channel/manager.mjs');
302
- const config = await loadConfig();
309
+ const config = await loadConfig({ createIfMissing: !process.argv.slice(2).includes('--dry-run') });
303
310
  if (config.devMode && config.edgePath && config.edgePath !== packageRoot) {
304
311
  const devRouter = path.join(config.edgePath, 'dist', 'src', 'cli', 'router.js');
305
312
  if (!existsSync(devRouter)) {
@@ -414,7 +421,12 @@ async function main() {
414
421
  process.exit(1);
415
422
  }
416
423
  const { assertCanonicalInstallation } = await import(pathToFileURL(identityPath).href);
417
- assertCanonicalInstallation({ actualRoot: activePackageRoot });
424
+ const readOnlyPreview = args.includes('--dry-run');
425
+ assertCanonicalInstallation({
426
+ actualRoot: activePackageRoot,
427
+ createIfMissing: !readOnlyPreview,
428
+ allowUnrecorded: readOnlyPreview,
429
+ });
418
430
  }
419
431
 
420
432
  // Wire up the logger level from -v/-vv/--quiet/AIWG_LOG_LEVEL before any
@@ -19,5 +19,8 @@ export * from '../marketplace/artifact-attestation.js';
19
19
  export * from '../uhp/index.js';
20
20
  export * from '../mission-protocol/index.js';
21
21
  export * from '../governance/index.js';
22
+ export * from '../output-modes/index.js';
23
+ export * from '../schema/index.js';
24
+ export * from '../dataset/index.js';
22
25
  export { ARTIFACT_TRUST_ROOT_MEDIA_TYPE, ARTIFACT_TRUST_ROOT_SCHEMA_VERSION, ARTIFACT_TRUST_STATE_SCHEMA_VERSION, bootstrapTrustRoot, parseTrustRoot, parseTrustState, readTrustState, validateTrustRoot, validateTrustState, verifyRootTransition, writeTrustState, channelStateKey, canonicalJson, dssePae, publicKeyFingerprint, sha256, type ArtifactTrustRoot, type ArtifactTrustState, type RootBootstrapResult, type RootTransitionResult, type ArtifactTrustPolicySettings, type TrustedChannelState, } from '../security/artifact-trust.js';
23
26
  //# sourceMappingURL=index.d.ts.map
@@ -19,5 +19,8 @@ export * from '../marketplace/artifact-attestation.js';
19
19
  export * from '../uhp/index.js';
20
20
  export * from '../mission-protocol/index.js';
21
21
  export * from '../governance/index.js';
22
+ export * from '../output-modes/index.js';
23
+ export * from '../schema/index.js';
24
+ export * from '../dataset/index.js';
22
25
  export { ARTIFACT_TRUST_ROOT_MEDIA_TYPE, ARTIFACT_TRUST_ROOT_SCHEMA_VERSION, ARTIFACT_TRUST_STATE_SCHEMA_VERSION, bootstrapTrustRoot, parseTrustRoot, parseTrustState, readTrustState, validateTrustRoot, validateTrustState, verifyRootTransition, writeTrustState, channelStateKey, canonicalJson, dssePae, publicKeyFingerprint, sha256, } from '../security/artifact-trust.js';
23
26
  //# sourceMappingURL=index.js.map
@@ -213,8 +213,8 @@ export async function getChannel() {
213
213
  * Get the framework root path based on current channel
214
214
  * @returns {Promise<string>} Path to framework root
215
215
  */
216
- export async function getFrameworkRoot() {
217
- const config = await loadConfig();
216
+ export async function getFrameworkRoot(options = {}) {
217
+ const config = await loadConfig(options);
218
218
 
219
219
  if (config.channel === 'edge') {
220
220
  // Check if edge installation exists
@@ -0,0 +1,186 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { createBuiltinAdapterRegistry } from "../../dataset/adapters.js";
4
+ import { FileDatasetOrchestrationRepository } from "../../dataset/file-orchestration-repository.js";
5
+ import { LocalDatasetExecutionBackend } from "../../dataset/local-execution-backend.js";
6
+ import { DatasetOrchestrationService } from "../../dataset/orchestration-service.js";
7
+ import { presentDatasetResult } from "../../dataset/presentation.js";
8
+ export const DATASET_ACTIONS = [
9
+ "source",
10
+ "check",
11
+ "preview",
12
+ "plan",
13
+ "ingest",
14
+ "status",
15
+ "show",
16
+ "verify",
17
+ "query",
18
+ "lineage",
19
+ "export",
20
+ "cancel",
21
+ "retry",
22
+ ];
23
+ function option(args, name) {
24
+ const i = args.indexOf(name);
25
+ return i < 0 ? undefined : args[i + 1];
26
+ }
27
+ function positions(args) {
28
+ const values = new Set([
29
+ "--file",
30
+ "--count",
31
+ "--digest",
32
+ "--idempotency-key",
33
+ "--approve",
34
+ "--reconciliation-digest",
35
+ "--reconciliation-threshold",
36
+ ]);
37
+ const out = [];
38
+ for (let i = 0; i < args.length; i++) {
39
+ if (values.has(args[i])) {
40
+ i++;
41
+ continue;
42
+ }
43
+ if (!args[i].startsWith("-"))
44
+ out.push(args[i]);
45
+ }
46
+ return out;
47
+ }
48
+ function input(ctx) {
49
+ const file = option(ctx.args, "--file");
50
+ if (!file)
51
+ throw new Error("DATASET_INPUT_REQUIRED: --file <json> is required");
52
+ return JSON.parse(readFileSync(resolve(ctx.cwd, file), "utf8"));
53
+ }
54
+ function service(ctx) {
55
+ const registry = createBuiltinAdapterRegistry();
56
+ return new DatasetOrchestrationService(new FileDatasetOrchestrationRepository(ctx.cwd), {
57
+ adapter: (id, version) => {
58
+ const adapter = registry.adapters.find((candidate) => {
59
+ const manifest = candidate.describe();
60
+ return manifest.id === id && manifest.version === version;
61
+ });
62
+ if (!adapter)
63
+ throw new Error(`ADAPTER_NOT_REGISTERED: ${id}@${version}`);
64
+ return adapter;
65
+ },
66
+ localBackend: new LocalDatasetExecutionBackend(),
67
+ });
68
+ }
69
+ export async function executeDatasetCommand(ctx, providedService) {
70
+ const [action, ...ids] = positions(ctx.args);
71
+ if (!DATASET_ACTIONS.includes(action))
72
+ return {
73
+ exitCode: 1,
74
+ message: `Unknown dataset action '${action ?? ""}'.`,
75
+ };
76
+ const s = providedService ?? service(ctx);
77
+ let result;
78
+ switch (action) {
79
+ case "source":
80
+ result = await s.source(input(ctx));
81
+ break;
82
+ case "check":
83
+ result = await s.check(ids[0], ctx.args.includes("--offline"));
84
+ break;
85
+ case "preview":
86
+ result = await s.preview(ids[0], Number(option(ctx.args, "--count") ?? 10), ctx.args.includes("--offline"), ctx.signal);
87
+ break;
88
+ case "plan":
89
+ result = await s.plan(input(ctx));
90
+ break;
91
+ case "ingest":
92
+ result = await s.ingest({
93
+ planId: ids[0],
94
+ planDigest: option(ctx.args, "--digest") ?? "",
95
+ idempotencyKey: option(ctx.args, "--idempotency-key") ?? "",
96
+ approvalIds: (option(ctx.args, "--approve") ?? "")
97
+ .split(",")
98
+ .filter(Boolean),
99
+ ...(option(ctx.args, "--reconciliation-digest")
100
+ ? {
101
+ reconciliationApproval: {
102
+ previewDigest: option(ctx.args, "--reconciliation-digest"),
103
+ threshold: Number(option(ctx.args, "--reconciliation-threshold")),
104
+ },
105
+ }
106
+ : {}),
107
+ signal: ctx.signal,
108
+ });
109
+ break;
110
+ case "status":
111
+ result = await s.status(ids[0]);
112
+ break;
113
+ case "show":
114
+ result = await s.show(ids[0]);
115
+ break;
116
+ case "verify":
117
+ result = await s.verify(ids[0]);
118
+ break;
119
+ case "query":
120
+ result = await s.query(ids[0]);
121
+ break;
122
+ case "lineage":
123
+ result = await s.lineage(ids[0]);
124
+ break;
125
+ case "export":
126
+ result = await s.export(ids[0]);
127
+ break;
128
+ case "cancel":
129
+ result = await s.cancel(ids[0]);
130
+ break;
131
+ case "retry":
132
+ result = await s.retry(ids[0], ctx.signal);
133
+ break;
134
+ }
135
+ return {
136
+ exitCode: result.ok ? 0 : 1,
137
+ rawOutput: true,
138
+ message: presentDatasetResult(result, ctx.args.includes("--json")),
139
+ };
140
+ }
141
+ export const datasetHandler = {
142
+ id: "dataset",
143
+ name: "Dataset intelligence",
144
+ description: "Register, preview, plan, ingest, verify, query, and trace datasets",
145
+ category: "index",
146
+ aliases: ["datasets"],
147
+ async help() {
148
+ return {
149
+ exitCode: 0,
150
+ rawOutput: true,
151
+ message: [
152
+ "Usage: aiwg dataset <action> [id] [options]",
153
+ "",
154
+ "Actions: source, check, preview, plan, ingest, status, show, verify, query, lineage, export, cancel, retry",
155
+ "source/plan: --file <json>",
156
+ "preview: <source-id> [--count N] [--offline]",
157
+ "ingest: <plan-id> --digest <sha256> --idempotency-key <key> [--approve id,id]",
158
+ "All actions: --json for the canonical aiwg.dataset-orchestration/v1 envelope.",
159
+ ].join("\n"),
160
+ };
161
+ },
162
+ async execute(ctx) {
163
+ try {
164
+ return await executeDatasetCommand(ctx);
165
+ }
166
+ catch (e) {
167
+ return {
168
+ exitCode: 1,
169
+ rawOutput: true,
170
+ message: JSON.stringify({
171
+ schema: "aiwg.dataset-orchestration/v1",
172
+ ok: false,
173
+ diagnostics: [
174
+ {
175
+ code: "DATASET_CLI_INVALID",
176
+ message: e instanceof Error ? e.message : String(e),
177
+ boundary: "input",
178
+ retryable: false,
179
+ },
180
+ ],
181
+ }, null, 2) + "\n",
182
+ };
183
+ }
184
+ },
185
+ };
186
+ //# sourceMappingURL=dataset.js.map
@@ -90,6 +90,7 @@ function displayHelp() {
90
90
  helpGroup('DISPATCH', [
91
91
  ['run skill <name>', 'Execute a script-bearing skill'],
92
92
  ['run <script-name>', 'Run a user-defined script from .aiwg/aiwg.config'],
93
+ ['output-mode <action>', 'Configure composable output language and presentation'],
93
94
  ]);
94
95
  helpGroup('FEATURES', [
95
96
  ['features', 'Show optional feature install status'],
@@ -143,7 +144,7 @@ function displayHelp() {
143
144
  ]);
144
145
  ui.rule();
145
146
  ui.blank();
146
- console.log(` ${ui.dimText('Providers:')} claude (default), copilot, factory, codex, cursor, opencode, warp, windsurf`);
147
+ console.log(` ${ui.dimText('Providers:')} 12 — claude (default), codex, copilot, cursor, factory, hermes, opencode, openclaw, openhuman, pi, warp, windsurf (alias: devin)`);
147
148
  ui.blank();
148
149
  console.log(` ${ui.dimText('Examples:')}`);
149
150
  console.log(` aiwg use sdlc ${ui.dimText('Install SDLC framework')}`);
@@ -66,12 +66,14 @@ import { artifactVerifyHandler } from './artifact-verify.js';
66
66
  import { outputModeHandler } from './output-mode.js';
67
67
  import { installationHandler } from './installation.js';
68
68
  import { uhpHandler } from './uhp.js';
69
+ import { schemaHandler } from './schema.js';
70
+ import { datasetHandler } from './dataset.js';
69
71
  // Re-export individual handlers
70
72
  export {
71
73
  // Maintenance
72
74
  helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
73
75
  // Framework management
74
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, installationHandler,
76
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, schemaHandler, datasetHandler, installationHandler,
75
77
  // Project
76
78
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
77
79
  // Workspace
@@ -156,6 +158,8 @@ export const allHandlers = [
156
158
  evidenceHandler,
157
159
  artifactVerifyHandler,
158
160
  outputModeHandler,
161
+ schemaHandler,
162
+ datasetHandler,
159
163
  // Workspace management
160
164
  ...workspaceHandlers,
161
165
  // Subcommand handlers (MCP, catalog, index, skills)
@@ -30,6 +30,7 @@ const PROVIDER_LABELS = {
30
30
  codex: 'OpenAI Codex ~/.codex/',
31
31
  openclaw: 'OpenClaw ~/.openclaw/',
32
32
  hermes: 'Hermes (MCP) aiwg mcp',
33
+ pi: 'Pi Coding Agent .pi/ + .agents/skills/',
33
34
  };
34
35
  // Local aliases for the shared prompt utilities. These preserve existing
35
36
  // call sites (askYesNo/askString) while routing through a single timeout
@@ -53,12 +53,29 @@ async function execute(ctx) {
53
53
  return { exitCode: 1, message: `Unknown output mode '${id}'.` };
54
54
  modes = action === 'enable' ? [...new Set([...modes, id])] : modes.filter(value => value !== id);
55
55
  }
56
- await resolveOutputModes(ctx.cwd, ctx.frameworkRoot, scope === 'session' ? modes : []);
56
+ await resolveOutputModes(ctx.cwd, ctx.frameworkRoot, [], { [scope]: modes });
57
57
  const path = await writeOutputModeState(ctx.cwd, scope, modes);
58
58
  return { exitCode: 0, message: `${action === 'clear' ? 'Cleared' : `${action}d`} ${scope} output modes (${modes.join(', ') || 'unaltered'}) at ${path}` };
59
59
  }
60
60
  export const outputModeHandler = {
61
61
  id: 'output-mode', name: 'Output Modes', description: 'List, inspect, and select composable output modes', category: 'project', aliases: ['output-modes'],
62
+ async help() {
63
+ return {
64
+ exitCode: 0,
65
+ rawOutput: true,
66
+ message: [
67
+ 'Usage:',
68
+ ' aiwg output-mode list',
69
+ ' aiwg output-mode show <id>',
70
+ ' aiwg output-mode enable <id> --scope invocation|session|project',
71
+ ' aiwg output-mode disable <id> --scope session|project',
72
+ ' aiwg output-mode clear --scope session|project',
73
+ ' aiwg output-mode status [--output-mode <id>]...',
74
+ '',
75
+ 'Use repeated --output-mode flags with aiwg run for a one-command stack.',
76
+ ].join('\n'),
77
+ };
78
+ },
62
79
  async execute(ctx) { try {
63
80
  return await execute(ctx);
64
81
  }
@@ -17,11 +17,16 @@ import { readAiwgConfig, getProjectDir } from '../../config/aiwg-config.js';
17
17
  import { handlerResultFromError } from '../errors.js';
18
18
  import * as ui from '../ui.js';
19
19
  import { resolveOutputModes } from '../../output-modes/registry.js';
20
- function extractOutputModes(args) {
20
+ export function extractOutputModes(args) {
21
21
  const cleaned = [];
22
22
  const modes = [];
23
+ let forwarding = false;
23
24
  for (let i = 0; i < args.length; i++) {
24
- if (args[i] === '--output-mode') {
25
+ if (args[i] === '--') {
26
+ forwarding = true;
27
+ cleaned.push(args[i]);
28
+ }
29
+ else if (!forwarding && args[i] === '--output-mode') {
25
30
  const id = args[i + 1];
26
31
  if (!id || id.startsWith('-'))
27
32
  throw new Error('--output-mode requires a mode ID');
@@ -67,7 +72,10 @@ export const runHandler = {
67
72
  }
68
73
  const scriptName = parsed.args[0];
69
74
  const projectDir = getProjectDir(ctx, ctx.args);
70
- let outputModeEnv = {};
75
+ let outputModeEnv = {
76
+ AIWG_OUTPUT_MODES: '',
77
+ AIWG_OUTPUT_MODES_JSON: '[]',
78
+ };
71
79
  try {
72
80
  const resolved = await resolveOutputModes(projectDir, ctx.frameworkRoot, parsed.modes);
73
81
  if (resolved.modes.length > 0) {
@@ -0,0 +1,221 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, extname, resolve } from 'node:path';
4
+ import { mkdirSync } from 'node:fs';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { analyzeBackwardCompatibility, loadSchemaCatalog, SchemaResolver, SchemaValidator } from '../../schema/index.js';
7
+ const ACTIONS = ['list', 'show', 'graph', 'policy', 'validate', 'lint', 'check-refs', 'diff', 'compatibility', 'generate', 'verify-projections'];
8
+ function value(args, flag) {
9
+ const index = args.indexOf(flag);
10
+ return index < 0 ? undefined : args[index + 1];
11
+ }
12
+ function positional(args) {
13
+ const flagsWithValues = new Set(['--catalog', '--domain', '--lifecycle', '--format', '--direction', '--schema', '--against']);
14
+ const result = [];
15
+ for (let index = 0; index < args.length; index += 1) {
16
+ const arg = args[index];
17
+ if (!arg)
18
+ continue;
19
+ if (flagsWithValues.has(arg)) {
20
+ index += 1;
21
+ continue;
22
+ }
23
+ if (!arg.startsWith('-'))
24
+ result.push(arg);
25
+ }
26
+ return result;
27
+ }
28
+ function jsonResult(payload, exitCode = 0) {
29
+ return { exitCode, rawOutput: true, message: `${JSON.stringify(payload, null, 2)}\n` };
30
+ }
31
+ function digest(bytes) {
32
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
33
+ }
34
+ function readData(path) {
35
+ const source = readFileSync(path, 'utf8');
36
+ return ['.yaml', '.yml'].includes(extname(path).toLowerCase()) ? parseYaml(source) : JSON.parse(source);
37
+ }
38
+ function diagnostic(code, message, entry, path) {
39
+ return { code, severity: 'error', message, ...(entry ? { artifactId: entry.artifact.id } : {}), ...(path ? { path } : {}) };
40
+ }
41
+ function load(ctx) {
42
+ const catalogPath = value(ctx.args, '--catalog');
43
+ const result = loadSchemaCatalog({ rootDir: ctx.cwd, ...(catalogPath ? { catalogPath } : {}) });
44
+ return {
45
+ ...(result.catalog ? { resolver: new SchemaResolver(result.catalog, { rootDir: ctx.cwd }) } : {}),
46
+ diagnostics: result.diagnostics,
47
+ };
48
+ }
49
+ function authorityPath(ctx, entry) {
50
+ const path = entry.artifact.authority.path;
51
+ if (!path)
52
+ throw new Error(`${entry.artifact.id} has no local canonical authority`);
53
+ return resolve(ctx.cwd, path);
54
+ }
55
+ function schemaRefs(node, refs = []) {
56
+ if (Array.isArray(node))
57
+ for (const child of node)
58
+ schemaRefs(child, refs);
59
+ else if (typeof node === 'object' && node !== null) {
60
+ for (const [key, child] of Object.entries(node)) {
61
+ if (key === '$ref' && typeof child === 'string')
62
+ refs.push(child);
63
+ else
64
+ schemaRefs(child, refs);
65
+ }
66
+ }
67
+ return refs;
68
+ }
69
+ function resolveBaseline(ctx, resolver, query) {
70
+ const entry = resolver.resolve(query);
71
+ return readData(entry ? authorityPath(ctx, entry) : resolve(ctx.cwd, query));
72
+ }
73
+ async function execute(ctx) {
74
+ const [action = 'list', ...terms] = positional(ctx.args);
75
+ if (!ACTIONS.includes(action))
76
+ return { exitCode: 1, message: `Unknown schema action '${action}'.` };
77
+ const loaded = load(ctx);
78
+ if (!loaded.resolver)
79
+ return jsonResult({ schema: 'aiwg.schema.diagnostics.v1', valid: false, diagnostics: loaded.diagnostics }, 1);
80
+ const resolver = loaded.resolver;
81
+ const query = terms[0];
82
+ if (action === 'list') {
83
+ const entries = resolver.list({ domain: value(ctx.args, '--domain'), lifecycle: value(ctx.args, '--lifecycle'), format: value(ctx.args, '--format') });
84
+ return jsonResult({ schema: 'aiwg.schema.list.v1', entries: entries.map(({ artifact, domain, digest: hash }) => ({ id: artifact.id, logicalName: artifact.logicalName, version: artifact.version, domain, format: artifact.format, lifecycle: artifact.lifecycle, digest: hash })) });
85
+ }
86
+ if (action === 'show') {
87
+ if (!query)
88
+ return { exitCode: 1, message: 'Usage: aiwg schema show <id|name@version|path>' };
89
+ const entry = resolver.require(query);
90
+ return jsonResult({ schema: 'aiwg.schema.show.v1', ...entry });
91
+ }
92
+ if (action === 'graph') {
93
+ const direction = value(ctx.args, '--direction');
94
+ if (direction && !['dependencies', 'dependents', 'both'].includes(direction))
95
+ throw new Error(`Invalid graph direction '${direction}'`);
96
+ return jsonResult({ schema: 'aiwg.schema.graph.v1', ...resolver.graph(query, { direction: direction }) });
97
+ }
98
+ if (action === 'policy') {
99
+ const effective = ctx.args.includes('--effective');
100
+ if (!effective || !query)
101
+ return { exitCode: 1, message: 'Usage: aiwg schema policy --effective <id>' };
102
+ return jsonResult({ schema: 'aiwg.schema.policy.v1', id: resolver.require(query).artifact.id, effective: resolver.policy(query) });
103
+ }
104
+ if (action === 'validate') {
105
+ const schemaQuery = value(ctx.args, '--schema');
106
+ const instance = schemaQuery ? query : terms[1];
107
+ const selected = schemaQuery ?? query;
108
+ if (!selected || !instance)
109
+ return { exitCode: 1, message: 'Usage: aiwg schema validate --schema <id> <instance>' };
110
+ const entry = resolver.require(selected);
111
+ if (entry.artifact.format !== 'json-schema')
112
+ throw new Error(`Validation currently requires a JSON Schema artifact; got ${entry.artifact.format}`);
113
+ const data = readData(resolve(ctx.cwd, instance));
114
+ const result = new SchemaValidator(resolver, { rootDir: ctx.cwd }).validate(entry.artifact.id, data);
115
+ return jsonResult({ schema: 'aiwg.schema.validation.v1', ...result, instance }, result.valid ? 0 : 1);
116
+ }
117
+ if (action === 'lint') {
118
+ const diagnostics = [...loaded.diagnostics];
119
+ const entries = resolver.list();
120
+ for (const entry of entries) {
121
+ if (entry.artifact.format !== 'json-schema')
122
+ continue;
123
+ try {
124
+ new SchemaValidator(resolver, { rootDir: ctx.cwd }).compile(entry.artifact.id);
125
+ }
126
+ catch (error) {
127
+ diagnostics.push(diagnostic('SCHEMA_COMPILE_FAILED', error instanceof Error ? error.message : String(error), entry, entry.artifact.authority.path));
128
+ }
129
+ }
130
+ const valid = !diagnostics.some(item => item.severity === 'error');
131
+ return jsonResult({ schema: 'aiwg.schema.diagnostics.v1', valid, diagnostics }, valid ? 0 : 1);
132
+ }
133
+ if (action === 'check-refs') {
134
+ const diagnostics = [];
135
+ for (const entry of resolver.list()) {
136
+ for (const dependency of entry.artifact.dependencies ?? [])
137
+ if (!resolver.resolve(dependency.id) && !dependency.optional)
138
+ diagnostics.push(diagnostic('SCHEMA_DEPENDENCY_UNRESOLVED', `Unresolved dependency ${dependency.id}`, entry));
139
+ if (entry.artifact.format !== 'json-schema' || !entry.artifact.authority.path)
140
+ continue;
141
+ for (const ref of schemaRefs(readData(authorityPath(ctx, entry)))) {
142
+ if (ref.startsWith('#'))
143
+ continue;
144
+ const base = ref.split('#')[0];
145
+ if (!base)
146
+ continue;
147
+ const local = resolve(dirname(authorityPath(ctx, entry)), base);
148
+ if (!resolver.resolve(base) && !resolver.resolve(ref) && !existsSync(local))
149
+ diagnostics.push(diagnostic('SCHEMA_REFERENCE_UNRESOLVED', `Unresolved $ref ${ref}`, entry, entry.artifact.authority.path));
150
+ }
151
+ }
152
+ return jsonResult({ schema: 'aiwg.schema.references.v1', valid: diagnostics.length === 0, diagnostics }, diagnostics.length ? 1 : 0);
153
+ }
154
+ if (action === 'diff' || action === 'compatibility') {
155
+ const against = value(ctx.args, '--against');
156
+ if (!query || !against)
157
+ return { exitCode: 1, message: `Usage: aiwg schema ${action} <id|path> --against <id|path>` };
158
+ const current = resolveBaseline(ctx, resolver, query);
159
+ const baseline = resolveBaseline(ctx, resolver, against);
160
+ const result = analyzeBackwardCompatibility(baseline, current);
161
+ return jsonResult({ schema: action === 'diff' ? 'aiwg.schema.diff.v1' : 'aiwg.schema.compatibility.v1', current: query, baseline: against, ...result }, result.status === 'breaking' ? 1 : 0);
162
+ }
163
+ if (action === 'verify-projections') {
164
+ const entries = query ? [resolver.require(query)] : resolver.list();
165
+ const diagnostics = [];
166
+ for (const entry of entries)
167
+ for (const projection of entry.artifact.projections ?? []) {
168
+ const path = resolve(ctx.cwd, projection.path);
169
+ if (!existsSync(path)) {
170
+ diagnostics.push(diagnostic('SCHEMA_PROJECTION_MISSING', `Projection does not exist: ${projection.path}`, entry, projection.path));
171
+ continue;
172
+ }
173
+ const actual = digest(readFileSync(path));
174
+ if (projection.digest && projection.digest !== actual)
175
+ diagnostics.push(diagnostic('SCHEMA_PROJECTION_DRIFT', `Projection digest does not match: ${projection.path}`, entry, projection.path));
176
+ if (projection.kind === 'mirror' && readFileSync(path).compare(readFileSync(authorityPath(ctx, entry))) !== 0)
177
+ diagnostics.push(diagnostic('SCHEMA_PROJECTION_DRIFT', `Mirror differs from canonical authority: ${projection.path}`, entry, projection.path));
178
+ }
179
+ return jsonResult({ schema: 'aiwg.schema.projections.v1', valid: diagnostics.length === 0, diagnostics }, diagnostics.length ? 1 : 0);
180
+ }
181
+ const entries = query ? [resolver.require(query)] : resolver.list();
182
+ const projections = entries.flatMap(entry => (entry.artifact.projections ?? []).map(projection => ({ artifactId: entry.artifact.id, source: entry.artifact.authority.path, ...projection })));
183
+ if (ctx.args.includes('--write'))
184
+ for (const item of projections) {
185
+ if (item.kind !== 'mirror' || !item.source)
186
+ continue;
187
+ const target = resolve(ctx.cwd, item.path);
188
+ mkdirSync(dirname(target), { recursive: true });
189
+ writeFileSync(target, readFileSync(resolve(ctx.cwd, item.source)));
190
+ }
191
+ return jsonResult({ schema: 'aiwg.schema.generation-plan.v1', write: ctx.args.includes('--write'), projections });
192
+ }
193
+ export const schemaHandler = {
194
+ id: 'schema',
195
+ name: 'Schema Control Plane',
196
+ description: 'Discover, validate, compare, and verify governed schema artifacts',
197
+ category: 'utility',
198
+ aliases: ['schemas'],
199
+ async help() {
200
+ return { exitCode: 0, rawOutput: true, message: [
201
+ 'Usage: aiwg schema <action> [options]',
202
+ '',
203
+ 'Discovery: list, show <id>, graph [id], policy --effective <id>',
204
+ 'Quality: lint, check-refs, validate --schema <id> <instance>',
205
+ 'Evolution: diff <id|path> --against <id|path>, compatibility <id|path> --against <id|path>',
206
+ 'Projections: generate [id] [--write], verify-projections [id]',
207
+ '',
208
+ 'Common options: --catalog <path>, --domain <id>, --lifecycle <state>, --format <format>',
209
+ 'All command results use stable versioned JSON envelopes.',
210
+ ].join('\n') };
211
+ },
212
+ async execute(ctx) {
213
+ try {
214
+ return await execute(ctx);
215
+ }
216
+ catch (error) {
217
+ return jsonResult({ schema: 'aiwg.schema.error.v1', error: error instanceof Error ? error.message : String(error) }, 1);
218
+ }
219
+ },
220
+ };
221
+ //# sourceMappingURL=schema.js.map