@aiwg/cli 2026.7.19 → 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 +397 -385
- 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/cli.js +59 -5
- package/dist/src/artifacts/discover-facets.js +15 -0
- package/dist/src/artifacts/discovery-eval.js +290 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +1 -1
- package/dist/src/artifacts/fortemi-shard-export.js +1 -1
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/query-engine.js +10 -6
- 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/help.js +2 -1
- package/dist/src/cli/handlers/index.js +6 -2
- package/dist/src/cli/handlers/resource-versions.js +247 -0
- package/dist/src/cli/handlers/sessions.js +966 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/subcommands.js +55 -3
- package/dist/src/cli/handlers/use.js +154 -59
- package/dist/src/cli/handlers/utilities.js +49 -34
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/config/cli.js +13 -9
- package/dist/src/config/project-artifacts-runtime.mjs +68 -0
- package/dist/src/config/project-artifacts.js +1 -68
- package/dist/src/extensions/commands/definitions.js +65 -2
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/extensions/project-local-discovery.js +86 -2
- package/dist/src/extensions/project-local-remove.js +52 -56
- package/dist/src/extensions/shadow-resolver.js +3 -1
- package/dist/src/plugins/standalone-packager.js +143 -0
- package/dist/src/resources/cache-cleanup.js +67 -0
- package/dist/src/resources/doctor.js +107 -0
- package/dist/src/resources/lockfile.js +125 -0
- package/dist/src/resources/resolver.js +133 -0
- package/dist/src/resources/web-release.d.ts +8 -0
- package/dist/src/resources/web-release.js +159 -1
- 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/dist/src/smiths/context-pipeline/aiwg-md.js +5 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +21 -1
- package/dist/src/smiths/context-pipeline/finalization.js +5 -3
- package/dist/src/smiths/context-pipeline/generator.js +4 -1
- package/dist/src/smiths/context-pipeline/parallelism-section.js +34 -1
- package/dist/src/smiths/context-pipeline/workspace-context.js +15 -3
- package/dist/src/smiths/mcpsmith/example.js +3 -1
- package/dist/src/smiths/mcpsmith/generator.js +3 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +2 -1
- package/dist/src/storage/cli.js +3 -2
- package/dist/src/storage/subsystem-cli.js +7 -2
- package/dist/src/update/notifier.mjs +1 -1
- package/dist/src/update/service.mjs +123 -0
- package/package.json +3 -2
|
@@ -14,7 +14,7 @@ import fsp from 'fs/promises';
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { createScriptRunner } from './script-runner.js';
|
|
16
16
|
import { getFrameworkRoot } from '../../channel/manager.mjs';
|
|
17
|
-
import {
|
|
17
|
+
import { updateInstallation } from '../../update/service.mjs';
|
|
18
18
|
import { useHandler as useFrameworkHandler } from './use.js';
|
|
19
19
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
20
20
|
import { checkCollisions, } from '../../smiths/skillsmith/collision-detector.js';
|
|
@@ -130,8 +130,8 @@ function countSentences(s) {
|
|
|
130
130
|
return matches.length + (endsWithTerminator ? 0 : 1);
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
|
-
* Scan source SKILL.md files in `agentic/code/` for
|
|
134
|
-
* - Missing `namespace: aiwg`
|
|
133
|
+
* Scan canonical AIWG source SKILL.md files in `agentic/code/` for policy issues:
|
|
134
|
+
* - Missing the AIWG-only `namespace: aiwg` source convention
|
|
135
135
|
* - Slug (`aiwg-{name}`) that would shadow an AIWG CLI command
|
|
136
136
|
*
|
|
137
137
|
* Returns lines suitable for console output, or empty array if clean.
|
|
@@ -263,12 +263,14 @@ export const validateMetadataHandler = {
|
|
|
263
263
|
const result = await runner.run('tools/cli/validate-metadata.mjs', scriptArgs, {
|
|
264
264
|
cwd: ctx.cwd,
|
|
265
265
|
});
|
|
266
|
-
// Append
|
|
266
|
+
// Append canonical AIWG source policy. `namespace` is an AIWG extension,
|
|
267
|
+
// not an Agent Skills standard requirement; shared conformance is emitted
|
|
268
|
+
// by validate-metadata.mjs before this source-only convention.
|
|
267
269
|
try {
|
|
268
270
|
const issues = await scanSourceNamespaceIssues(frameworkRoot);
|
|
269
271
|
if (issues.length > 0) {
|
|
270
|
-
console.log('\n──
|
|
271
|
-
console.log(` ${issues.length}
|
|
272
|
+
console.log('\n── AIWG source conventions ──');
|
|
273
|
+
console.log(` ${issues.length} canonical source convention issue(s):`);
|
|
272
274
|
// Show first 20 to avoid flooding output
|
|
273
275
|
issues.slice(0, 20).forEach(l => console.log(l));
|
|
274
276
|
if (issues.length > 20) {
|
|
@@ -276,11 +278,11 @@ export const validateMetadataHandler = {
|
|
|
276
278
|
}
|
|
277
279
|
// Non-zero exit only in strict mode
|
|
278
280
|
if (ctx.args.includes('--strict') && result.exitCode === 0) {
|
|
279
|
-
return { exitCode: 1, message: `
|
|
281
|
+
return { exitCode: 1, message: `AIWG source convention validation failed: ${issues.length} issue(s)` };
|
|
280
282
|
}
|
|
281
283
|
}
|
|
282
284
|
else {
|
|
283
|
-
console.log('\n──
|
|
285
|
+
console.log('\n── AIWG source conventions: all canonical skills include the AIWG namespace extension ✓');
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
288
|
catch {
|
|
@@ -460,6 +462,16 @@ export const doctorHandler = {
|
|
|
460
462
|
const runner = createScriptRunner(frameworkRoot);
|
|
461
463
|
// Run core doctor diagnostics
|
|
462
464
|
const result = await runner.run('tools/cli/doctor.mjs', ctx.args, { cwd: ctx.cwd });
|
|
465
|
+
let agentSkillsFailure = false;
|
|
466
|
+
try {
|
|
467
|
+
const { buildAgentSkillsDoctorSection } = await import('../../skills/doctor.js');
|
|
468
|
+
const section = buildAgentSkillsDoctorSection(ctx.cwd || process.cwd());
|
|
469
|
+
console.log(section.output);
|
|
470
|
+
agentSkillsFailure = section.hasFailures;
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
console.log(`\n── Agent Skills conformance ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
|
|
474
|
+
}
|
|
463
475
|
// Surface feedback escape hatch when doctor finds issues
|
|
464
476
|
if (result.exitCode !== 0) {
|
|
465
477
|
console.log(`
|
|
@@ -536,6 +548,21 @@ export const doctorHandler = {
|
|
|
536
548
|
catch {
|
|
537
549
|
// Project-local section is non-fatal for doctor
|
|
538
550
|
}
|
|
551
|
+
// Web-backed resource lock/cache diagnostics (#1850). Report lock source
|
|
552
|
+
// mode, cold cache, and digest drift without requiring web mode to be in use.
|
|
553
|
+
try {
|
|
554
|
+
const { buildWebResourceDoctorSection } = await import('../../resources/doctor.js');
|
|
555
|
+
const section = buildWebResourceDoctorSection(ctx.cwd || process.cwd(), {
|
|
556
|
+
cacheRoot: process.env.AIWG_RESOURCE_CACHE_ROOT,
|
|
557
|
+
});
|
|
558
|
+
if (section.output)
|
|
559
|
+
console.log(section.output);
|
|
560
|
+
if (section.hasFailures)
|
|
561
|
+
return { exitCode: 1, message: '' };
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
console.log(`\n── Web resource cache ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
|
|
565
|
+
}
|
|
539
566
|
// Canonical workspace-context graph diagnostics (#1811). Legacy projects
|
|
540
567
|
// remain valid; drift, loops, conflicts, and possible credentials fail.
|
|
541
568
|
try {
|
|
@@ -552,7 +579,9 @@ export const doctorHandler = {
|
|
|
552
579
|
catch (error) {
|
|
553
580
|
console.log(`\n── Workspace context graph ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
|
|
554
581
|
}
|
|
555
|
-
return
|
|
582
|
+
return agentSkillsFailure
|
|
583
|
+
? { exitCode: 1, message: '' }
|
|
584
|
+
: result;
|
|
556
585
|
},
|
|
557
586
|
};
|
|
558
587
|
/**
|
|
@@ -578,7 +607,7 @@ export const updateHandler = {
|
|
|
578
607
|
name: 'Update',
|
|
579
608
|
description: 'Update AIWG and re-deploy installed frameworks',
|
|
580
609
|
category: 'maintenance',
|
|
581
|
-
aliases: ['-update', '--update'],
|
|
610
|
+
aliases: ['-update', '--update', 'upgrade'],
|
|
582
611
|
async execute(ctx) {
|
|
583
612
|
const args = ctx.args;
|
|
584
613
|
const deployAll = args.includes('--all');
|
|
@@ -592,8 +621,12 @@ export const updateHandler = {
|
|
|
592
621
|
// Step 1: Check for package updates (unless --skip-check)
|
|
593
622
|
if (!skipCheck) {
|
|
594
623
|
try {
|
|
595
|
-
console.log('
|
|
596
|
-
await
|
|
624
|
+
console.log('Updating AIWG installation...\n');
|
|
625
|
+
const update = await updateInstallation({
|
|
626
|
+
dryRun,
|
|
627
|
+
offline: args.includes('--offline'),
|
|
628
|
+
});
|
|
629
|
+
console.log(`${update.message}\n`);
|
|
597
630
|
}
|
|
598
631
|
catch (error) {
|
|
599
632
|
console.error(`Warning: Update check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -626,30 +659,12 @@ export const updateHandler = {
|
|
|
626
659
|
console.log(' aiwg use all');
|
|
627
660
|
return { exitCode: 0 };
|
|
628
661
|
}
|
|
629
|
-
//
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const useName = REGISTRY_ID_TO_USE_NAME[fw.id];
|
|
634
|
-
if (useName) {
|
|
635
|
-
installedFrameworks.push(useName);
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
unmapped.push(fw.id);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
if (installedFrameworks.length === 0) {
|
|
642
|
-
console.log('No recognized frameworks in registry');
|
|
643
|
-
if (unmapped.length > 0) {
|
|
644
|
-
console.log(`Unrecognized entries: ${unmapped.join(', ')}`);
|
|
645
|
-
}
|
|
646
|
-
return { exitCode: 0 };
|
|
647
|
-
}
|
|
662
|
+
// Canonical framework IDs need their historical public aliases. Add-ons,
|
|
663
|
+
// extensions, and project-local bundles are already accepted by `aiwg use`,
|
|
664
|
+
// so preserve their registry IDs instead of silently skipping them.
|
|
665
|
+
const installedFrameworks = registry.frameworks.map(item => REGISTRY_ID_TO_USE_NAME[item.id] ?? item.id);
|
|
648
666
|
// Report what will be updated
|
|
649
667
|
console.log(`Installed frameworks: ${installedFrameworks.join(', ')}`);
|
|
650
|
-
if (unmapped.length > 0) {
|
|
651
|
-
console.log(`Skipping unrecognized: ${unmapped.join(', ')}`);
|
|
652
|
-
}
|
|
653
668
|
console.log('');
|
|
654
669
|
if (dryRun) {
|
|
655
670
|
console.log('Dry run: Would re-deploy the following frameworks:');
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, stat, writeFile, appendFile, readdir } from 'fs/promises';
|
|
2
|
-
import { existsSync } from 'fs';
|
|
2
|
+
import { createReadStream, existsSync } from 'fs';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
|
+
import { createInterface } from 'readline';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import os from 'os';
|
|
6
7
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
7
8
|
import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath } from '../config/project-artifacts.js';
|
|
8
9
|
const DEFAULT_MAX_BYTES = 1_048_576;
|
|
9
10
|
const DEFAULT_REPORT_LIMIT = 20;
|
|
11
|
+
const MAX_TRANSCRIPT_LINE_BYTES = 1_048_576;
|
|
12
|
+
const MAX_TRANSCRIPT_BYTES = 256 * 1_048_576;
|
|
13
|
+
const MAX_TRANSCRIPT_RECORDS = 1_000_000;
|
|
10
14
|
export async function maybeAppendSkillUsage(input) {
|
|
11
15
|
const env = input.env ?? process.env;
|
|
12
16
|
const resolved = await resolveSkillUsageSettings(input.cwd, env);
|
|
@@ -43,15 +47,38 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
43
47
|
const contextCwd = options.projectRoot ?? options.cwd;
|
|
44
48
|
const resolved = await resolveSkillUsageSettings(contextCwd, env);
|
|
45
49
|
if (!resolved.enabled || resolved.scopes.length === 0) {
|
|
46
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
events: [], appended: 0, skipped: 0, paths: [],
|
|
52
|
+
receipt: {
|
|
53
|
+
source_generation: '', provider: normalizeProvider(options.provider),
|
|
54
|
+
records_read: 0, events_extracted: 0, events_appended: 0,
|
|
55
|
+
duplicates_skipped: 0, malformed_skipped: 0,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const provider = normalizeProvider(options.provider);
|
|
60
|
+
if (provider !== 'claude-code') {
|
|
61
|
+
throw new Error(`unsupported skill-usage transcript provider: ${provider}`);
|
|
47
62
|
}
|
|
48
|
-
const
|
|
49
|
-
const
|
|
63
|
+
const sourceStat = await stat(options.transcriptPath);
|
|
64
|
+
const sourceGeneration = digest([
|
|
65
|
+
provider, String(sourceStat.size), String(sourceStat.mtimeMs),
|
|
66
|
+
].join('\0'));
|
|
50
67
|
const version = await readAiwgVersion(options.frameworkRoot);
|
|
51
68
|
const context = await resolvePathContext(contextCwd);
|
|
52
69
|
const extracted = [];
|
|
53
70
|
let skipped = 0;
|
|
54
|
-
|
|
71
|
+
let recordsRead = 0;
|
|
72
|
+
let bytesRead = 0;
|
|
73
|
+
for await (const line of streamTranscriptLines(options.transcriptPath)) {
|
|
74
|
+
recordsRead += 1;
|
|
75
|
+
bytesRead += Buffer.byteLength(line) + 1;
|
|
76
|
+
if (recordsRead > MAX_TRANSCRIPT_RECORDS || bytesRead > MAX_TRANSCRIPT_BYTES) {
|
|
77
|
+
throw new Error('skill-usage transcript exceeds bounded ingestion limits');
|
|
78
|
+
}
|
|
79
|
+
if (Buffer.byteLength(line) > MAX_TRANSCRIPT_LINE_BYTES) {
|
|
80
|
+
throw new Error('skill-usage transcript record exceeds bounded line limit');
|
|
81
|
+
}
|
|
55
82
|
let parsed;
|
|
56
83
|
try {
|
|
57
84
|
parsed = JSON.parse(line);
|
|
@@ -65,22 +92,50 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
65
92
|
skipped += 1;
|
|
66
93
|
}
|
|
67
94
|
else {
|
|
68
|
-
extracted.push(...events
|
|
95
|
+
extracted.push(...events.map(event => ({
|
|
96
|
+
...event,
|
|
97
|
+
occurredAt: sourceOccurrenceTime(parsed),
|
|
98
|
+
nativeEventId: sourceNativeEventId(parsed),
|
|
99
|
+
position: recordsRead,
|
|
100
|
+
contentDigest: digest(line),
|
|
101
|
+
})));
|
|
69
102
|
}
|
|
70
103
|
}
|
|
71
104
|
const persisted = [];
|
|
72
105
|
const paths = resolved.scopes
|
|
73
106
|
.map(scope => resolveUsagePath(scope, context.projectRoot, env))
|
|
74
107
|
.filter((p) => Boolean(p));
|
|
108
|
+
const knownEventIdsByPath = new Map();
|
|
109
|
+
for (const filePath of paths) {
|
|
110
|
+
const knownEventIds = new Set();
|
|
111
|
+
for (const event of await readJsonlEvents(filePath)) {
|
|
112
|
+
if (event.event_id)
|
|
113
|
+
knownEventIds.add(event.event_id);
|
|
114
|
+
}
|
|
115
|
+
knownEventIdsByPath.set(filePath, knownEventIds);
|
|
116
|
+
}
|
|
117
|
+
let duplicatesSkipped = 0;
|
|
75
118
|
for (const scope of resolved.scopes) {
|
|
76
119
|
const filePath = resolveUsagePath(scope, context.projectRoot, env);
|
|
77
120
|
if (!filePath)
|
|
78
121
|
continue;
|
|
122
|
+
const knownEventIds = knownEventIdsByPath.get(filePath) ?? new Set();
|
|
79
123
|
for (const item of extracted) {
|
|
124
|
+
const eventId = digest([
|
|
125
|
+
sourceGeneration,
|
|
126
|
+
item.nativeEventId ?? String(item.position),
|
|
127
|
+
item.contentDigest,
|
|
128
|
+
item.artifact.kind,
|
|
129
|
+
item.artifact.id,
|
|
130
|
+
].join('\0'));
|
|
131
|
+
if (knownEventIds.has(eventId)) {
|
|
132
|
+
duplicatesSkipped += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
80
135
|
const event = buildUsageEvent({
|
|
81
136
|
env,
|
|
82
137
|
source: 'transcript',
|
|
83
|
-
provider
|
|
138
|
+
provider,
|
|
84
139
|
artifact: item.artifact,
|
|
85
140
|
action: item.action,
|
|
86
141
|
outcome: 'unknown',
|
|
@@ -88,8 +143,13 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
88
143
|
cwd: context.projectRoot ?? contextCwd,
|
|
89
144
|
context,
|
|
90
145
|
scope,
|
|
146
|
+
occurredAt: item.occurredAt,
|
|
147
|
+
eventId,
|
|
148
|
+
sourceGeneration,
|
|
149
|
+
nativeEventId: item.nativeEventId,
|
|
91
150
|
});
|
|
92
151
|
persisted.push(event);
|
|
152
|
+
knownEventIds.add(eventId);
|
|
93
153
|
if (!options.dryRun) {
|
|
94
154
|
await appendBoundedJsonl(filePath, event, resolved.maxBytes);
|
|
95
155
|
}
|
|
@@ -100,6 +160,15 @@ export async function ingestSkillUsageTranscript(options) {
|
|
|
100
160
|
appended: options.dryRun ? 0 : persisted.length,
|
|
101
161
|
skipped,
|
|
102
162
|
paths,
|
|
163
|
+
receipt: {
|
|
164
|
+
source_generation: sourceGeneration,
|
|
165
|
+
provider,
|
|
166
|
+
records_read: recordsRead,
|
|
167
|
+
events_extracted: extracted.length,
|
|
168
|
+
events_appended: options.dryRun ? 0 : persisted.length,
|
|
169
|
+
duplicates_skipped: duplicatesSkipped,
|
|
170
|
+
malformed_skipped: skipped,
|
|
171
|
+
},
|
|
103
172
|
};
|
|
104
173
|
}
|
|
105
174
|
export async function readSkillUsageReport(options) {
|
|
@@ -131,6 +200,10 @@ export async function readSkillUsageReport(options) {
|
|
|
131
200
|
limit: 5,
|
|
132
201
|
}),
|
|
133
202
|
paths,
|
|
203
|
+
window: {
|
|
204
|
+
retained_segments: paths.reduce((count, filePath) => count + retainedUsagePaths(filePath).filter(existsSync).length, 0),
|
|
205
|
+
truncated_before: paths.some(filePath => existsSync(`${filePath}.1`)),
|
|
206
|
+
},
|
|
134
207
|
};
|
|
135
208
|
}
|
|
136
209
|
export async function printSkillUsageReport(options) {
|
|
@@ -205,10 +278,17 @@ function classifyCliUsage(command, args) {
|
|
|
205
278
|
return { kind: 'command', id: command, action: 'invoke' };
|
|
206
279
|
}
|
|
207
280
|
function buildUsageEvent(input) {
|
|
281
|
+
const observedTimestamp = new Date().toISOString();
|
|
208
282
|
const event = {
|
|
209
|
-
schema_version: 1,
|
|
283
|
+
schema_version: input.eventId ? 2 : 1,
|
|
210
284
|
event_type: 'aiwg.skill_usage',
|
|
211
|
-
timestamp:
|
|
285
|
+
timestamp: input.occurredAt ?? observedTimestamp,
|
|
286
|
+
...(input.eventId ? {
|
|
287
|
+
observed_timestamp: observedTimestamp,
|
|
288
|
+
event_id: input.eventId,
|
|
289
|
+
source_generation: input.sourceGeneration,
|
|
290
|
+
native_event_id: input.nativeEventId,
|
|
291
|
+
} : {}),
|
|
212
292
|
invocation_id: input.env.AIWG_INVOCATION_ID,
|
|
213
293
|
source: input.source,
|
|
214
294
|
provider: input.provider,
|
|
@@ -294,6 +374,25 @@ function normalizeProvider(value) {
|
|
|
294
374
|
return 'claude-code';
|
|
295
375
|
return normalized || 'unknown';
|
|
296
376
|
}
|
|
377
|
+
function sourceOccurrenceTime(value) {
|
|
378
|
+
if (!isRecord(value))
|
|
379
|
+
return undefined;
|
|
380
|
+
for (const key of ['timestamp', 'created_at', 'createdAt', 'time']) {
|
|
381
|
+
const candidate = value[key];
|
|
382
|
+
if (typeof candidate === 'string' && !Number.isNaN(Date.parse(candidate))) {
|
|
383
|
+
return new Date(candidate).toISOString();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
function sourceNativeEventId(value) {
|
|
389
|
+
if (!isRecord(value))
|
|
390
|
+
return undefined;
|
|
391
|
+
return firstString(value, ['uuid', 'event_id', 'eventId', 'id', 'message_id']);
|
|
392
|
+
}
|
|
393
|
+
function digest(value) {
|
|
394
|
+
return createHash('sha256').update(value).digest('hex');
|
|
395
|
+
}
|
|
297
396
|
function isArtifactKind(value) {
|
|
298
397
|
return value === 'skill' ||
|
|
299
398
|
value === 'agent' ||
|
|
@@ -393,15 +492,20 @@ function resolveUsagePath(scope, projectRoot, env) {
|
|
|
393
492
|
}
|
|
394
493
|
async function appendBoundedJsonl(filePath, event, maxBytes) {
|
|
395
494
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
495
|
+
const line = JSON.stringify(event) + '\n';
|
|
496
|
+
const lineBytes = Buffer.byteLength(line);
|
|
497
|
+
if (maxBytes > 0 && lineBytes > maxBytes)
|
|
498
|
+
event.oversized_record = true;
|
|
499
|
+
const finalLine = JSON.stringify(event) + '\n';
|
|
500
|
+
await rotateIfNeeded(filePath, maxBytes, Buffer.byteLength(finalLine));
|
|
501
|
+
await appendFile(filePath, finalLine, 'utf8');
|
|
502
|
+
}
|
|
503
|
+
async function rotateIfNeeded(filePath, maxBytes, appendBytes) {
|
|
400
504
|
if (maxBytes <= 0)
|
|
401
505
|
return;
|
|
402
506
|
try {
|
|
403
507
|
const current = await stat(filePath);
|
|
404
|
-
if (current.size <= maxBytes)
|
|
508
|
+
if (current.size === 0 || current.size + appendBytes <= maxBytes)
|
|
405
509
|
return;
|
|
406
510
|
const rotated = `${filePath}.1`;
|
|
407
511
|
if (existsSync(rotated)) {
|
|
@@ -415,18 +519,36 @@ async function rotateIfNeeded(filePath, maxBytes) {
|
|
|
415
519
|
}
|
|
416
520
|
}
|
|
417
521
|
async function readJsonlEvents(filePath) {
|
|
522
|
+
const events = [];
|
|
523
|
+
for (const retainedPath of retainedUsagePaths(filePath)) {
|
|
524
|
+
try {
|
|
525
|
+
for await (const line of streamTranscriptLines(retainedPath)) {
|
|
526
|
+
if (line.trim())
|
|
527
|
+
events.push(JSON.parse(line));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (error.code !== 'ENOENT')
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return events;
|
|
536
|
+
}
|
|
537
|
+
function retainedUsagePaths(filePath) {
|
|
538
|
+
return [`${filePath}.1`, filePath];
|
|
539
|
+
}
|
|
540
|
+
async function* streamTranscriptLines(filePath) {
|
|
541
|
+
const input = createReadStream(filePath, { encoding: 'utf8' });
|
|
542
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
418
543
|
try {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
.filter(Boolean)
|
|
424
|
-
.map(line => JSON.parse(line));
|
|
544
|
+
for await (const line of lines) {
|
|
545
|
+
if (line.trim())
|
|
546
|
+
yield line;
|
|
547
|
+
}
|
|
425
548
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
throw error;
|
|
549
|
+
finally {
|
|
550
|
+
lines.close();
|
|
551
|
+
input.destroy();
|
|
430
552
|
}
|
|
431
553
|
}
|
|
432
554
|
function summarizeEvents(events) {
|
package/dist/src/config/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'url';
|
|
|
21
21
|
import path from 'path';
|
|
22
22
|
import { UserConfig } from './user-config.js';
|
|
23
23
|
import { AiwgError, EXIT_CODES } from '../cli/errors.js';
|
|
24
|
+
import { projectAiwgPath, resolveProjectAiwgDir } from './project-artifacts.js';
|
|
24
25
|
const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
25
26
|
/**
|
|
26
27
|
* Main CLI entry point for `aiwg config <subcommand> [args]`
|
|
@@ -182,7 +183,7 @@ async function projectConfigGet(key, args) {
|
|
|
182
183
|
if (!cfg) {
|
|
183
184
|
throw new AiwgError({
|
|
184
185
|
code: 'ERR_NO_PROJECT_CONFIG',
|
|
185
|
-
message: 'No
|
|
186
|
+
message: 'No project AIWG config found at the resolved artifact root.',
|
|
186
187
|
hint: 'Run `aiwg init`, then ask your AIWG agent to set up repo/tracker/delivery policy.',
|
|
187
188
|
exitCode: EXIT_CODES.CONFIG,
|
|
188
189
|
});
|
|
@@ -362,7 +363,7 @@ async function handleProjectValidate(args) {
|
|
|
362
363
|
if (!cfg) {
|
|
363
364
|
throw new AiwgError({
|
|
364
365
|
code: 'ERR_NO_PROJECT_CONFIG',
|
|
365
|
-
message: 'No
|
|
366
|
+
message: 'No project AIWG config found at the resolved artifact root.',
|
|
366
367
|
hint: 'Run `aiwg init`, then configure project policy.',
|
|
367
368
|
exitCode: EXIT_CODES.CONFIG,
|
|
368
369
|
});
|
|
@@ -403,7 +404,8 @@ async function handleProjectValidate(args) {
|
|
|
403
404
|
? validateIssueLabels(cfg.issues)
|
|
404
405
|
: resolveIssueLabels(undefined, 'local').diagnostics;
|
|
405
406
|
const diagnostics = [...indexErrors, ...externalLinkErrors, ...labelDiagnostics];
|
|
406
|
-
console.log(`Project config: ${projectDir
|
|
407
|
+
console.log(`Project config: ${projectAiwgPath(projectDir, 'aiwg.config')}`);
|
|
408
|
+
console.log(`Artifact root: ${resolveProjectAiwgDir(projectDir)}\n`);
|
|
407
409
|
if (diagnostics.length === 0) {
|
|
408
410
|
console.log('✓ Project config valid');
|
|
409
411
|
return;
|
|
@@ -464,7 +466,7 @@ async function projectConfigReset(key, args) {
|
|
|
464
466
|
if (!cfg) {
|
|
465
467
|
throw new AiwgError({
|
|
466
468
|
code: 'ERR_NO_PROJECT_CONFIG',
|
|
467
|
-
message: 'No
|
|
469
|
+
message: 'No project AIWG config found at the resolved artifact root.',
|
|
468
470
|
hint: 'Run `aiwg init`, then ask your AIWG agent to establish project policy.',
|
|
469
471
|
exitCode: EXIT_CODES.CONFIG,
|
|
470
472
|
});
|
|
@@ -553,7 +555,7 @@ For project-level config: aiwg config show --project [--json]
|
|
|
553
555
|
if (!cfg) {
|
|
554
556
|
throw new AiwgError({
|
|
555
557
|
code: 'ERR_NO_PROJECT_CONFIG',
|
|
556
|
-
message: 'No
|
|
558
|
+
message: 'No project AIWG config found at the resolved artifact root.',
|
|
557
559
|
hint: 'Run `aiwg init`, then ask your AIWG agent to set up repo/tracker/delivery policy.',
|
|
558
560
|
exitCode: EXIT_CODES.CONFIG,
|
|
559
561
|
});
|
|
@@ -593,7 +595,8 @@ For project-level config: aiwg config show --project [--json]
|
|
|
593
595
|
return;
|
|
594
596
|
}
|
|
595
597
|
// Human-readable view
|
|
596
|
-
console.log(`Project config: ${projectDir
|
|
598
|
+
console.log(`Project config: ${projectAiwgPath(projectDir, 'aiwg.config')}`);
|
|
599
|
+
console.log(`Artifact root: ${resolveProjectAiwgDir(projectDir)}\n`);
|
|
597
600
|
console.log(`Schema version: ${cfg.version}`);
|
|
598
601
|
console.log(`Providers: ${cfg.providers.join(', ') || '(none)'}`);
|
|
599
602
|
console.log('');
|
|
@@ -663,13 +666,14 @@ function printUsage() {
|
|
|
663
666
|
|
|
664
667
|
Subcommands:
|
|
665
668
|
get <key> Read a user config value
|
|
666
|
-
get --project <key> Read a project config value (.aiwg/aiwg.config
|
|
669
|
+
get --project <key> Read a project config value (default .aiwg/aiwg.config;
|
|
670
|
+
AIWG_ARTIFACTS_PATH may override the artifact root)
|
|
667
671
|
set <key> <value> Write a user config value
|
|
668
672
|
set --project <key> <value> Write a project config value (validates enums)
|
|
669
673
|
list Show all user config
|
|
670
|
-
show --project Show resolved project config
|
|
674
|
+
show --project Show resolved project config and artifact root
|
|
671
675
|
validate Validate user config files
|
|
672
|
-
validate --project Validate
|
|
676
|
+
validate --project Validate resolved project config taxonomy/index semantics
|
|
673
677
|
[--provider gitea|github|local] [--available-label NAME ...]
|
|
674
678
|
reset [<key>] Reset key or all config to defaults
|
|
675
679
|
path Print config directory path
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime implementation of project AIWG artifact directory resolution.
|
|
3
|
+
*
|
|
4
|
+
* Kept as ESM JavaScript so direct `.mjs` tools and compiled TypeScript
|
|
5
|
+
* consumers share exactly one resolver implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { isAbsolute, join, resolve } from 'path';
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_PROJECT_AIWG_DIR = '.aiwg';
|
|
13
|
+
export const AIWG_ARTIFACTS_PATH_ENV = 'AIWG_ARTIFACTS_PATH';
|
|
14
|
+
export const PROJECT_AIWG_LOCATION_FILE = '.aiwg-location';
|
|
15
|
+
|
|
16
|
+
const ARTIFACT_PATH_ENV_ALIASES = [
|
|
17
|
+
AIWG_ARTIFACTS_PATH_ENV,
|
|
18
|
+
'AIWG_PROJECT_ARTIFACTS_PATH',
|
|
19
|
+
'AIWG_PROJECT_AIWG_DIR',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export function expandProjectArtifactPath(pathValue, projectDir) {
|
|
23
|
+
const trimmed = pathValue.trim();
|
|
24
|
+
if (trimmed === '~') return homedir();
|
|
25
|
+
if (trimmed.startsWith('~/')) return resolve(homedir(), trimmed.slice(2));
|
|
26
|
+
if (isAbsolute(trimmed)) return trimmed;
|
|
27
|
+
return resolve(projectDir, trimmed);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseProjectArtifactLocation(contents) {
|
|
31
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
32
|
+
let line = rawLine.trim();
|
|
33
|
+
if (line.length === 0 || line.startsWith('#')) continue;
|
|
34
|
+
if (line.startsWith('export ')) line = line.slice('export '.length).trim();
|
|
35
|
+
const assignment = line.match(/^AIWG_ARTIFACTS_PATH\s*=\s*(.+)$/);
|
|
36
|
+
if (assignment) line = assignment[1].trim();
|
|
37
|
+
if (
|
|
38
|
+
(line.startsWith('"') && line.endsWith('"')) ||
|
|
39
|
+
(line.startsWith("'") && line.endsWith("'"))
|
|
40
|
+
) {
|
|
41
|
+
line = line.slice(1, -1);
|
|
42
|
+
}
|
|
43
|
+
return line.length > 0 ? line : null;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function readProjectArtifactLocation(projectDir) {
|
|
49
|
+
const pointerPath = resolve(projectDir, PROJECT_AIWG_LOCATION_FILE);
|
|
50
|
+
if (!existsSync(pointerPath)) return null;
|
|
51
|
+
return parseProjectArtifactLocation(readFileSync(pointerPath, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resolveProjectAiwgDir(projectDir, env = process.env) {
|
|
55
|
+
for (const key of ARTIFACT_PATH_ENV_ALIASES) {
|
|
56
|
+
const value = env[key];
|
|
57
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
58
|
+
return expandProjectArtifactPath(value, projectDir);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const configuredLocation = readProjectArtifactLocation(projectDir);
|
|
62
|
+
if (configuredLocation) return expandProjectArtifactPath(configuredLocation, projectDir);
|
|
63
|
+
return resolve(projectDir, DEFAULT_PROJECT_AIWG_DIR);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function projectAiwgPath(projectDir, ...segments) {
|
|
67
|
+
return join(resolveProjectAiwgDir(projectDir), ...segments);
|
|
68
|
+
}
|
|
@@ -5,72 +5,5 @@
|
|
|
5
5
|
* override for the project-local `.aiwg/` artifact root. Keep that contract
|
|
6
6
|
* centralized so callers do not hardcode `<project>/.aiwg`.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
import { existsSync, readFileSync } from 'fs';
|
|
10
|
-
import { isAbsolute, join, resolve } from 'path';
|
|
11
|
-
export const DEFAULT_PROJECT_AIWG_DIR = '.aiwg';
|
|
12
|
-
export const AIWG_ARTIFACTS_PATH_ENV = 'AIWG_ARTIFACTS_PATH';
|
|
13
|
-
export const PROJECT_AIWG_LOCATION_FILE = '.aiwg-location';
|
|
14
|
-
const ARTIFACT_PATH_ENV_ALIASES = [
|
|
15
|
-
AIWG_ARTIFACTS_PATH_ENV,
|
|
16
|
-
'AIWG_PROJECT_ARTIFACTS_PATH',
|
|
17
|
-
'AIWG_PROJECT_AIWG_DIR',
|
|
18
|
-
];
|
|
19
|
-
export function expandProjectArtifactPath(pathValue, projectDir) {
|
|
20
|
-
const trimmed = pathValue.trim();
|
|
21
|
-
if (trimmed === '~')
|
|
22
|
-
return homedir();
|
|
23
|
-
if (trimmed.startsWith('~/'))
|
|
24
|
-
return resolve(homedir(), trimmed.slice(2));
|
|
25
|
-
if (isAbsolute(trimmed))
|
|
26
|
-
return trimmed;
|
|
27
|
-
return resolve(projectDir, trimmed);
|
|
28
|
-
}
|
|
29
|
-
export function parseProjectArtifactLocation(contents) {
|
|
30
|
-
for (const rawLine of contents.split(/\r?\n/)) {
|
|
31
|
-
let line = rawLine.trim();
|
|
32
|
-
if (line.length === 0 || line.startsWith('#'))
|
|
33
|
-
continue;
|
|
34
|
-
if (line.startsWith('export '))
|
|
35
|
-
line = line.slice('export '.length).trim();
|
|
36
|
-
const assignment = line.match(/^AIWG_ARTIFACTS_PATH\s*=\s*(.+)$/);
|
|
37
|
-
if (assignment)
|
|
38
|
-
line = assignment[1].trim();
|
|
39
|
-
if ((line.startsWith('"') && line.endsWith('"')) ||
|
|
40
|
-
(line.startsWith("'") && line.endsWith("'"))) {
|
|
41
|
-
line = line.slice(1, -1);
|
|
42
|
-
}
|
|
43
|
-
return line.length > 0 ? line : null;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
export function readProjectArtifactLocation(projectDir) {
|
|
48
|
-
const pointerPath = resolve(projectDir, PROJECT_AIWG_LOCATION_FILE);
|
|
49
|
-
if (!existsSync(pointerPath))
|
|
50
|
-
return null;
|
|
51
|
-
return parseProjectArtifactLocation(readFileSync(pointerPath, 'utf-8'));
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Resolve the directory that contains AIWG project artifacts.
|
|
55
|
-
*
|
|
56
|
-
* Defaults to `<projectDir>/.aiwg`. When `AIWG_ARTIFACTS_PATH` is set, the
|
|
57
|
-
* override may be absolute, project-relative, or `~/`-relative. The override
|
|
58
|
-
* intentionally points at the artifact directory itself, not its parent, so
|
|
59
|
-
* callers can rename `.aiwg` or place it outside the checkout.
|
|
60
|
-
*/
|
|
61
|
-
export function resolveProjectAiwgDir(projectDir, env = process.env) {
|
|
62
|
-
for (const key of ARTIFACT_PATH_ENV_ALIASES) {
|
|
63
|
-
const value = env[key];
|
|
64
|
-
if (typeof value === 'string' && value.trim().length > 0) {
|
|
65
|
-
return expandProjectArtifactPath(value, projectDir);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const configuredLocation = readProjectArtifactLocation(projectDir);
|
|
69
|
-
if (configuredLocation)
|
|
70
|
-
return expandProjectArtifactPath(configuredLocation, projectDir);
|
|
71
|
-
return resolve(projectDir, DEFAULT_PROJECT_AIWG_DIR);
|
|
72
|
-
}
|
|
73
|
-
export function projectAiwgPath(projectDir, ...segments) {
|
|
74
|
-
return join(resolveProjectAiwgDir(projectDir), ...segments);
|
|
75
|
-
}
|
|
8
|
+
export { AIWG_ARTIFACTS_PATH_ENV, DEFAULT_PROJECT_AIWG_DIR, PROJECT_AIWG_LOCATION_FILE, expandProjectArtifactPath, parseProjectArtifactLocation, projectAiwgPath, readProjectArtifactLocation, resolveProjectAiwgDir, } from './project-artifacts-runtime.mjs';
|
|
76
9
|
//# sourceMappingURL=project-artifacts.js.map
|