@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.
Files changed (88) hide show
  1. package/README.md +397 -385
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -0
  4. package/dist/src/artifacts/browser-export.js +7 -0
  5. package/dist/src/artifacts/citation-parser.js +96 -35
  6. package/dist/src/artifacts/cli.js +59 -5
  7. package/dist/src/artifacts/discover-facets.js +15 -0
  8. package/dist/src/artifacts/discovery-eval.js +290 -0
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +1 -1
  10. package/dist/src/artifacts/fortemi-shard-export.js +1 -1
  11. package/dist/src/artifacts/index-builder.js +54 -17
  12. package/dist/src/artifacts/query-engine.js +10 -6
  13. package/dist/src/artifacts/state-transfer.js +27 -0
  14. package/dist/src/artifacts/stats.js +8 -0
  15. package/dist/src/cli/cli-extension-loader.js +73 -0
  16. package/dist/src/cli/handlers/help.js +2 -1
  17. package/dist/src/cli/handlers/index.js +6 -2
  18. package/dist/src/cli/handlers/resource-versions.js +247 -0
  19. package/dist/src/cli/handlers/sessions.js +966 -0
  20. package/dist/src/cli/handlers/skill-lint.js +49 -45
  21. package/dist/src/cli/handlers/subcommands.js +55 -3
  22. package/dist/src/cli/handlers/use.js +154 -59
  23. package/dist/src/cli/handlers/utilities.js +49 -34
  24. package/dist/src/cli/skill-usage.js +146 -24
  25. package/dist/src/config/cli.js +13 -9
  26. package/dist/src/config/project-artifacts-runtime.mjs +68 -0
  27. package/dist/src/config/project-artifacts.js +1 -68
  28. package/dist/src/extensions/commands/definitions.js +65 -2
  29. package/dist/src/extensions/manifest.js +29 -0
  30. package/dist/src/extensions/project-local-discovery.js +86 -2
  31. package/dist/src/extensions/project-local-remove.js +52 -56
  32. package/dist/src/extensions/shadow-resolver.js +3 -1
  33. package/dist/src/plugins/standalone-packager.js +143 -0
  34. package/dist/src/resources/cache-cleanup.js +67 -0
  35. package/dist/src/resources/doctor.js +107 -0
  36. package/dist/src/resources/lockfile.js +125 -0
  37. package/dist/src/resources/resolver.js +133 -0
  38. package/dist/src/resources/web-release.d.ts +8 -0
  39. package/dist/src/resources/web-release.js +159 -1
  40. package/dist/src/sessions/adapters/claude.js +357 -0
  41. package/dist/src/sessions/adapters/codex.js +521 -0
  42. package/dist/src/sessions/adapters/copilot.js +226 -0
  43. package/dist/src/sessions/adapters/cursor.js +372 -0
  44. package/dist/src/sessions/adapters/factory.js +345 -0
  45. package/dist/src/sessions/adapters/generic.js +225 -0
  46. package/dist/src/sessions/adapters/hermes.js +341 -0
  47. package/dist/src/sessions/adapters/openclaw.js +381 -0
  48. package/dist/src/sessions/adapters/opencode.js +454 -0
  49. package/dist/src/sessions/adapters/openhuman.js +315 -0
  50. package/dist/src/sessions/adapters/warp.js +160 -0
  51. package/dist/src/sessions/adapters/windsurf.js +212 -0
  52. package/dist/src/sessions/candidates.js +210 -0
  53. package/dist/src/sessions/contracts.js +310 -0
  54. package/dist/src/sessions/discovery.js +51 -0
  55. package/dist/src/sessions/fixtures.js +12 -0
  56. package/dist/src/sessions/importer.js +315 -0
  57. package/dist/src/sessions/index.js +25 -0
  58. package/dist/src/sessions/knowledge-shard.js +61 -0
  59. package/dist/src/sessions/optional-backends.js +238 -0
  60. package/dist/src/sessions/policy.js +192 -0
  61. package/dist/src/sessions/ports.js +2 -0
  62. package/dist/src/sessions/promotion.js +367 -0
  63. package/dist/src/sessions/readers.js +176 -0
  64. package/dist/src/sessions/repository.js +1551 -0
  65. package/dist/src/skills/adapters/agent-skills.js +59 -0
  66. package/dist/src/skills/adapters/local.js +19 -1
  67. package/dist/src/skills/agent-skills.js +249 -0
  68. package/dist/src/skills/cli.js +463 -7
  69. package/dist/src/skills/deployer.js +554 -0
  70. package/dist/src/skills/doctor.js +105 -0
  71. package/dist/src/skills/exporter.js +382 -0
  72. package/dist/src/skills/importer.js +921 -0
  73. package/dist/src/skills/registry.js +19 -0
  74. package/dist/src/skills/validator.js +323 -0
  75. package/dist/src/smiths/context-pipeline/aiwg-md.js +5 -1
  76. package/dist/src/smiths/context-pipeline/claude-hook.js +21 -1
  77. package/dist/src/smiths/context-pipeline/finalization.js +5 -3
  78. package/dist/src/smiths/context-pipeline/generator.js +4 -1
  79. package/dist/src/smiths/context-pipeline/parallelism-section.js +34 -1
  80. package/dist/src/smiths/context-pipeline/workspace-context.js +15 -3
  81. package/dist/src/smiths/mcpsmith/example.js +3 -1
  82. package/dist/src/smiths/mcpsmith/generator.js +3 -1
  83. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +2 -1
  84. package/dist/src/storage/cli.js +3 -2
  85. package/dist/src/storage/subsystem-cli.js +7 -2
  86. package/dist/src/update/notifier.mjs +1 -1
  87. package/dist/src/update/service.mjs +123 -0
  88. 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 { forceUpdateCheck } from '../../update/checker.mjs';
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 namespace issues:
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 namespace validation: scan source SKILL.md files
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── Namespace validation ──');
271
- console.log(` ${issues.length} skill(s) missing namespace field:`);
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: `Namespace validation failed: ${issues.length} skill(s) missing namespace field` };
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── Namespace validation: all skills have namespace field ✓');
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 result;
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('Checking for AIWG updates...\n');
596
- await forceUpdateCheck();
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
- // Map registry IDs to framework use-names
630
- const installedFrameworks = [];
631
- const unmapped = [];
632
- for (const fw of registry.frameworks) {
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 { events: [], appended: 0, skipped: 0, paths: [] };
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 raw = await readFile(options.transcriptPath, 'utf8');
49
- const lines = raw.split('\n').map(line => line.trim()).filter(Boolean);
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
- for (const line of lines) {
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: normalizeProvider(options.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: new Date().toISOString(),
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
- await rotateIfNeeded(filePath, maxBytes);
397
- await appendFile(filePath, JSON.stringify(event) + '\n', 'utf8');
398
- }
399
- async function rotateIfNeeded(filePath, maxBytes) {
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
- const raw = await readFile(filePath, 'utf8');
420
- return raw
421
- .split('\n')
422
- .map(line => line.trim())
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
- catch (error) {
427
- if (error.code === 'ENOENT')
428
- return [];
429
- throw error;
549
+ finally {
550
+ lines.close();
551
+ input.destroy();
430
552
  }
431
553
  }
432
554
  function summarizeEvents(events) {
@@ -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 .aiwg/aiwg.config in this project.',
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 .aiwg/aiwg.config in this project.',
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}/.aiwg/aiwg.config\n`);
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 .aiwg/aiwg.config in this project.',
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 .aiwg/aiwg.config in this project.',
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}/.aiwg/aiwg.config\n`);
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 (.aiwg/aiwg.config)
674
+ show --project Show resolved project config and artifact root
671
675
  validate Validate user config files
672
- validate --project Validate .aiwg/aiwg.config taxonomy/index semantics
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
- import { homedir } from 'os';
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