@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.
- package/agentic/code/providers/capability-matrix.yaml +41 -0
- package/bin/aiwg.mjs +15 -3
- package/dist/src/api/index.d.ts +3 -0
- package/dist/src/api/index.js +3 -0
- package/dist/src/channel/manager.mjs +2 -2
- package/dist/src/cli/handlers/dataset.js +186 -0
- package/dist/src/cli/handlers/help.js +2 -1
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/init.js +1 -0
- package/dist/src/cli/handlers/output-mode.js +18 -1
- package/dist/src/cli/handlers/run.js +11 -3
- package/dist/src/cli/handlers/schema.js +221 -0
- package/dist/src/cli/handlers/steward.js +11 -1
- package/dist/src/cli/handlers/use.js +46 -9
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/scope-resolver.js +22 -0
- package/dist/src/dataset/adapter-sdk.d.ts +41 -0
- package/dist/src/dataset/adapter-sdk.js +147 -0
- package/dist/src/dataset/adapter-types.d.ts +179 -0
- package/dist/src/dataset/adapter-types.js +2 -0
- package/dist/src/dataset/adapters.d.ts +104 -0
- package/dist/src/dataset/adapters.js +518 -0
- package/dist/src/dataset/conformance-types.d.ts +84 -0
- package/dist/src/dataset/conformance-types.js +3 -0
- package/dist/src/dataset/conformance.d.ts +13 -0
- package/dist/src/dataset/conformance.js +90 -0
- package/dist/src/dataset/contracts.d.ts +17 -0
- package/dist/src/dataset/contracts.js +236 -0
- package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
- package/dist/src/dataset/file-orchestration-repository.js +68 -0
- package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
- package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
- package/dist/src/dataset/index.d.ts +21 -0
- package/dist/src/dataset/index.js +21 -0
- package/dist/src/dataset/ledger-types.d.ts +141 -0
- package/dist/src/dataset/ledger-types.js +2 -0
- package/dist/src/dataset/ledger.d.ts +27 -0
- package/dist/src/dataset/ledger.js +100 -0
- package/dist/src/dataset/local-execution-backend.d.ts +10 -0
- package/dist/src/dataset/local-execution-backend.js +32 -0
- package/dist/src/dataset/orchestration-repository.d.ts +29 -0
- package/dist/src/dataset/orchestration-repository.js +34 -0
- package/dist/src/dataset/orchestration-service.d.ts +56 -0
- package/dist/src/dataset/orchestration-service.js +466 -0
- package/dist/src/dataset/orchestration-types.d.ts +83 -0
- package/dist/src/dataset/orchestration-types.js +2 -0
- package/dist/src/dataset/presentation.d.ts +3 -0
- package/dist/src/dataset/presentation.js +8 -0
- package/dist/src/dataset/projections.d.ts +42 -0
- package/dist/src/dataset/projections.js +192 -0
- package/dist/src/dataset/schema-governance.d.ts +71 -0
- package/dist/src/dataset/schema-governance.js +135 -0
- package/dist/src/dataset/standards-types.d.ts +66 -0
- package/dist/src/dataset/standards-types.js +10 -0
- package/dist/src/dataset/standards.d.ts +13 -0
- package/dist/src/dataset/standards.js +291 -0
- package/dist/src/dataset/types.d.ts +258 -0
- package/dist/src/dataset/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +27 -1
- package/dist/src/installation/manager.mjs +5 -1
- package/dist/src/output-modes/index.js +4 -0
- package/dist/src/output-modes/registry.js +68 -24
- package/dist/src/output-modes/runtime.js +10 -8
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +41 -0
- package/dist/src/providers/provider-definitions.js +72 -0
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/schema/catalog.js +234 -0
- package/dist/src/schema/compatibility.js +42 -0
- package/dist/src/schema/diagnostics.js +36 -0
- package/dist/src/schema/index.js +8 -0
- package/dist/src/schema/policy.js +58 -0
- package/dist/src/schema/resolver.js +76 -0
- package/dist/src/schema/types.js +2 -0
- package/dist/src/schema/validator.js +82 -0
- package/dist/src/storage/backends/fortemi.js +6 -0
- package/dist/src/storage/config.js +18 -4
- package/dist/src/storage/fortemi-qualification.js +106 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/types.js +1 -1
- package/package.json +4 -1
- package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
- package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
- package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
- package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
- package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
- package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
- package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
- package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
- package/schemas/dataset/run-ledger.v1.schema.json +39 -0
- package/schemas/dataset/source-adapter.v1.schema.json +112 -0
- package/tools/agents/deploy-agents.mjs +9 -3
- 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 {
|
|
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(
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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' };
|
|
@@ -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: [],
|
|
@@ -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
|