@aiwg/cli 2026.8.28 → 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 +46 -9
  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 +4 -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
@@ -1,23 +1,57 @@
1
1
  import { access, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
2
2
  import { constants } from 'node:fs';
3
- import { homedir, tmpdir } from 'node:os';
3
+ import { tmpdir } from 'node:os';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { dirname, extname, join, resolve } from 'node:path';
6
6
  import { parse, stringify } from 'yaml';
7
+ import { z } from 'zod';
8
+ import { resolveUserConfigDir } from '../config/user-config-dir.mjs';
9
+ const MODE_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*$/;
7
10
  const PROTECTED = ['code', 'commands', 'citations', 'quoted-text', 'identifiers', 'machine-readable-blocks'];
8
11
  const STAGE_ORDER = ['semantic', 'voice', 'controlled-language', 'structure', 'presentation'];
12
+ const modeIdSchema = z.string().regex(MODE_ID_PATTERN, 'must start with a lowercase letter or number and contain only lowercase letters, numbers, dots, or hyphens');
13
+ const uniqueModeIds = z.array(modeIdSchema).refine(values => new Set(values).size === values.length, 'must not contain duplicates');
14
+ const outputModeProfileSchema = z.object({
15
+ id: modeIdSchema,
16
+ version: z.string().min(1),
17
+ description: z.string().min(1),
18
+ kind: z.enum(['voice', 'controlled-language', 'structure', 'presentation']),
19
+ stage: z.enum(STAGE_ORDER),
20
+ order: z.number().int().optional(),
21
+ instructions: z.string(),
22
+ provenance: z.object({ source: z.string().min(1), license: z.string().min(1) }).strict(),
23
+ validation: z.object({
24
+ level: z.enum(['advisory', 'validated', 'conformance']),
25
+ hook: z.string().min(1).optional(),
26
+ standardVersion: z.string().min(1).optional(),
27
+ }).strict(),
28
+ compatible: uniqueModeIds.optional(),
29
+ conflicts: uniqueModeIds.optional(),
30
+ requires: uniqueModeIds.optional(),
31
+ supersedes: uniqueModeIds.optional(),
32
+ protectedContent: z.array(z.enum(PROTECTED)).refine(values => new Set(values).size === values.length, 'must not contain duplicates').optional(),
33
+ contextCost: z.number().int().nonnegative().optional(),
34
+ mergeStrategy: z.literal('weighted-voice').optional(),
35
+ }).strict().superRefine((profile, ctx) => {
36
+ if (profile.validation.level !== 'advisory' && !profile.validation.hook) {
37
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['validation', 'hook'], message: `${profile.validation.level} modes require a validator hook` });
38
+ }
39
+ if (profile.validation.level === 'conformance' && !profile.validation.standardVersion) {
40
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['validation', 'standardVersion'], message: 'conformance modes require a standard version' });
41
+ }
42
+ });
9
43
  const BUILTINS = [
10
44
  {
11
45
  id: 'unaltered', version: '1.0.0', description: 'No-op mode; preserves the provider output path unchanged.',
12
46
  kind: 'presentation', stage: 'presentation', order: -1000, instructions: '',
13
47
  provenance: { source: 'AIWG', license: 'MIT' }, validation: { level: 'advisory' }, contextCost: 0,
14
- protectedContent: PROTECTED,
48
+ protectedContent: [...PROTECTED],
15
49
  },
16
50
  {
17
51
  id: 'wittgenstein-inspired', version: '1.0.0', description: 'Concise, proposition-oriented stylistic profile; not impersonation or attribution.',
18
52
  kind: 'voice', stage: 'voice', order: 100, instructions: 'Prefer concise propositions, clarify terms in use, and expose category errors. Do not imitate or attribute text to Ludwig Wittgenstein.',
19
53
  provenance: { source: 'AIWG original style guidance', license: 'MIT' }, validation: { level: 'advisory' }, contextCost: 48,
20
- protectedContent: PROTECTED,
54
+ protectedContent: [...PROTECTED],
21
55
  },
22
56
  {
23
57
  id: 'asd-ste', version: '1.0.0', description: 'Operator-configured ASD Simplified Technical English adapter.',
@@ -25,13 +59,13 @@ const BUILTINS = [
25
59
  instructions: 'Apply only operator-supplied ASD-STE rules and approved terminology. Without licensed rules and a configured validator, describe output as advisory and never claim conformance.',
26
60
  provenance: { source: 'AIWG adapter; standard content supplied by operator', license: 'MIT adapter only' },
27
61
  validation: { level: 'advisory', standardVersion: 'operator-configured' }, contextCost: 64,
28
- protectedContent: PROTECTED,
62
+ protectedContent: [...PROTECTED],
29
63
  },
30
64
  ];
31
65
  function profileDirs(cwd) {
32
66
  return [
33
67
  { dir: join(cwd, '.aiwg', 'output-modes'), source: 'project' },
34
- { dir: join(homedir(), '.config', 'aiwg', 'output-modes'), source: 'user' },
68
+ { dir: join(resolveUserConfigDir(), 'output-modes'), source: 'user' },
35
69
  ];
36
70
  }
37
71
  async function readable(path) {
@@ -43,31 +77,33 @@ async function readable(path) {
43
77
  return false;
44
78
  }
45
79
  }
46
- function validateProfile(value, path) {
47
- if (!value || typeof value !== 'object')
48
- throw new Error(`Invalid output mode profile at ${path}: expected an object`);
49
- const p = value;
50
- for (const field of ['id', 'version', 'description', 'kind', 'stage', 'instructions', 'provenance', 'validation']) {
51
- if (p[field] === undefined)
52
- throw new Error(`Invalid output mode profile at ${path}: missing ${field}`);
80
+ export function validateOutputModeProfile(value, path = '<profile>') {
81
+ const result = outputModeProfileSchema.safeParse(value);
82
+ if (!result.success) {
83
+ const details = result.error.issues
84
+ .map(issue => `${issue.path.length ? issue.path.join('.') : 'profile'}: ${issue.message}`)
85
+ .join('; ');
86
+ throw new Error(`Invalid output mode profile at ${path}: ${details}`);
53
87
  }
54
- if (!['voice', 'controlled-language', 'structure', 'presentation'].includes(String(p.kind)))
55
- throw new Error(`Invalid output mode kind in ${path}: ${p.kind}`);
56
- if (!STAGE_ORDER.includes(String(p.stage)))
57
- throw new Error(`Invalid output mode stage in ${path}: ${p.stage}`);
58
- return p;
88
+ return result.data;
59
89
  }
60
90
  async function loadDirectory(dir, source) {
61
91
  if (!(await readable(dir)))
62
92
  return [];
63
93
  const result = [];
94
+ const seen = new Map();
64
95
  for (const name of (await readdir(dir)).sort()) {
65
96
  if (!['.yaml', '.yml', '.json'].includes(extname(name)))
66
97
  continue;
67
98
  const sourcePath = join(dir, name);
68
99
  const raw = await readFile(sourcePath, 'utf8');
69
100
  const value = extname(name) === '.json' ? JSON.parse(raw) : parse(raw);
70
- result.push({ ...validateProfile(value, sourcePath), source, sourcePath });
101
+ const profile = validateOutputModeProfile(value, sourcePath);
102
+ const previous = seen.get(profile.id);
103
+ if (previous)
104
+ throw new Error(`Duplicate output mode '${profile.id}' in ${previous} and ${sourcePath}. Keep one definition per scope.`);
105
+ seen.set(profile.id, sourcePath);
106
+ result.push({ ...profile, source, sourcePath });
71
107
  }
72
108
  return result;
73
109
  }
@@ -84,7 +120,7 @@ async function loadVoiceAdapters(frameworkRoot) {
84
120
  id, version: String(voice.version ?? '1.0.0'), description: String(voice.description ?? `Adapted voice profile: ${id}`),
85
121
  kind: 'voice', stage: 'voice', order: 100, instructions: `Apply the existing voice profile '${id}' through voice-apply.`,
86
122
  provenance: { source: sourcePath, license: String(voice.license ?? 'project license') },
87
- validation: { level: 'advisory' }, protectedContent: PROTECTED, contextCost: 32,
123
+ validation: { level: 'advisory' }, protectedContent: [...PROTECTED], contextCost: 32,
88
124
  mergeStrategy: 'weighted-voice', source: 'voice-adapter', sourcePath,
89
125
  });
90
126
  }
@@ -115,7 +151,10 @@ export async function readOutputModeState(cwd, scope) {
115
151
  if (!(await readable(path)))
116
152
  return { version: 1, modes: [] };
117
153
  const value = parse(await readFile(path, 'utf8'));
118
- return { version: 1, modes: Array.isArray(value.modes) ? value.modes.map(String) : [] };
154
+ if (!value || value.version !== 1 || !Array.isArray(value.modes) || value.modes.some(mode => typeof mode !== 'string' || !MODE_ID_PATTERN.test(mode))) {
155
+ throw new Error(`Invalid output mode state at ${path}: expected version 1 and an array of valid mode IDs.`);
156
+ }
157
+ return { version: 1, modes: [...new Set(value.modes)] };
119
158
  }
120
159
  export async function writeOutputModeState(cwd, scope, modes) {
121
160
  const path = statePath(cwd, scope);
@@ -123,11 +162,16 @@ export async function writeOutputModeState(cwd, scope, modes) {
123
162
  await writeFile(path, stringify({ version: 1, modes }), 'utf8');
124
163
  return path;
125
164
  }
126
- export async function resolveOutputModes(cwd, frameworkRoot, invocation = []) {
165
+ export async function resolveOutputModes(cwd, frameworkRoot, invocation = [], overrides = {}) {
127
166
  const registry = await loadOutputModeRegistry(cwd, frameworkRoot);
128
- const project = await readOutputModeState(cwd, 'project');
129
- const session = await readOutputModeState(cwd, 'session');
130
- const selected = [...project.modes.map(id => ({ id, scope: 'project' })), ...session.modes.map(id => ({ id, scope: 'session' })), ...invocation.map(id => ({ id, scope: 'invocation' }))];
167
+ const project = overrides.project ?? (await readOutputModeState(cwd, 'project')).modes;
168
+ const session = overrides.session ?? (await readOutputModeState(cwd, 'session')).modes;
169
+ for (const [scope, modes] of [['project', project], ['session', session], ['invocation', invocation]]) {
170
+ if (!Array.isArray(modes) || modes.some(id => typeof id !== 'string' || !MODE_ID_PATTERN.test(id))) {
171
+ throw new Error(`Invalid ${scope} output mode selection: expected valid mode IDs.`);
172
+ }
173
+ }
174
+ const selected = [...project.map(id => ({ id, scope: 'project' })), ...session.map(id => ({ id, scope: 'session' })), ...invocation.map(id => ({ id, scope: 'invocation' }))];
131
175
  const effective = new Map();
132
176
  const diagnostics = [];
133
177
  for (const item of selected) {
@@ -16,8 +16,11 @@ function protectedPattern(classes) {
16
16
  function protect(content, classes) {
17
17
  const literals = [];
18
18
  const pattern = protectedPattern(classes);
19
+ let tokenPrefix = '\uE000AIWG_OUTPUT_MODE_';
20
+ while (content.includes(tokenPrefix))
21
+ tokenPrefix += '_';
19
22
  const protectedContent = pattern ? content.replace(pattern, value => {
20
- const token = `\uE000${literals.length}\uE001`;
23
+ const token = `${tokenPrefix}${literals.length}\uE001`;
21
24
  literals.push({ token, value });
22
25
  return token;
23
26
  }) : content;
@@ -26,9 +29,10 @@ function protect(content, classes) {
26
29
  function restore(content, literals, mode) {
27
30
  let restored = content;
28
31
  for (const literal of literals) {
29
- if (!restored.includes(literal.token))
30
- throw new Error(`Output mode '${mode}' modified or removed a protected literal.`);
31
- restored = restored.replaceAll(literal.token, literal.value);
32
+ const occurrences = restored.split(literal.token).length - 1;
33
+ if (occurrences !== 1)
34
+ throw new Error(`Output mode '${mode}' modified, removed, or duplicated a protected literal.`);
35
+ restored = restored.replace(literal.token, literal.value);
32
36
  }
33
37
  return restored;
34
38
  }
@@ -39,9 +43,10 @@ export async function applyOutputModes(input, modes, options) {
39
43
  const diagnostics = [];
40
44
  const applied = [];
41
45
  for (const mode of modes) {
42
- const snapshot = content;
43
46
  const masked = protect(content, mode.protectedContent ?? []);
44
47
  const transformed = await options.transform(masked.content, mode);
48
+ if (typeof transformed !== 'string')
49
+ throw new Error(`Output mode '${mode.id}' transform returned a non-string result.`);
45
50
  content = restore(transformed, masked.literals, mode.id);
46
51
  if (mode.validation.level !== 'advisory') {
47
52
  if (!options.validate)
@@ -54,9 +59,6 @@ export async function applyOutputModes(input, modes, options) {
54
59
  return { content: input, diagnostics, applied, fallback: 'unaltered' };
55
60
  }
56
61
  }
57
- // A transform may only change semantic presentation, never return an absent result.
58
- if (typeof content !== 'string')
59
- content = snapshot;
60
62
  applied.push(mode.id);
61
63
  }
62
64
  return { content, diagnostics, applied, fallback: 'none' };
@@ -38,6 +38,7 @@ const SKILLS_ONLY_PROVIDERS = new Set([
38
38
  'cursor',
39
39
  'hermes',
40
40
  'openhuman',
41
+ 'pi',
41
42
  ]);
42
43
  /**
43
44
  * Check if a provider needs command files generated from skills.
@@ -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:
@@ -21,6 +21,7 @@ const ProviderDefinitionSchema = z.object({
21
21
  'opencode',
22
22
  'openclaw',
23
23
  'openhuman',
24
+ 'pi',
24
25
  'warp',
25
26
  'windsurf',
26
27
  'generic',
@@ -131,6 +132,7 @@ export const PROVIDER_IDS = [
131
132
  'opencode',
132
133
  'openclaw',
133
134
  'openhuman',
135
+ 'pi',
134
136
  'warp',
135
137
  'windsurf',
136
138
  'generic',
@@ -191,6 +193,17 @@ const CONTEXT_CONTRACTS = {
191
193
  bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: false, support: 'degraded',
192
194
  verification: { method: 'AIWG mixed-scope adapter contract; host loading remains capability-dependent', source: 'agentic/code/providers/capability-matrix.yaml', lastVerified: VERIFIED_ON },
193
195
  },
196
+ pi: {
197
+ startupFiles: ['AGENTS.override.md', 'AGENTS.md', 'CLAUDE.md'],
198
+ precedence: ['provider/system', 'root-to-cwd context chain', 'AGENTS.override.md supersedes same-directory AGENTS.md and CLAUDE.md'],
199
+ loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
200
+ bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: true, support: 'supported',
201
+ verification: {
202
+ method: 'Pi coding-agent resource loader and official README context-file documentation',
203
+ source: 'https://github.com/earendil-works/pi/blob/79680533c6b898894f2d2421c7f640b212d3dfdd/packages/coding-agent/README.md#context-files',
204
+ lastVerified: '2026-09-03',
205
+ },
206
+ },
194
207
  warp: {
195
208
  startupFiles: ['WARP.md', 'AGENTS.md'], precedence: ['provider/system', 'subdirectory rule', 'root rule', 'global rule'],
196
209
  loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
@@ -656,6 +669,65 @@ const BUILT_IN_SEEDS = [
656
669
  },
657
670
  matrixRef: 'openhuman',
658
671
  },
672
+ {
673
+ id: 'pi',
674
+ aliases: ['pi-coding-agent'],
675
+ builtIn: true,
676
+ surfaces: {
677
+ primary: 'pi',
678
+ compatibility: ['pi-coding-agent'],
679
+ precedence: ['AGENTS.override.md', 'AGENTS.md', '.agents/skills/', '.pi/skills/', '.pi/prompts/'],
680
+ related: [],
681
+ },
682
+ detection: {
683
+ // PI_CODING_AGENT_DIR only relocates configuration; it is deliberately
684
+ // not an active-runtime marker. Availability is proven by process or executable.
685
+ env: [],
686
+ process: ['pi'],
687
+ capabilityId: 'pi',
688
+ },
689
+ paths: {
690
+ artifacts: {
691
+ agents: '.agents/skills',
692
+ commands: '.pi/prompts',
693
+ skills: '.pi/.aiwg/skills',
694
+ rules: null,
695
+ behaviors: '.pi/extensions',
696
+ },
697
+ kernelSkills: '.agents/skills',
698
+ contextDiscovery: {
699
+ agents: '.agents/skills',
700
+ skills: '.agents/skills',
701
+ rules: null,
702
+ behaviors: '.pi/extensions',
703
+ },
704
+ configFile: 'AGENTS.md',
705
+ contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
706
+ },
707
+ smithPaths: {
708
+ agents: '.agents/skills',
709
+ commands: '.pi/prompts',
710
+ skills: '.pi/skills',
711
+ rules: null,
712
+ fileExtension: '.md',
713
+ configFile: 'AGENTS.md',
714
+ aggregated: false,
715
+ },
716
+ skillNamespace: {
717
+ deploymentGroup: 'deep-recursion',
718
+ pathType: 'project',
719
+ skillsBaseDir: '.pi/skills',
720
+ subdirLayout: true,
721
+ },
722
+ adapters: {
723
+ agentFormat: 'agents-md',
724
+ hookBridge: null,
725
+ mcpInjection: null,
726
+ contextAggregation: 'agents-md',
727
+ ruleFormat: 'agents-md-section',
728
+ },
729
+ matrixRef: 'pi',
730
+ },
659
731
  {
660
732
  id: 'warp',
661
733
  aliases: [],
@@ -14,6 +14,7 @@ const PROVIDER_EXECUTABLES = {
14
14
  opencode: ['opencode'],
15
15
  openclaw: ['openclaw'],
16
16
  openhuman: ['openhuman'],
17
+ pi: ['pi'],
17
18
  warp: ['warp'],
18
19
  windsurf: ['windsurf'],
19
20
  };
@@ -0,0 +1,234 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
4
+ import { createDiagnostic, hasSchemaErrors, SCHEMA_DIAGNOSTIC_CODES } from './diagnostics.js';
5
+ import { diagnosePolicy, resolveEffectivePolicy } from './policy.js';
6
+ function parseJson(path) {
7
+ return JSON.parse(readFileSync(path, 'utf8'));
8
+ }
9
+ function isRecord(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+ function isDomainManifest(value) {
13
+ return (isRecord(value) &&
14
+ value.schemaVersion === '1' &&
15
+ typeof value.domain === 'string' &&
16
+ isRecord(value.owner) &&
17
+ typeof value.owner.id === 'string' &&
18
+ Array.isArray(value.artifacts));
19
+ }
20
+ function isCatalogSource(value) {
21
+ return isRecord(value) && value.schemaVersion === '1' && Array.isArray(value.domains);
22
+ }
23
+ function safePath(rootDir, path) {
24
+ const absolute = resolve(rootDir, path);
25
+ const rel = relative(resolve(rootDir), absolute);
26
+ return rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel)) ? absolute : undefined;
27
+ }
28
+ function sha256(path) {
29
+ return `sha256:${createHash('sha256').update(readFileSync(path)).digest('hex')}`;
30
+ }
31
+ function validateArtifactShape(artifact, domain) {
32
+ const diagnostics = [];
33
+ const required = [
34
+ 'logicalName',
35
+ 'id',
36
+ 'version',
37
+ 'format',
38
+ 'lifecycle',
39
+ 'owner',
40
+ 'authority',
41
+ ];
42
+ for (const field of required) {
43
+ if (artifact[field] === undefined || artifact[field] === '') {
44
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, `Artifact in domain ${domain} is missing required field ${field}`, { artifactId: artifact.id }));
45
+ }
46
+ }
47
+ if (artifact.format === 'json-schema' && !artifact.dialect) {
48
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, 'JSON Schema artifacts require a dialect', {
49
+ artifactId: artifact.id,
50
+ }));
51
+ }
52
+ if (artifact.lifecycle === 'deprecated' && artifact.deprecation === undefined) {
53
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, 'Deprecated artifacts require deprecation data', {
54
+ artifactId: artifact.id,
55
+ }));
56
+ }
57
+ return diagnostics;
58
+ }
59
+ function walkSchemaFiles(root) {
60
+ if (!existsSync(root))
61
+ return [];
62
+ const result = [];
63
+ for (const entry of readdirSync(root).sort()) {
64
+ const path = resolve(root, entry);
65
+ const stat = statSync(path);
66
+ if (stat.isDirectory())
67
+ result.push(...walkSchemaFiles(path));
68
+ else if (/\.schema\.json$/u.test(entry))
69
+ result.push(path);
70
+ }
71
+ return result;
72
+ }
73
+ function readSchemaId(path) {
74
+ try {
75
+ const value = parseJson(path);
76
+ return isRecord(value) && typeof value.$id === 'string' ? value.$id : undefined;
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ export function compileSchemaCatalog(source, manifests, options) {
83
+ const rootDir = resolve(options.rootDir);
84
+ const diagnostics = [];
85
+ const entries = [];
86
+ const ids = new Map();
87
+ const names = new Map();
88
+ const authorities = new Map();
89
+ const declaredPaths = new Map();
90
+ for (const manifest of [...manifests].sort((a, b) => a.domain.localeCompare(b.domain))) {
91
+ for (const artifact of [...manifest.artifacts].sort((a, b) => `${a.logicalName}\0${a.version}\0${a.id}`.localeCompare(`${b.logicalName}\0${b.version}\0${b.id}`))) {
92
+ diagnostics.push(...validateArtifactShape(artifact, manifest.domain));
93
+ const nameVersion = `${artifact.logicalName}@${artifact.version}`;
94
+ if (ids.has(artifact.id)) {
95
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.duplicateId, `Duplicate schema id ${artifact.id}`, { artifactId: artifact.id, details: { firstDomain: ids.get(artifact.id), secondDomain: manifest.domain } }));
96
+ }
97
+ else
98
+ ids.set(artifact.id, manifest.domain);
99
+ if (names.has(nameVersion)) {
100
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.duplicateNameVersion, `Duplicate schema name and version ${nameVersion}`, { artifactId: artifact.id }));
101
+ }
102
+ else
103
+ names.set(nameVersion, artifact.id);
104
+ let digest = artifact.digest;
105
+ if (artifact.authority.kind === 'canonical') {
106
+ const canonicalPath = artifact.authority.path;
107
+ if (!canonicalPath) {
108
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, 'Canonical authority requires a path', { artifactId: artifact.id }));
109
+ }
110
+ else {
111
+ const absolute = safePath(rootDir, canonicalPath);
112
+ if (!absolute)
113
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.pathOutsideRoot, `Authority path escapes repository root: ${canonicalPath}`, { artifactId: artifact.id, path: canonicalPath }));
114
+ else if (!existsSync(absolute))
115
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.missingPath, `Authority path does not exist: ${canonicalPath}`, { artifactId: artifact.id, path: canonicalPath }));
116
+ else {
117
+ const normalized = relative(rootDir, absolute).split(sep).join('/');
118
+ const previous = authorities.get(normalized);
119
+ if (previous)
120
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.duplicateAuthority, `Canonical path ${normalized} is claimed by ${previous} and ${artifact.id}`, { artifactId: artifact.id, path: normalized }));
121
+ else
122
+ authorities.set(normalized, artifact.id);
123
+ declaredPaths.set(normalized, { id: artifact.id, projection: false });
124
+ const actual = sha256(absolute);
125
+ if (digest && digest !== actual)
126
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.digestMismatch, `Digest mismatch for ${normalized}`, { artifactId: artifact.id, path: normalized, details: { declared: digest, actual } }));
127
+ digest = actual;
128
+ }
129
+ }
130
+ }
131
+ for (const projection of artifact.projections ?? []) {
132
+ const absolute = safePath(rootDir, projection.path);
133
+ if (!absolute)
134
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.pathOutsideRoot, `Projection path escapes repository root: ${projection.path}`, { artifactId: artifact.id, path: projection.path }));
135
+ else if (!existsSync(absolute))
136
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.missingPath, `Projection path does not exist: ${projection.path}`, { artifactId: artifact.id, path: projection.path }));
137
+ else {
138
+ const normalized = relative(rootDir, absolute).split(sep).join('/');
139
+ declaredPaths.set(normalized, { id: artifact.id, projection: true });
140
+ if (projection.digest && projection.digest !== sha256(absolute))
141
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.digestMismatch, `Projection digest mismatch for ${normalized}`, { artifactId: artifact.id, path: normalized }));
142
+ }
143
+ }
144
+ const effectivePolicy = resolveEffectivePolicy(source.policy, manifest, artifact);
145
+ diagnostics.push(...diagnosePolicy(effectivePolicy, artifact.id, options.now));
146
+ if (effectivePolicy.requireDigest && artifact.digest === undefined) {
147
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, 'Effective policy requires a declared digest', { artifactId: artifact.id }));
148
+ }
149
+ entries.push({ artifact, domain: manifest.domain, ...(digest === undefined ? {} : { digest }), effectivePolicy });
150
+ }
151
+ }
152
+ for (const entry of entries) {
153
+ for (const dependency of entry.artifact.dependencies ?? []) {
154
+ if (!ids.has(dependency.id))
155
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.unresolvedDependency, `Unresolved dependency ${dependency.id}`, { artifactId: entry.artifact.id, details: { dependency: dependency.id } }));
156
+ }
157
+ }
158
+ for (const inventoryRoot of options.inventoryRoots ?? []) {
159
+ const absoluteRoot = safePath(rootDir, inventoryRoot);
160
+ if (!absoluteRoot) {
161
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.pathOutsideRoot, `Inventory root escapes repository root: ${inventoryRoot}`, { path: inventoryRoot }));
162
+ continue;
163
+ }
164
+ for (const file of walkSchemaFiles(absoluteRoot)) {
165
+ const normalized = relative(rootDir, file).split(sep).join('/');
166
+ if (!declaredPaths.has(normalized)) {
167
+ const schemaId = readSchemaId(file);
168
+ const canonical = schemaId === undefined ? undefined : entries.find((entry) => entry.artifact.id === schemaId);
169
+ diagnostics.push(createDiagnostic(canonical ? SCHEMA_DIAGNOSTIC_CODES.undeclaredMirror : SCHEMA_DIAGNOSTIC_CODES.undeclaredCanonical, canonical
170
+ ? `Schema file shares ${schemaId} but is not declared as a mirror: ${normalized}`
171
+ : `Schema file is not registered: ${normalized}`, { path: normalized, ...(schemaId === undefined ? {} : { details: { schemaId } }) }));
172
+ }
173
+ }
174
+ }
175
+ entries.sort((a, b) => `${a.artifact.id}\0${a.artifact.version}`.localeCompare(`${b.artifact.id}\0${b.artifact.version}`));
176
+ const catalog = { schemaVersion: '1', entries, domains: [...new Set(manifests.map((manifest) => manifest.domain))].sort() };
177
+ return { catalog, diagnostics, valid: !hasSchemaErrors(diagnostics) };
178
+ }
179
+ export function loadSchemaCatalog(options) {
180
+ const rootDir = resolve(options.rootDir);
181
+ const catalogPath = resolve(rootDir, options.catalogPath ?? 'schemas/catalog/catalog.json');
182
+ const diagnostics = [];
183
+ if (!existsSync(catalogPath)) {
184
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.missingPath, `Catalog does not exist: ${relative(rootDir, catalogPath)}`, { path: relative(rootDir, catalogPath) }));
185
+ return { diagnostics, valid: false };
186
+ }
187
+ let source;
188
+ try {
189
+ source = parseJson(catalogPath);
190
+ }
191
+ catch (error) {
192
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidCatalog, `Cannot parse catalog: ${error instanceof Error ? error.message : String(error)}`, { path: relative(rootDir, catalogPath) }));
193
+ return { diagnostics, valid: false };
194
+ }
195
+ if (!isCatalogSource(source)) {
196
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidCatalog, 'Catalog must use schemaVersion 1 and contain a domains array', { path: relative(rootDir, catalogPath) }));
197
+ return { diagnostics, valid: false };
198
+ }
199
+ const manifests = [];
200
+ for (const item of source.domains) {
201
+ let candidate = item;
202
+ let path;
203
+ if (typeof item === 'string') {
204
+ path = resolve(dirname(catalogPath), item);
205
+ if (!safePath(rootDir, relative(rootDir, path))) {
206
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.pathOutsideRoot, `Domain manifest escapes repository root: ${item}`, { path: item }));
207
+ continue;
208
+ }
209
+ if (!existsSync(path)) {
210
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.missingPath, `Domain manifest does not exist: ${item}`, { path: item }));
211
+ continue;
212
+ }
213
+ try {
214
+ candidate = parseJson(path);
215
+ }
216
+ catch (error) {
217
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, `Cannot parse domain manifest ${item}: ${error instanceof Error ? error.message : String(error)}`, { path: item }));
218
+ continue;
219
+ }
220
+ }
221
+ if (!isDomainManifest(candidate)) {
222
+ diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.invalidManifest, 'Domain manifest must use schemaVersion 1 and contain domain, owner, and artifacts', path ? { path: relative(rootDir, path) } : {}));
223
+ continue;
224
+ }
225
+ manifests.push(candidate);
226
+ }
227
+ const result = compileSchemaCatalog(source, manifests, options);
228
+ return { catalog: result.catalog, diagnostics: [...diagnostics, ...result.diagnostics], valid: diagnostics.length === 0 && result.valid };
229
+ }
230
+ /** Serialize compiler output without timestamps or filesystem-specific data. */
231
+ export function serializeCompiledSchemaCatalog(catalog) {
232
+ return `${JSON.stringify(catalog, null, 2)}\n`;
233
+ }
234
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1,42 @@
1
+ function record(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : undefined;
3
+ }
4
+ function strings(value) {
5
+ return new Set(Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []);
6
+ }
7
+ /** Conservative backward-compatibility analysis: unknown means human review, never an optimistic pass. */
8
+ export function analyzeBackwardCompatibility(before, after) {
9
+ const left = record(before);
10
+ const right = record(after);
11
+ if (!left || !right)
12
+ return { status: 'unknown', direction: 'backward', reasons: ['schemas are not JSON objects'] };
13
+ if (JSON.stringify(left) === JSON.stringify(right))
14
+ return { status: 'compatible', direction: 'backward', reasons: ['schemas are identical'] };
15
+ const reasons = [];
16
+ if (left.type !== undefined && right.type !== undefined && JSON.stringify(left.type) !== JSON.stringify(right.type))
17
+ reasons.push('accepted root type changed');
18
+ const oldRequired = strings(left.required);
19
+ for (const field of strings(right.required))
20
+ if (!oldRequired.has(field))
21
+ reasons.push(`required property added: ${field}`);
22
+ const oldProperties = record(left.properties) ?? {};
23
+ const newProperties = record(right.properties) ?? {};
24
+ for (const field of Object.keys(oldProperties))
25
+ if (!(field in newProperties))
26
+ reasons.push(`property removed: ${field}`);
27
+ if (left.additionalProperties !== false && right.additionalProperties === false)
28
+ reasons.push('additional properties changed from allowed to forbidden');
29
+ const oldEnum = strings(left.enum);
30
+ const newEnum = strings(right.enum);
31
+ if (oldEnum.size && newEnum.size)
32
+ for (const member of oldEnum)
33
+ if (!newEnum.has(member))
34
+ reasons.push(`enum member removed: ${member}`);
35
+ return reasons.length
36
+ ? { status: 'breaking', direction: 'backward', reasons }
37
+ : { status: 'unknown', direction: 'backward', reasons: ['no conservative breaking rule matched; review is required'] };
38
+ }
39
+ export function analyzeCompatibilityChain(schemas) {
40
+ return schemas.slice(1).map((schema, index) => analyzeBackwardCompatibility(schemas[index], schema));
41
+ }
42
+ //# sourceMappingURL=compatibility.js.map