@aiwg/cli 2026.7.20 → 2026.7.21
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/README.md +4 -4
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +7 -0
- package/dist/src/artifacts/citation-parser.js +96 -35
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/state-transfer.js +27 -0
- package/dist/src/artifacts/stats.js +8 -0
- package/dist/src/cli/cli-extension-loader.js +73 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/sessions.js +966 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/use.js +143 -60
- package/dist/src/cli/handlers/utilities.js +22 -8
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/extensions/commands/definitions.js +29 -0
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/sessions/adapters/claude.js +357 -0
- package/dist/src/sessions/adapters/codex.js +521 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +372 -0
- package/dist/src/sessions/adapters/factory.js +345 -0
- package/dist/src/sessions/adapters/generic.js +225 -0
- package/dist/src/sessions/adapters/hermes.js +341 -0
- package/dist/src/sessions/adapters/openclaw.js +381 -0
- package/dist/src/sessions/adapters/opencode.js +454 -0
- package/dist/src/sessions/adapters/openhuman.js +315 -0
- package/dist/src/sessions/adapters/warp.js +160 -0
- package/dist/src/sessions/adapters/windsurf.js +212 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +310 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/importer.js +315 -0
- package/dist/src/sessions/index.js +25 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/policy.js +192 -0
- package/dist/src/sessions/ports.js +2 -0
- package/dist/src/sessions/promotion.js +367 -0
- package/dist/src/sessions/readers.js +176 -0
- package/dist/src/sessions/repository.js +1551 -0
- package/dist/src/skills/adapters/agent-skills.js +59 -0
- package/dist/src/skills/adapters/local.js +19 -1
- package/dist/src/skills/agent-skills.js +249 -0
- package/dist/src/skills/cli.js +463 -7
- package/dist/src/skills/deployer.js +554 -0
- package/dist/src/skills/doctor.js +105 -0
- package/dist/src/skills/exporter.js +382 -0
- package/dist/src/skills/importer.js +921 -0
- package/dist/src/skills/registry.js +19 -0
- package/dist/src/skills/validator.js +323 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -247,7 +247,7 @@ rather than merely printed.
|
|
|
247
247
|
Agents do not need to memorize the remaining command surface. AIWG discovery
|
|
248
248
|
finds the relevant skill, and the skill supplies the right CLI step. Operators
|
|
249
249
|
who need the complete syntax and examples can use the
|
|
250
|
-
[AIWG CLI reference](https://
|
|
250
|
+
[AIWG CLI reference](https://github.com/jmagly/aiwg/blob/main/docs/agents/cli-reference.md).
|
|
251
251
|
|
|
252
252
|
## How It Works
|
|
253
253
|
|
|
@@ -438,7 +438,7 @@ This README therefore documents the operating model, package boundary, trust
|
|
|
438
438
|
model, and troubleshooting path instead of duplicating every command and flag.
|
|
439
439
|
The complete operator reference is maintained at:
|
|
440
440
|
|
|
441
|
-
**[AIWG CLI Reference — every command and example](https://
|
|
441
|
+
**[AIWG CLI Reference — every command and example](https://github.com/jmagly/aiwg/blob/main/docs/agents/cli-reference.md)**
|
|
442
442
|
|
|
443
443
|
Keeping the command catalog in one canonical location prevents package
|
|
444
444
|
documentation from drifting as the runtime grows.
|
|
@@ -492,7 +492,7 @@ security guidance.
|
|
|
492
492
|
Do not routinely call `aiwg help` and place the full output in the model
|
|
493
493
|
context. If a skill exists, use it. If a maintainer or operator needs an
|
|
494
494
|
unfamiliar command, link to the canonical
|
|
495
|
-
[CLI reference](https://
|
|
495
|
+
[CLI reference](https://github.com/jmagly/aiwg/blob/main/docs/agents/cli-reference.md) or retrieve only
|
|
496
496
|
the relevant section.
|
|
497
497
|
|
|
498
498
|
## Using AIWG from a Web-Connected Chat
|
|
@@ -1020,7 +1020,7 @@ Package invariants include:
|
|
|
1020
1020
|
|
|
1021
1021
|
## Documentation
|
|
1022
1022
|
|
|
1023
|
-
- [Complete AIWG CLI reference](https://
|
|
1023
|
+
- [Complete AIWG CLI reference](https://github.com/jmagly/aiwg/blob/main/docs/agents/cli-reference.md)
|
|
1024
1024
|
- [AIWG documentation](https://docs.aiwg.io/)
|
|
1025
1025
|
- [AIWG project README](https://github.com/jmagly/aiwg#readme)
|
|
1026
1026
|
- [Web-backed resources guide](https://github.com/jmagly/aiwg/blob/main/docs/install/web-backed-resources.md)
|
package/dist/src/api/index.d.ts
CHANGED
package/dist/src/api/index.js
CHANGED
|
@@ -508,6 +508,13 @@ function recordForEntry(cwd, entry, graphName, dependencyGraph, privacy, schemaV
|
|
|
508
508
|
...(entry.operationalState
|
|
509
509
|
? { operational_state: entry.operationalState }
|
|
510
510
|
: {}),
|
|
511
|
+
...(entry.stateTransfer
|
|
512
|
+
? {
|
|
513
|
+
state_transfer: {
|
|
514
|
+
deleted_at: entry.stateTransfer.deletedAt,
|
|
515
|
+
},
|
|
516
|
+
}
|
|
517
|
+
: {}),
|
|
511
518
|
}
|
|
512
519
|
: {}),
|
|
513
520
|
updated_at: entry.updated,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - **Incoming**: corpus papers that cite this work (column: "REF") → `cited-by` edges
|
|
9
9
|
*
|
|
10
10
|
* Supported node-id forms (#105):
|
|
11
|
-
* - `REF-\d
|
|
11
|
+
* - `REF-\d+[a-z]?` research-paper IDs (REF-001, REF-434a, ...)
|
|
12
12
|
* - `PROF-[POFG]-[a-z0-9-]+` entity-profile IDs:
|
|
13
13
|
* - `PROF-P-*` people, `PROF-O-*` orgs, `PROF-F-*` funders, `PROF-G-*` groups
|
|
14
14
|
*
|
|
@@ -26,17 +26,43 @@ import { parseFrontmatter } from './index-builder.js';
|
|
|
26
26
|
* Match a single node identifier (REF-* or PROF-*) anywhere in a string.
|
|
27
27
|
* Used by `extractRefsFromTable` to pull every ID out of a table cell.
|
|
28
28
|
*/
|
|
29
|
-
const NODE_ID_PATTERN = /(?:REF-\d
|
|
29
|
+
const NODE_ID_PATTERN = /(?:REF-\d+[a-z]?|PROF-[POFG]-[a-z0-9-]+)/g;
|
|
30
30
|
/**
|
|
31
31
|
* Validate that a string is a complete node identifier.
|
|
32
32
|
* Used by `parseCitationSidecar` and `buildRefToPathMap` to gate
|
|
33
33
|
* frontmatter `ref` values.
|
|
34
34
|
*/
|
|
35
|
-
const NODE_ID_FULL = /^(?:REF-\d
|
|
35
|
+
const NODE_ID_FULL = /^(?:REF-\d+[a-z]?|PROF-[POFG]-[a-z0-9-]+)$/;
|
|
36
|
+
/** Explicit column aliases used by historical citation-sidecar variants. */
|
|
37
|
+
const CITATION_REF_COLUMNS = [
|
|
38
|
+
'Inducted REF',
|
|
39
|
+
'REF',
|
|
40
|
+
'Corpus REF',
|
|
41
|
+
'In-corpus REF',
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Preserve the corpus snapshot's legacy edge semantics: every node ID on a
|
|
45
|
+
* pipe-delimited line inside an outgoing/incoming section is a declaration.
|
|
46
|
+
* Explicit column parsing remains the primary path, while this compatibility
|
|
47
|
+
* scan covers malformed rows and prose continuations that historical corpus
|
|
48
|
+
* snapshots already count.
|
|
49
|
+
*/
|
|
50
|
+
function extractSectionNodeIds(section) {
|
|
51
|
+
const refs = [];
|
|
52
|
+
for (const line of section.split('\n')) {
|
|
53
|
+
if (!line.includes('|'))
|
|
54
|
+
continue;
|
|
55
|
+
NODE_ID_PATTERN.lastIndex = 0;
|
|
56
|
+
const matches = line.match(NODE_ID_PATTERN);
|
|
57
|
+
if (matches)
|
|
58
|
+
refs.push(...matches);
|
|
59
|
+
}
|
|
60
|
+
return [...new Set(refs)];
|
|
61
|
+
}
|
|
36
62
|
/**
|
|
37
63
|
* Test whether a string is a valid sidecar node identifier.
|
|
38
64
|
*
|
|
39
|
-
* Accepts `REF-\d
|
|
65
|
+
* Accepts `REF-\d+[a-z]?` and `PROF-[POFG]-[a-z0-9-]+`. Returns false for any
|
|
40
66
|
* other input (including unrelated `PROF-` prefixed strings that don't
|
|
41
67
|
* match the four-letter type code form).
|
|
42
68
|
*/
|
|
@@ -54,34 +80,71 @@ export function isNodeId(value) {
|
|
|
54
80
|
* @returns Array of node identifiers found
|
|
55
81
|
*/
|
|
56
82
|
export function extractRefsFromTable(tableText, columnName) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
const aliases = (Array.isArray(columnName) ? columnName : [columnName])
|
|
84
|
+
.map(name => name.trim().toLowerCase());
|
|
85
|
+
const tableBlocks = [];
|
|
86
|
+
let currentBlock = [];
|
|
87
|
+
for (const line of tableText.split('\n')) {
|
|
88
|
+
if (line.trim().startsWith('|')) {
|
|
89
|
+
currentBlock.push(line);
|
|
90
|
+
}
|
|
91
|
+
else if (currentBlock.length > 0) {
|
|
92
|
+
tableBlocks.push(currentBlock);
|
|
93
|
+
currentBlock = [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (currentBlock.length > 0)
|
|
97
|
+
tableBlocks.push(currentBlock);
|
|
98
|
+
const parseRow = (line) => {
|
|
99
|
+
let row = line.trim();
|
|
100
|
+
if (row.startsWith('|'))
|
|
101
|
+
row = row.slice(1);
|
|
102
|
+
if (row.endsWith('|'))
|
|
103
|
+
row = row.slice(0, -1);
|
|
104
|
+
return row.split('|').map(cell => cell.trim());
|
|
105
|
+
};
|
|
66
106
|
const refs = [];
|
|
67
|
-
for (
|
|
68
|
-
const
|
|
69
|
-
|
|
107
|
+
for (const lines of tableBlocks) {
|
|
108
|
+
const headerCells = parseRow(lines[0]);
|
|
109
|
+
const colIndex = headerCells.findIndex(header => {
|
|
110
|
+
const normalized = header.toLowerCase().replace(/[`*_]/g, '').trim();
|
|
111
|
+
return aliases.some(alias => normalized === alias ||
|
|
112
|
+
normalized.startsWith(`${alias} /`) ||
|
|
113
|
+
normalized.startsWith(`${alias} (`));
|
|
114
|
+
});
|
|
115
|
+
if (lines.length < 3 || colIndex === -1) {
|
|
116
|
+
// Legacy sidecars sometimes have headerless continuation rows or a
|
|
117
|
+
// generic table whose rows were extended with a final REF cell. The
|
|
118
|
+
// corpus snapshot has always treated every in-section table REF as an
|
|
119
|
+
// outgoing/incoming declaration, so retain that compatibility fallback.
|
|
120
|
+
const dataLines = lines.length >= 3 ? lines.slice(2) : lines;
|
|
121
|
+
for (const line of dataLines) {
|
|
122
|
+
NODE_ID_PATTERN.lastIndex = 0;
|
|
123
|
+
const refMatches = line.match(NODE_ID_PATTERN);
|
|
124
|
+
if (refMatches)
|
|
125
|
+
refs.push(...refMatches);
|
|
126
|
+
}
|
|
70
127
|
continue;
|
|
71
|
-
|
|
72
|
-
// Skip
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
128
|
+
}
|
|
129
|
+
// Skip this table's header and separator, preserving all interior cells.
|
|
130
|
+
for (let i = 2; i < lines.length; i++) {
|
|
131
|
+
const cells = parseRow(lines[i]);
|
|
132
|
+
if (colIndex >= cells.length)
|
|
133
|
+
continue;
|
|
134
|
+
// Historical rows may expand the named REF cell into an issue/REF pair
|
|
135
|
+
// or append placeholder cells without extending the header. Scan from
|
|
136
|
+
// the named column through the remainder of the row; columns before the
|
|
137
|
+
// alias (title/authors/year) remain excluded.
|
|
138
|
+
const value = cells.slice(colIndex).join(' | ');
|
|
139
|
+
if (!value || value === '—' || value === '-' || value === '–')
|
|
140
|
+
continue;
|
|
141
|
+
NODE_ID_PATTERN.lastIndex = 0;
|
|
142
|
+
const refMatches = value.match(NODE_ID_PATTERN);
|
|
143
|
+
if (refMatches)
|
|
144
|
+
refs.push(...refMatches);
|
|
82
145
|
}
|
|
83
146
|
}
|
|
84
|
-
return refs;
|
|
147
|
+
return [...new Set(refs)];
|
|
85
148
|
}
|
|
86
149
|
/**
|
|
87
150
|
* Parse a citation sidecar markdown file into structured edges.
|
|
@@ -102,17 +165,15 @@ export function parseCitationSidecar(content) {
|
|
|
102
165
|
for (const section of sections) {
|
|
103
166
|
const sectionLower = section.toLowerCase();
|
|
104
167
|
if (sectionLower.startsWith('outgoing')) {
|
|
105
|
-
|
|
106
|
-
cites
|
|
168
|
+
cites.push(...extractRefsFromTable(section, CITATION_REF_COLUMNS));
|
|
169
|
+
cites.push(...extractSectionNodeIds(section));
|
|
107
170
|
}
|
|
108
171
|
else if (sectionLower.startsWith('incoming')) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Look for tables anywhere in this section
|
|
112
|
-
citedBy = extractRefsFromTable(section, 'REF');
|
|
172
|
+
citedBy.push(...extractRefsFromTable(section, CITATION_REF_COLUMNS));
|
|
173
|
+
citedBy.push(...extractSectionNodeIds(section));
|
|
113
174
|
}
|
|
114
175
|
}
|
|
115
|
-
return { ref, cites, citedBy };
|
|
176
|
+
return { ref, cites: [...new Set(cites)], citedBy: [...new Set(citedBy)] };
|
|
116
177
|
}
|
|
117
178
|
/**
|
|
118
179
|
* Convert a CitationParseResult into TypedEdge arrays for the dependency graph.
|
|
@@ -20,6 +20,7 @@ import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-
|
|
|
20
20
|
import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
|
|
21
21
|
import { normalizeOperationalState } from './operational-state.js';
|
|
22
22
|
import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
|
|
23
|
+
import { normalizeStateTransferProjection } from './state-transfer.js';
|
|
23
24
|
function pathContains(parent, child) {
|
|
24
25
|
const relative = path.relative(parent, child);
|
|
25
26
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
@@ -322,10 +323,10 @@ export function parseRunbookDoc(data, body, filePath) {
|
|
|
322
323
|
/**
|
|
323
324
|
* Extract trigger phrases from a SKILL.md / agent body.
|
|
324
325
|
*
|
|
325
|
-
* Skills declare alternate activation phrases
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
326
|
+
* Skills declare alternate activation phrases in `triggers`, `aliases`,
|
|
327
|
+
* `deprecated_names`, or under a `## Triggers` heading. This function pulls
|
|
328
|
+
* those names plus each bullet's leading phrase (the part before any `→`
|
|
329
|
+
* arrow or em-dash explanation), lowercased and trimmed.
|
|
329
330
|
*
|
|
330
331
|
* Returns an empty array when no `## Triggers` section is found —
|
|
331
332
|
* non-skill artifacts get `triggers: undefined` after this is wired.
|
|
@@ -334,18 +335,20 @@ export function parseRunbookDoc(data, body, filePath) {
|
|
|
334
335
|
*/
|
|
335
336
|
export function extractTriggers(body, frontmatter) {
|
|
336
337
|
const phrases = [];
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
338
|
+
for (const field of ['triggers', 'aliases', 'deprecated_names']) {
|
|
339
|
+
const declaredValues = Array.isArray(frontmatter?.[field])
|
|
340
|
+
? frontmatter[field]
|
|
341
|
+
: [];
|
|
342
|
+
for (const value of declaredValues) {
|
|
343
|
+
if (typeof value !== 'string')
|
|
344
|
+
continue;
|
|
345
|
+
const phrase = value.trim().toLowerCase();
|
|
346
|
+
if (phrase.length === 0)
|
|
347
|
+
continue;
|
|
348
|
+
if (phrase.length > 200)
|
|
349
|
+
continue;
|
|
350
|
+
phrases.push(phrase);
|
|
351
|
+
}
|
|
349
352
|
}
|
|
350
353
|
// Find a triggers heading (case-insensitive). Accepted variants:
|
|
351
354
|
// ## Triggers
|
|
@@ -882,6 +885,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
882
885
|
// Script entrypoint metadata is meaningful for skills only (#1227).
|
|
883
886
|
const script = type === 'skill' ? extractSkillScript(data) : undefined;
|
|
884
887
|
const operationalState = normalizeOperationalState(data.operational_state);
|
|
888
|
+
const stateTransfer = normalizeStateTransferProjection(data.state_transfer);
|
|
885
889
|
// Canonical short name (#1233) — used by the scorer to floor exact-name
|
|
886
890
|
// queries to 1.0 so hyphenated kernel-skill names like `aiwg-doctor`
|
|
887
891
|
// remain searchable even when the rendered title strips the hyphen.
|
|
@@ -907,6 +911,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
907
911
|
...(kernel ? { kernel } : {}),
|
|
908
912
|
...(script ? { script } : {}),
|
|
909
913
|
...(operationalState ? { operationalState } : {}),
|
|
914
|
+
...(stateTransfer ? { stateTransfer } : {}),
|
|
910
915
|
};
|
|
911
916
|
}
|
|
912
917
|
entries[relativePath] = entry;
|
|
@@ -957,6 +962,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
957
962
|
}
|
|
958
963
|
}
|
|
959
964
|
// Run citation sidecar edge extraction if configured
|
|
965
|
+
let citationMetrics = null;
|
|
960
966
|
if (graphConfig?.edgeExtraction?.parser === 'citation-sidecar') {
|
|
961
967
|
// Build REF-XXX → path map from all entries with ref frontmatter
|
|
962
968
|
const entryFrontmatter = new Map();
|
|
@@ -971,6 +977,10 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
971
977
|
const refToPath = buildRefToPathMap(entryFrontmatter);
|
|
972
978
|
// Parse each entry as a citation sidecar and extract edges
|
|
973
979
|
let citationEdgeCount = 0;
|
|
980
|
+
let outgoingDeclarations = 0;
|
|
981
|
+
let incomingDeclarations = 0;
|
|
982
|
+
const canonicalOutgoing = new Set();
|
|
983
|
+
const declaredIncoming = new Set();
|
|
974
984
|
for (const entryPath of Object.keys(entries)) {
|
|
975
985
|
const fullPath = absoluteEntryPath(cwd, entryPath, graph);
|
|
976
986
|
if (!fs.existsSync(fullPath))
|
|
@@ -979,6 +989,14 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
979
989
|
const result = parseCitationSidecar(content);
|
|
980
990
|
if (!result)
|
|
981
991
|
continue;
|
|
992
|
+
outgoingDeclarations += result.cites.length;
|
|
993
|
+
incomingDeclarations += result.citedBy.length;
|
|
994
|
+
for (const targetRef of result.cites) {
|
|
995
|
+
canonicalOutgoing.add(`${result.ref}\0${targetRef}`);
|
|
996
|
+
}
|
|
997
|
+
for (const sourceRef of result.citedBy) {
|
|
998
|
+
declaredIncoming.add(`${sourceRef}\0${result.ref}`);
|
|
999
|
+
}
|
|
982
1000
|
const edges = citationResultToEdges(result, refToPath);
|
|
983
1001
|
if (!depGraph[entryPath]) {
|
|
984
1002
|
depGraph[entryPath] = { upstream: [], downstream: [] };
|
|
@@ -1004,6 +1022,15 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1004
1022
|
citationEdgeCount++;
|
|
1005
1023
|
}
|
|
1006
1024
|
}
|
|
1025
|
+
citationMetrics = {
|
|
1026
|
+
canonicalEdges: canonicalOutgoing.size,
|
|
1027
|
+
outgoingDeclarations,
|
|
1028
|
+
incomingDeclarations,
|
|
1029
|
+
unmirroredOutgoing: [...canonicalOutgoing]
|
|
1030
|
+
.filter(edge => !declaredIncoming.has(edge)).length,
|
|
1031
|
+
unmirroredIncoming: [...declaredIncoming]
|
|
1032
|
+
.filter(edge => !canonicalOutgoing.has(edge)).length,
|
|
1033
|
+
};
|
|
1007
1034
|
if (verbose && citationEdgeCount > 0) {
|
|
1008
1035
|
console.log(` citation edges: ${citationEdgeCount}`);
|
|
1009
1036
|
}
|
|
@@ -1038,7 +1065,9 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1038
1065
|
manifestStats.pruned = 0;
|
|
1039
1066
|
writeManifest(indexOutputDir, nextManifest);
|
|
1040
1067
|
// Write stats
|
|
1041
|
-
const
|
|
1068
|
+
const downstreamEdges = Object.values(depGraph).reduce((sum, node) => sum + node.downstream.length, 0);
|
|
1069
|
+
const adjacencyEntries = Object.values(depGraph).reduce((sum, node) => sum + node.upstream.length + node.downstream.length, 0);
|
|
1070
|
+
const totalEdges = citationMetrics?.canonicalEdges ?? downstreamEdges;
|
|
1042
1071
|
const orphaned = Object.entries(depGraph).filter(([, node]) => node.upstream.length === 0 && node.downstream.length === 0).length;
|
|
1043
1072
|
const mostReferenced = Object.entries(depGraph)
|
|
1044
1073
|
.map(([p, node]) => ({ path: p, count: node.downstream.length }))
|
|
@@ -1064,6 +1093,14 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
1064
1093
|
tagDistribution: tagDist,
|
|
1065
1094
|
graphMetrics: {
|
|
1066
1095
|
totalEdges,
|
|
1096
|
+
...(citationMetrics ? {
|
|
1097
|
+
canonicalEdges: citationMetrics.canonicalEdges,
|
|
1098
|
+
outgoingDeclarations: citationMetrics.outgoingDeclarations,
|
|
1099
|
+
incomingDeclarations: citationMetrics.incomingDeclarations,
|
|
1100
|
+
adjacencyEntries,
|
|
1101
|
+
unmirroredOutgoing: citationMetrics.unmirroredOutgoing,
|
|
1102
|
+
unmirroredIncoming: citationMetrics.unmirroredIncoming,
|
|
1103
|
+
} : {}),
|
|
1067
1104
|
orphanedArtifacts: orphaned,
|
|
1068
1105
|
mostReferenced,
|
|
1069
1106
|
},
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function normalizeStateTransferProjection(value) {
|
|
2
|
+
if (value === undefined)
|
|
3
|
+
return undefined;
|
|
4
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
5
|
+
throw new Error("state_transfer must be an object");
|
|
6
|
+
}
|
|
7
|
+
const record = value;
|
|
8
|
+
const unknown = Object.keys(record).filter((key) => key !== "deleted_at");
|
|
9
|
+
if (unknown.length > 0) {
|
|
10
|
+
throw new Error(`state_transfer has unsupported fields: ${unknown.join(", ")}`);
|
|
11
|
+
}
|
|
12
|
+
if (!Object.hasOwn(record, "deleted_at")) {
|
|
13
|
+
throw new Error("state_transfer.deleted_at is required");
|
|
14
|
+
}
|
|
15
|
+
const deletedAt = record.deleted_at;
|
|
16
|
+
if (deletedAt === null)
|
|
17
|
+
return { deletedAt: null };
|
|
18
|
+
if (deletedAt instanceof Date && !Number.isNaN(deletedAt.getTime())) {
|
|
19
|
+
return { deletedAt: deletedAt.toISOString() };
|
|
20
|
+
}
|
|
21
|
+
if (typeof deletedAt !== "string"
|
|
22
|
+
|| Number.isNaN(Date.parse(deletedAt))) {
|
|
23
|
+
throw new Error("state_transfer.deleted_at must be null or an ISO date-time");
|
|
24
|
+
}
|
|
25
|
+
return { deletedAt: new Date(deletedAt).toISOString() };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=state-transfer.js.map
|
|
@@ -153,6 +153,14 @@ async function renderStats(cwd, stats, options, graphType) {
|
|
|
153
153
|
// Dependency graph
|
|
154
154
|
console.log('Dependency Graph:');
|
|
155
155
|
console.log(` Total edges: ${stats.graphMetrics.totalEdges}`);
|
|
156
|
+
if (stats.graphMetrics.canonicalEdges !== undefined) {
|
|
157
|
+
console.log(` Canonical edges: ${stats.graphMetrics.canonicalEdges}`);
|
|
158
|
+
console.log(` Outgoing declares: ${stats.graphMetrics.outgoingDeclarations}`);
|
|
159
|
+
console.log(` Incoming declares: ${stats.graphMetrics.incomingDeclarations}`);
|
|
160
|
+
console.log(` Adjacency entries: ${stats.graphMetrics.adjacencyEntries}`);
|
|
161
|
+
console.log(` Unmirrored outgoing:${String(stats.graphMetrics.unmirroredOutgoing).padStart(3)}`);
|
|
162
|
+
console.log(` Unmirrored incoming:${String(stats.graphMetrics.unmirroredIncoming).padStart(3)}`);
|
|
163
|
+
}
|
|
156
164
|
console.log(` Orphaned artifacts: ${stats.graphMetrics.orphanedArtifacts}`);
|
|
157
165
|
if (stats.graphMetrics.mostReferenced) {
|
|
158
166
|
console.log(` Most referenced: ${stats.graphMetrics.mostReferenced.path} (${stats.graphMetrics.mostReferenced.count} dependents)`);
|
|
@@ -41,10 +41,83 @@ export async function writeRegistry(cwd, registry) {
|
|
|
41
41
|
* Called by `aiwg use <addon>` after reading the addon manifest.
|
|
42
42
|
*/
|
|
43
43
|
export async function registerCliCommands(cwd, namespace, description, source, subcommands) {
|
|
44
|
+
if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(namespace)) {
|
|
45
|
+
throw new Error(`Invalid CLI namespace: ${namespace}`);
|
|
46
|
+
}
|
|
47
|
+
if (!source || !path.isAbsolute(source)) {
|
|
48
|
+
throw new Error('CLI extension source must be an absolute path.');
|
|
49
|
+
}
|
|
50
|
+
if (Object.keys(subcommands).length === 0) {
|
|
51
|
+
throw new Error(`CLI namespace '${namespace}' declares no subcommands.`);
|
|
52
|
+
}
|
|
53
|
+
for (const [name, subcommand] of Object.entries(subcommands)) {
|
|
54
|
+
if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name)) {
|
|
55
|
+
throw new Error(`Invalid CLI subcommand: ${namespace} ${name}`);
|
|
56
|
+
}
|
|
57
|
+
if (!subcommand
|
|
58
|
+
|| typeof subcommand.file !== 'string'
|
|
59
|
+
|| !/^[a-zA-Z0-9_-]+\.mjs$/.test(subcommand.file)) {
|
|
60
|
+
throw new Error(`CLI subcommand '${namespace} ${name}' must name a local .mjs file.`);
|
|
61
|
+
}
|
|
62
|
+
if (typeof subcommand.description !== 'string' || !subcommand.description.trim()) {
|
|
63
|
+
throw new Error(`CLI subcommand '${namespace} ${name}' requires a description.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
44
66
|
const existing = await readRegistry(cwd) ?? {};
|
|
45
67
|
existing[namespace] = { source, description, subcommands };
|
|
46
68
|
await writeRegistry(cwd, existing);
|
|
47
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Read and validate an addon-shaped manifest's expandable CLI declaration.
|
|
72
|
+
*
|
|
73
|
+
* `sourceRoot` may be a bundled addon, a project-local addon, or the validated
|
|
74
|
+
* payload of a project-local plugin wrapper. Module files are constrained to
|
|
75
|
+
* the declared commands directory; registration never follows `..` paths.
|
|
76
|
+
*/
|
|
77
|
+
export async function loadCliCommandsContribution(sourceRoot) {
|
|
78
|
+
const manifestPath = path.join(sourceRoot, 'manifest.json');
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code === 'ENOENT')
|
|
85
|
+
return null;
|
|
86
|
+
throw new Error(`Unable to read CLI extension manifest at ${manifestPath}: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
const candidate = raw.cli_commands;
|
|
89
|
+
if (candidate === undefined)
|
|
90
|
+
return null;
|
|
91
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
92
|
+
throw new Error(`Invalid cli_commands block in ${manifestPath}`);
|
|
93
|
+
}
|
|
94
|
+
const commands = candidate;
|
|
95
|
+
if (typeof commands.namespace !== 'string'
|
|
96
|
+
|| typeof commands.description !== 'string'
|
|
97
|
+
|| !commands.subcommands
|
|
98
|
+
|| typeof commands.subcommands !== 'object'
|
|
99
|
+
|| Array.isArray(commands.subcommands)) {
|
|
100
|
+
throw new Error(`Incomplete cli_commands block in ${manifestPath}`);
|
|
101
|
+
}
|
|
102
|
+
const entry = commands.entry ?? 'commands/';
|
|
103
|
+
if (typeof entry !== 'string'
|
|
104
|
+
|| path.isAbsolute(entry)
|
|
105
|
+
|| entry.split(/[\\/]+/).includes('..')) {
|
|
106
|
+
throw new Error(`Unsafe cli_commands.entry in ${manifestPath}`);
|
|
107
|
+
}
|
|
108
|
+
const commandsSource = path.resolve(sourceRoot, entry);
|
|
109
|
+
const relative = path.relative(path.resolve(sourceRoot), commandsSource);
|
|
110
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
111
|
+
throw new Error(`cli_commands.entry escapes its addon root in ${manifestPath}`);
|
|
112
|
+
}
|
|
113
|
+
const manifest = {
|
|
114
|
+
namespace: commands.namespace,
|
|
115
|
+
description: commands.description,
|
|
116
|
+
entry,
|
|
117
|
+
subcommands: commands.subcommands,
|
|
118
|
+
};
|
|
119
|
+
return { manifest, commandsSource };
|
|
120
|
+
}
|
|
48
121
|
/**
|
|
49
122
|
* Try to execute an addon-contributed CLI command
|
|
50
123
|
*
|
|
@@ -44,6 +44,7 @@ import { serveHandler } from './serve.js';
|
|
|
44
44
|
import { lintHandler } from './lint.js';
|
|
45
45
|
import { feedbackHandler } from './feedback.js';
|
|
46
46
|
import { sessionHandler } from './session.js';
|
|
47
|
+
import { sessionsHandler } from './sessions.js';
|
|
47
48
|
import { sandboxHandler, sandboxHandlers } from './sandbox.js';
|
|
48
49
|
import { diagnoseHandler } from './diagnose.js';
|
|
49
50
|
import { localExecutorHandler, localExecutorServeHandler } from './local-executor.js';
|
|
@@ -62,7 +63,7 @@ helpHandler, versionHandler, doctorHandler, updateHandler, refreshHandler, regen
|
|
|
62
63
|
// Framework management
|
|
63
64
|
useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, issueHandler, issueAuditHandler, runHandler,
|
|
64
65
|
// Project
|
|
65
|
-
newBundleHandler, quickrefHandler, newProjectHandler,
|
|
66
|
+
newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
|
|
66
67
|
// Workspace
|
|
67
68
|
statusHandler, wizardHandler, migrateWorkspaceHandler, rollbackWorkspaceHandler,
|
|
68
69
|
// Subcommands
|
|
@@ -224,6 +225,7 @@ export const allHandlers = [
|
|
|
224
225
|
feedbackHandler,
|
|
225
226
|
// Session (#884)
|
|
226
227
|
sessionHandler,
|
|
228
|
+
sessionsHandler,
|
|
227
229
|
// Repo access policy (#1376)
|
|
228
230
|
...repoAccessHandlers,
|
|
229
231
|
];
|