@aiwg/cli 2026.8.7 → 2026.8.10

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 (76) hide show
  1. package/README.md +23 -8
  2. package/THIRD_PARTY_NOTICES.md +35 -0
  3. package/agentic/code/providers/capability-matrix.yaml +3 -3
  4. package/bin/aiwg.mjs +125 -0
  5. package/dist/src/artifacts/backends/graphology-backend.js +4 -3
  6. package/dist/src/artifacts/backends/sqlite-backend.js +4 -5
  7. package/dist/src/artifacts/cli.js +2 -2
  8. package/dist/src/artifacts/corpus-tools/cli.js +27 -0
  9. package/dist/src/artifacts/corpus-tools/profile-embed.js +3 -2
  10. package/dist/src/artifacts/corpus-tools/retrieval-lab.js +356 -0
  11. package/dist/src/artifacts/discover-facets.js +2 -2
  12. package/dist/src/artifacts/embedding-index.js +9 -8
  13. package/dist/src/artifacts/graph-backend.js +2 -2
  14. package/dist/src/artifacts/move.js +40 -2
  15. package/dist/src/artifacts/query-engine.js +21 -7
  16. package/dist/src/artifacts/repair.js +47 -0
  17. package/dist/src/artifacts/types.js +3 -3
  18. package/dist/src/cli/command-log.js +2 -2
  19. package/dist/src/cli/handlers/artifacts.js +50 -1
  20. package/dist/src/cli/handlers/cost-report.js +71 -0
  21. package/dist/src/cli/handlers/evidence.js +78 -0
  22. package/dist/src/cli/handlers/help.js +9 -0
  23. package/dist/src/cli/handlers/index.js +8 -3
  24. package/dist/src/cli/handlers/local-executor.js +4 -3
  25. package/dist/src/cli/handlers/refresh.js +20 -8
  26. package/dist/src/cli/handlers/regenerate.js +22 -12
  27. package/dist/src/cli/handlers/serve.js +15 -36
  28. package/dist/src/cli/handlers/sessions.js +5 -4
  29. package/dist/src/cli/handlers/setup-manifest.js +8 -1
  30. package/dist/src/cli/handlers/subcommands.js +30 -0
  31. package/dist/src/cli/handlers/use.js +272 -89
  32. package/dist/src/cli/handlers/utilities.js +149 -0
  33. package/dist/src/cli/handlers/workspace.js +10 -0
  34. package/dist/src/cli/help-generator.js +2 -1
  35. package/dist/src/cli/regenerate-selector.js +94 -0
  36. package/dist/src/cli/router.js +4 -1
  37. package/dist/src/cli/services/deployment-verification.js +596 -0
  38. package/dist/src/cli/skill-usage.js +2 -2
  39. package/dist/src/cli/workflow-orchestrator.js +1 -1
  40. package/dist/src/cli/workspace-signals.js +2 -2
  41. package/dist/src/config/aiwg-config.js +54 -27
  42. package/dist/src/config/cli.js +3 -3
  43. package/dist/src/config/project-artifacts-health.js +2 -0
  44. package/dist/src/config/project-artifacts-health.mjs +123 -0
  45. package/dist/src/config/project-artifacts-runtime.mjs +16 -0
  46. package/dist/src/config/project-artifacts.js +2 -1
  47. package/dist/src/cost/fleet-report.js +329 -0
  48. package/dist/src/evidence/bundle.js +256 -0
  49. package/dist/src/extensions/commands/definitions.js +77 -25
  50. package/dist/src/extensions/deployment-registration.js +6 -4
  51. package/dist/src/extensions/project-local-doctor.js +10 -6
  52. package/dist/src/extensions/project-local-gitignore.js +8 -4
  53. package/dist/src/extensions/project-quickref.js +197 -10
  54. package/dist/src/features/catalog.js +26 -0
  55. package/dist/src/features/cli.js +1 -3
  56. package/dist/src/features/runtime.js +17 -1
  57. package/dist/src/issues/cli.js +91 -7
  58. package/dist/src/mcp/server.mjs +1 -1
  59. package/dist/src/ops/registry.js +2 -2
  60. package/dist/src/policy/authorization.js +2 -2
  61. package/dist/src/providers/capability-matrix.yaml +3 -3
  62. package/dist/src/providers/provider-definitions.js +7 -5
  63. package/dist/src/providers/provider-definitions.mjs +1 -1
  64. package/dist/src/serve/pty-bridge.js +2 -8
  65. package/dist/src/serve/screen-reader.js +3 -6
  66. package/dist/src/smiths/context-pipeline/aiwg-md.js +2 -2
  67. package/dist/src/smiths/context-pipeline/finalization.js +18 -5
  68. package/dist/src/smiths/context-pipeline/generator.js +2 -2
  69. package/dist/src/smiths/context-pipeline/workspace-context.js +16 -17
  70. package/package.json +2 -1
  71. package/tools/agents/deploy-agents.mjs +10 -11
  72. package/tools/agents/providers/base.mjs +47 -5
  73. package/tools/agents/providers/openclaw.mjs +5 -2
  74. package/tools/agents/providers/windsurf.mjs +13 -24
  75. package/tools/plugin/package-plugins.mjs +53 -0
  76. package/tools/skills/deploy-skills-codex.mjs +21 -5
@@ -7,13 +7,15 @@
7
7
  */
8
8
  import { createHash } from 'crypto';
9
9
  import { access, mkdir, readFile, readdir, rm, writeFile } from 'fs/promises';
10
- import { dirname, isAbsolute, join, resolve } from 'path';
10
+ import { basename, dirname, isAbsolute, join, resolve } from 'path';
11
11
  import { homedir } from 'os';
12
12
  import { z } from 'zod';
13
13
  import { getProviderDefinition, normalizeProviderDefinitionId, } from '../providers/provider-definitions.js';
14
14
  import { OPERATIONAL_SHOW_TYPES } from '../artifacts/types.js';
15
15
  import { projectAiwgPath } from '../config/project-artifacts.js';
16
16
  import { appendAiwgSourceTrackBlock } from './project-local-gitignore.js';
17
+ import { discoverProjectLocalBundles } from './project-local-discovery.js';
18
+ import { enumerateBundleArtifacts } from './shadow-resolver.js';
17
19
  const OWNERSHIP_MARKER = '.aiwg-project-quickref.json';
18
20
  const ShowHintSchema = z.object({
19
21
  type: z.enum(OPERATIONAL_SHOW_TYPES),
@@ -35,6 +37,25 @@ export const ProjectQuickrefSchema = z.object({
35
37
  precedence: z.string().min(1).max(1024),
36
38
  entries: z.array(QuickrefEntrySchema).min(1).max(50),
37
39
  }).strict();
40
+ const QuickrefOverrideSchema = z.object({
41
+ title: z.string().min(1).max(128).optional(),
42
+ summary: z.string().min(1).max(512).optional(),
43
+ discover: z.array(z.string().min(1).max(256)).max(10).optional(),
44
+ show: z.array(ShowHintSchema).max(20).optional(),
45
+ hidden: z.boolean().optional(),
46
+ order: z.number().int().optional(),
47
+ }).strict();
48
+ export const ProjectQuickrefConfigSchema = z.object({
49
+ version: z.literal('1'),
50
+ project: ProjectQuickrefSchema.shape.project.optional(),
51
+ precedence: z.string().min(1).max(1024).optional(),
52
+ entries: z.array(QuickrefEntrySchema).max(50).default([]),
53
+ discovery: z.object({
54
+ enabled: z.boolean().default(true),
55
+ excludeBundles: z.array(z.string().min(1)).max(200).default([]),
56
+ overrides: z.record(z.string(), QuickrefOverrideSchema).default({}),
57
+ }).strict().default({ enabled: true, excludeBundles: [], overrides: {} }),
58
+ }).strict();
38
59
  function sha256(content) {
39
60
  return createHash('sha256').update(content).digest('hex');
40
61
  }
@@ -78,6 +99,158 @@ export async function loadProjectQuickref(projectDir) {
78
99
  return { sourcePath, errors: [`${sourcePath}: invalid JSON: ${error.message}`], exists: true };
79
100
  }
80
101
  }
102
+ function slug(value) {
103
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
104
+ return normalized || 'project';
105
+ }
106
+ async function inferredProject(projectDir) {
107
+ let packageName = basename(resolve(projectDir));
108
+ let description = `Managed project-specific orientation for ${packageName}.`;
109
+ try {
110
+ const parsed = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf8'));
111
+ if (parsed.name)
112
+ packageName = parsed.name;
113
+ if (parsed.description)
114
+ description = parsed.description.slice(0, 512);
115
+ }
116
+ catch {
117
+ // package metadata is optional
118
+ }
119
+ const id = slug(packageName);
120
+ const name = packageName
121
+ .replace(/^@[^/]+\//, '')
122
+ .split(/[-_]/)
123
+ .filter(Boolean)
124
+ .map(part => part[0]?.toUpperCase() + part.slice(1))
125
+ .join(' ') || 'Project';
126
+ return { id, name, description };
127
+ }
128
+ async function loadManagedConfig(projectDir) {
129
+ const path = projectAiwgPath(projectDir, 'quickref.config.json');
130
+ const raw = await readIfPresent(path);
131
+ if (raw === null)
132
+ return { path };
133
+ let json;
134
+ try {
135
+ json = JSON.parse(raw);
136
+ }
137
+ catch (error) {
138
+ throw new Error(`${path}: invalid JSON: ${error.message}`);
139
+ }
140
+ const parsed = ProjectQuickrefConfigSchema.safeParse(json);
141
+ if (!parsed.success) {
142
+ throw new Error(parsed.error.issues.map(issue => `${path}: ${issue.path.join('.') || '(root)'}: ${issue.message}`).join('\n'));
143
+ }
144
+ return { path, config: parsed.data };
145
+ }
146
+ function dedupe(values) {
147
+ const seen = new Set();
148
+ return values.filter(value => {
149
+ const key = value.trim().toLowerCase();
150
+ if (!key || seen.has(key))
151
+ return false;
152
+ seen.add(key);
153
+ return true;
154
+ });
155
+ }
156
+ function dedupeShow(values) {
157
+ const seen = new Set();
158
+ return values.filter(value => {
159
+ const key = `${value.type}:${value.name.toLowerCase()}`;
160
+ if (seen.has(key))
161
+ return false;
162
+ seen.add(key);
163
+ return true;
164
+ });
165
+ }
166
+ /** Resolve legacy operator input or synthesize a managed definition from project-local bundles. */
167
+ export async function resolveProjectQuickref(projectDir) {
168
+ const legacy = await loadProjectQuickref(projectDir);
169
+ if (legacy.exists) {
170
+ if (!legacy.definition)
171
+ throw new Error(legacy.errors.join('\n'));
172
+ return {
173
+ definition: legacy.definition,
174
+ exists: true,
175
+ sourcePath: legacy.sourcePath,
176
+ provenance: 'legacy',
177
+ warnings: [],
178
+ };
179
+ }
180
+ const managed = await loadManagedConfig(projectDir);
181
+ const discovery = await discoverProjectLocalBundles(projectDir);
182
+ if (discovery.errors.length > 0) {
183
+ throw new Error(discovery.errors.map(error => `${error.path}: ${error.field}: ${error.actual}`).join('\n'));
184
+ }
185
+ if (!managed.config && discovery.bundles.length === 0) {
186
+ return { exists: false, sourcePath: managed.path, provenance: 'managed', warnings: [] };
187
+ }
188
+ const config = managed.config ?? ProjectQuickrefConfigSchema.parse({ version: '1' });
189
+ const excluded = new Set(config.discovery.excludeBundles.map(id => id.toLowerCase()));
190
+ const warnings = [];
191
+ const candidates = [];
192
+ if (config.discovery.enabled) {
193
+ for (const bundle of discovery.bundles) {
194
+ if (excluded.has(bundle.id.toLowerCase()))
195
+ continue;
196
+ const override = config.discovery.overrides[bundle.id];
197
+ if (override?.hidden)
198
+ continue;
199
+ const artifacts = (await enumerateBundleArtifacts(bundle.artifactPath ?? bundle.bundlePath))
200
+ .sort((a, b) => a.type.localeCompare(b.type) || a.id.localeCompare(b.id));
201
+ const inferredShow = dedupeShow(artifacts.map(artifact => ({
202
+ type: artifact.type,
203
+ name: artifact.id,
204
+ }))).slice(0, 8);
205
+ if (artifacts.length > inferredShow.length && inferredShow.length === 8) {
206
+ warnings.push(`${bundle.id}: show hints truncated from ${artifacts.length} artifacts to 8`);
207
+ }
208
+ let discover = override?.discover ?? dedupe([
209
+ bundle.id,
210
+ bundle.manifest.name,
211
+ ...(bundle.manifest.keywords ?? []),
212
+ ]).slice(0, 3);
213
+ const show = override?.show ? dedupeShow(override.show) : inferredShow;
214
+ if (discover.length === 0 && show.length === 0)
215
+ discover = [bundle.id];
216
+ candidates.push({
217
+ order: override?.order ?? 0,
218
+ type: bundle.type,
219
+ id: bundle.id,
220
+ entry: {
221
+ title: override?.title ?? bundle.manifest.name,
222
+ summary: override?.summary ?? bundle.manifest.description,
223
+ discover,
224
+ show,
225
+ },
226
+ });
227
+ }
228
+ }
229
+ candidates.sort((a, b) => a.order - b.order || a.type.localeCompare(b.type) || a.id.localeCompare(b.id));
230
+ const discoveredEntries = candidates.map(candidate => candidate.entry);
231
+ const entries = [...discoveredEntries, ...config.entries];
232
+ if (entries.length > 50)
233
+ warnings.push(`quickref entries truncated from ${entries.length} to 50`);
234
+ const bounded = entries.slice(0, 50);
235
+ if (bounded.length === 0) {
236
+ return { exists: false, sourcePath: managed.path, provenance: 'managed', warnings };
237
+ }
238
+ return {
239
+ definition: {
240
+ version: '1',
241
+ project: config.project ?? await inferredProject(projectDir),
242
+ precedence: config.precedence ?? 'Use project-local capabilities before generic AIWG workflows when they apply.',
243
+ entries: bounded,
244
+ },
245
+ exists: true,
246
+ sourcePath: managed.path,
247
+ provenance: 'managed',
248
+ warnings,
249
+ };
250
+ }
251
+ export async function hasProjectQuickref(projectDir) {
252
+ return (await resolveProjectQuickref(projectDir)).exists;
253
+ }
81
254
  export function renderProjectQuickref(definition) {
82
255
  const skillName = projectQuickrefSkillName(definition.project.id);
83
256
  const lines = [
@@ -111,11 +284,9 @@ export function renderProjectQuickref(definition) {
111
284
  return lines.join('\n');
112
285
  }
113
286
  export async function generateProjectQuickref(projectDir, options = {}) {
114
- const loaded = await loadProjectQuickref(projectDir);
115
- if (!loaded.exists)
287
+ const loaded = await resolveProjectQuickref(projectDir);
288
+ if (!loaded.exists || !loaded.definition)
116
289
  throw new Error(`Project quickref source not found: ${loaded.sourcePath}`);
117
- if (!loaded.definition)
118
- throw new Error(loaded.errors.join('\n'));
119
290
  if (!options.dryRun)
120
291
  await appendAiwgSourceTrackBlock(projectDir);
121
292
  const skillName = projectQuickrefSkillName(loaded.definition.project.id);
@@ -127,6 +298,14 @@ export async function generateProjectQuickref(projectDir, options = {}) {
127
298
  await mkdir(dirname(outputPath), { recursive: true });
128
299
  await writeFile(outputPath, content, 'utf8');
129
300
  }
301
+ if (!options.dryRun && loaded.provenance === 'managed') {
302
+ const snapshotPath = projectAiwgPath(projectDir, 'generated', 'project-quickref', 'definition.json');
303
+ const snapshot = JSON.stringify(loaded.definition, null, 2) + '\n';
304
+ if (await readIfPresent(snapshotPath) !== snapshot) {
305
+ await mkdir(dirname(snapshotPath), { recursive: true });
306
+ await writeFile(snapshotPath, snapshot, 'utf8');
307
+ }
308
+ }
130
309
  if (!options.dryRun) {
131
310
  const generatedRoot = projectAiwgPath(projectDir, 'generated', 'project-quickref');
132
311
  try {
@@ -149,6 +328,8 @@ export async function generateProjectQuickref(projectDir, options = {}) {
149
328
  content,
150
329
  changed,
151
330
  dryRun: options.dryRun ?? false,
331
+ provenance: loaded.provenance,
332
+ warnings: loaded.warnings,
152
333
  };
153
334
  }
154
335
  function resolveProviderSkillsRoot(provider, projectDir, homeDir) {
@@ -206,9 +387,9 @@ async function findStaleOwnedQuickrefs(root, projectDir, keepName, global) {
206
387
  }
207
388
  export async function deployProjectQuickref(projectDir, provider, options = {}) {
208
389
  const generated = await generateProjectQuickref(projectDir, { dryRun: options.dryRun });
209
- const loaded = await loadProjectQuickref(projectDir);
390
+ const loaded = await resolveProjectQuickref(projectDir);
210
391
  if (!loaded.definition)
211
- throw new Error(loaded.errors.join('\n'));
392
+ throw new Error(`Project quickref source not found: ${loaded.sourcePath}`);
212
393
  const target = resolveProviderSkillsRoot(provider, projectDir, options.homeDir ?? homedir());
213
394
  const targetDir = join(target.root, generated.skillName);
214
395
  const targetPath = join(targetDir, 'SKILL.md');
@@ -259,11 +440,17 @@ export async function deployProjectQuickref(projectDir, provider, options = {})
259
440
  };
260
441
  }
261
442
  export async function auditProjectQuickref(projectDir, providers, options = {}) {
262
- const loaded = await loadProjectQuickref(projectDir);
443
+ let loaded;
444
+ try {
445
+ loaded = await resolveProjectQuickref(projectDir);
446
+ }
447
+ catch (error) {
448
+ return { exists: true, errors: [error.message], drift: [] };
449
+ }
263
450
  if (!loaded.exists)
264
- return { exists: false, errors: loaded.errors, drift: [] };
451
+ return { exists: false, errors: [], drift: [] };
265
452
  if (!loaded.definition)
266
- return { exists: true, errors: loaded.errors, drift: [] };
453
+ return { exists: true, errors: ['project quickref definition unavailable'], drift: [] };
267
454
  const content = renderProjectQuickref(loaded.definition);
268
455
  const skillName = projectQuickrefSkillName(loaded.definition.project.id);
269
456
  const generatedPath = projectAiwgPath(projectDir, 'generated', 'project-quickref', skillName, 'SKILL.md');
@@ -70,6 +70,32 @@ export const FEATURE_CATALOG = [
70
70
  ],
71
71
  cost: '~3 MB — pure JS, no native deps',
72
72
  },
73
+ {
74
+ name: 'graph',
75
+ description: 'Graphology backend for rich artifact traversal and graph operators',
76
+ packages: ['graphology', 'graphology-operators', 'graphology-traversal'],
77
+ packageSpecs: {
78
+ graphology: '0.26.0',
79
+ 'graphology-operators': '1.6.0',
80
+ 'graphology-traversal': '0.3.0',
81
+ },
82
+ enables: [
83
+ 'index.graphBackend: graphology',
84
+ 'in-memory attributed graph traversal and operator workflows',
85
+ ],
86
+ cost: '~2 MB — pure JS, no native deps',
87
+ },
88
+ {
89
+ name: 'terminal',
90
+ description: 'Headless terminal state parsing for PTY orchestration and auditing',
91
+ packages: ['@xterm/headless'],
92
+ packageSpecs: { '@xterm/headless': '6.0.0' },
93
+ enables: [
94
+ 'structured screen-state parsing for PTY orchestrators',
95
+ 'auditable terminal snapshots without a browser DOM',
96
+ ],
97
+ cost: '~1 MB — pure JS, no native deps',
98
+ },
73
99
  ];
74
100
  /** Native packages require a successful runtime load, not just package files. */
75
101
  export const NATIVE_FEATURE_PACKAGES = new Set(FEATURE_CATALOG.flatMap(feature => feature.scriptPackages ?? []));
@@ -29,9 +29,7 @@ export async function main(args) {
29
29
  case 'remove':
30
30
  console.error(`Error: \`aiwg features ${subcommand}\` not yet implemented (#1219 Cycle 3).`);
31
31
  console.error('');
32
- console.error('To install manually for now:');
33
- console.error(' cd <aiwg install root>');
34
- console.error(' npm install <packages> # see `aiwg features info <name>` for the package list');
32
+ console.error('No files were changed. Feature removal must use a future AIWG CLI release.');
35
33
  process.exit(1);
36
34
  break;
37
35
  case 'help':
@@ -11,7 +11,23 @@ export async function loadFeaturePackage(name) {
11
11
  }
12
12
  catch (featureError) {
13
13
  try {
14
- return await (new Function('m', 'return import(m)'))(name);
14
+ const resolved = createRequire(import.meta.url).resolve(name);
15
+ return await import(pathToFileURL(resolved).href);
16
+ }
17
+ catch {
18
+ throw featureError;
19
+ }
20
+ }
21
+ }
22
+ /** Synchronous counterpart for optional CommonJS packages used by constructors. */
23
+ export function requireFeaturePackage(name) {
24
+ const featureRequire = createRequire(path.join(getFeaturesRoot(), 'package.json'));
25
+ try {
26
+ return featureRequire(name);
27
+ }
28
+ catch (featureError) {
29
+ try {
30
+ return createRequire(import.meta.url)(name);
15
31
  }
16
32
  catch {
17
33
  throw featureError;
@@ -8,6 +8,10 @@
8
8
  * @issue #1462
9
9
  */
10
10
  import { readFile, writeFile } from 'fs/promises';
11
+ import path from 'node:path';
12
+ import { pathToFileURL } from 'node:url';
13
+ import { getFrameworkRoot } from '../channel/manager.mjs';
14
+ import { readAiwgConfig } from '../config/aiwg-config.js';
11
15
  import { LocalIssueProviderCore, buildExternalIssueSnapshotFromLocal, buildLocalIssueConflictReport, createLiveIssueClient, localIssueRoot, parseCommentIdMappings, parseExternalIssueSnapshot, } from './index.js';
12
16
  export async function main(args, cwd = process.cwd()) {
13
17
  const parsed = parseArgs(args);
@@ -27,7 +31,11 @@ export async function main(args, cwd = process.cwd()) {
27
31
  return;
28
32
  case 'new':
29
33
  case 'create':
30
- await handleNew(issues, parsed);
34
+ await handleNew(issues, parsed, cwd);
35
+ return;
36
+ case 'plan':
37
+ case 'draft-plan':
38
+ await handlePlan(parsed, cwd);
31
39
  return;
32
40
  case 'list':
33
41
  case 'ls':
@@ -68,12 +76,19 @@ async function handleInit(issues, args) {
68
76
  });
69
77
  printJsonOrText(args, config, `Initialized local issues with prefix ${config.issue_key.prefix}`);
70
78
  }
71
- async function handleNew(issues, args) {
79
+ async function loadIssueComposer() {
80
+ const frameworkRoot = await getFrameworkRoot();
81
+ const modulePath = path.join(frameworkRoot, 'tools', 'issues', 'policy-boundary-composer.mjs');
82
+ return import(pathToFileURL(modulePath).href);
83
+ }
84
+ async function buildIssuePlan(args, cwd) {
72
85
  const title = stringFlag(args, 'title') ?? args.positional[1];
73
86
  if (!title)
74
- throw new Error('Usage: aiwg issue new --title <title> [--body <text>|--body-file <path>]');
87
+ throw new Error('Usage: aiwg issue plan --title <title> [--body <text>|--body-file <path>]');
75
88
  const body = await readBody(args);
76
- const issue = await issues.createIssue({
89
+ const config = await readAiwgConfig(cwd);
90
+ const composer = await loadIssueComposer();
91
+ return composer.planIssueDraft({
77
92
  title,
78
93
  body,
79
94
  type: stringFlag(args, 'type'),
@@ -81,8 +96,76 @@ async function handleNew(issues, args) {
81
96
  labels: csvFlag(args, 'label'),
82
97
  assignees: csvFlag(args, 'assignee'),
83
98
  author: stringFlag(args, 'author'),
84
- });
85
- printJsonOrText(args, issue, `Created ${issue.fields.id}: ${issue.fields.title}`);
99
+ }, { threatAssessment: config?.security?.threatAssessment });
100
+ }
101
+ async function handlePlan(args, cwd) {
102
+ const plan = await buildIssuePlan(args, cwd);
103
+ console.log(JSON.stringify(plan, null, 2));
104
+ }
105
+ async function handleNew(issues, args, cwd) {
106
+ const plan = await buildIssuePlan(args, cwd);
107
+ if (plan.disposition === 'blocked') {
108
+ throw new Error(`Issue draft rejected by ${plan.blockingRule}; no issue was written. Run aiwg issue plan to inspect suggested segments.`);
109
+ }
110
+ const authorizationDigest = stringFlag(args, 'authorize-policy');
111
+ if (plan.authorizationRequired && authorizationDigest !== plan.digest) {
112
+ throw new Error(`Issue draft requires explicit policy authorization. Review 'aiwg issue plan', then pass --authorize-policy ${plan.digest}. No issue was written.`);
113
+ }
114
+ const composer = await loadIssueComposer();
115
+ const records = new Map();
116
+ const adapter = {
117
+ async findByMarker(marker) {
118
+ const listed = await issues.listIssues({ limit: 10_000 });
119
+ for (const entry of listed.issues) {
120
+ const record = await issues.getIssue(entry.id, { body: true, comments: 0 });
121
+ if (record.body.includes(marker)) {
122
+ records.set(record.fields.id, record);
123
+ return { id: record.fields.id, reference: record.fields.id, body: record.body };
124
+ }
125
+ }
126
+ return null;
127
+ },
128
+ async create(draft) {
129
+ const record = await issues.createIssue({
130
+ title: draft.title,
131
+ body: draft.body,
132
+ type: stringFlag(args, 'type'),
133
+ priority: stringFlag(args, 'priority'),
134
+ labels: draft.labels,
135
+ assignees: draft.assignees,
136
+ author: stringFlag(args, 'author'),
137
+ });
138
+ records.set(record.fields.id, record);
139
+ return { id: record.fields.id, reference: record.fields.id, body: record.body };
140
+ },
141
+ async update(issue, patch) {
142
+ const record = records.get(issue.id) ?? await issues.getIssue(issue.id, { body: true, comments: 0 });
143
+ const references = [...patch.body.matchAll(/(?:Depends on|Related):\s+([A-Z][A-Z0-9_-]*-\d+)/g)]
144
+ .map((match) => match[1])
145
+ .filter((reference) => reference !== issue.id);
146
+ await issues.updateIssueFields(issue.id, {
147
+ links: {
148
+ ...record.fields.links,
149
+ related: [...new Set([...record.fields.links.related, ...references])],
150
+ parent: patch.body.match(/Depends on:\s+([A-Z][A-Z0-9_-]*-\d+)/)?.[1] ?? record.fields.links.parent,
151
+ },
152
+ });
153
+ await issues.commentIssue(issue.id, patch.body.slice(patch.body.indexOf('## Related')), {
154
+ author: stringFlag(args, 'author') ?? 'aiwg',
155
+ });
156
+ },
157
+ };
158
+ const result = await composer.executeIssuePlan(plan, adapter, { authorizationDigest });
159
+ if (result.status !== 'created') {
160
+ throw new Error(`Issue creation ${result.status}: ${result.error ?? 'review the recovery envelope'}\n${JSON.stringify(result.recovery ?? result, null, 2)}`);
161
+ }
162
+ if (args.flags.has('json') || result.created.length > 1) {
163
+ console.log(JSON.stringify(result, null, 2));
164
+ }
165
+ else {
166
+ const issue = result.created[0];
167
+ console.log(`Created ${issue.reference}: ${plan.segments[0].title}`);
168
+ }
86
169
  }
87
170
  async function handleList(issues, args) {
88
171
  const limit = stringFlag(args, 'limit');
@@ -313,7 +396,8 @@ function csvFlag(args, name) {
313
396
  function printUsage() {
314
397
  console.log(`Usage:
315
398
  aiwg issue init [--prefix KEY] [--padding N]
316
- aiwg issue new --title "..." [--body "..."] [--body-file path]
399
+ aiwg issue plan --title "..." [--body "..."] [--body-file path] [--json]
400
+ aiwg issue new --title "..." [--body "..."] [--body-file path] [--authorize-policy DIGEST]
317
401
  aiwg issue list [--status open] [--label bug] [--search text] [--limit 20] [--json]
318
402
  aiwg issue show <KEY> [--comments last:10|all]
319
403
  aiwg issue import --from gitea|github --snapshot-file path [--json]
@@ -7,7 +7,7 @@
7
7
  * @see https://modelcontextprotocol.io/specification/2025-11-25
8
8
  * @architecture @.aiwg/architecture/software-architecture-doc.md - Section 2.1 CLI Layer
9
9
  * @reference @docs/references/REF-066-mcp-specification-2025.md
10
- * @cli @docs/CLI_USAGE.md - mcp serve command
10
+ * @cli @docs/cli/agent-usage.md - mcp serve command
11
11
  * @frameworks @agentic/code/frameworks/sdlc-complete/
12
12
  * @frameworks @agentic/code/frameworks/media-marketing-kit/
13
13
  */
@@ -13,7 +13,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
13
13
  import { execSync } from 'child_process';
14
14
  import { resolveConfigDir } from '../config/user-config.js';
15
15
  import { resolveWorkspace } from '../config/workspace.js';
16
- import { projectAiwgPath } from '../config/project-artifacts.js';
16
+ import { projectControlPath } from '../config/project-artifacts.js';
17
17
  /**
18
18
  * Extension type abbreviations
19
19
  */
@@ -455,7 +455,7 @@ export class OpsRegistry {
455
455
  throw new Error(`Workspace "${name}" not found`);
456
456
  }
457
457
  let canonical = null;
458
- const canonicalConfigPath = projectAiwgPath(workspace.home, 'aiwg.config');
458
+ const canonicalConfigPath = projectControlPath(workspace.home, 'aiwg.config');
459
459
  let canonicalDeclared = false;
460
460
  if (existsSync(canonicalConfigPath)) {
461
461
  try {
@@ -9,7 +9,7 @@
9
9
  import { access, copyFile, mkdir, readFile, rename } from 'node:fs/promises';
10
10
  import { isAbsolute, join, relative, resolve } from 'node:path';
11
11
  import { load as loadYaml } from 'js-yaml';
12
- import { projectAiwgPath } from '../config/project-artifacts.js';
12
+ import { projectAiwgPath, projectControlPath } from '../config/project-artifacts.js';
13
13
  /**
14
14
  * Export provider mappings without inventing provider grants. Stable IDs,
15
15
  * principals, scopes, resources, actions, and conditions survive unchanged;
@@ -375,7 +375,7 @@ export async function archiveLegacyPermissionManifests(projectDir) {
375
375
  return archived;
376
376
  }
377
377
  export async function backupConfig(projectDir) {
378
- const source = projectAiwgPath(projectDir, 'aiwg.config');
378
+ const source = projectControlPath(projectDir, 'aiwg.config');
379
379
  const backupDir = projectAiwgPath(projectDir, 'backups');
380
380
  await mkdir(backupDir, { recursive: true });
381
381
  const content = await readFile(source);
@@ -323,9 +323,9 @@ providers:
323
323
  aggregated_output: true
324
324
 
325
325
  windsurf:
326
- display_name: Windsurf
327
- status: experimental
328
- # Windsurf is an IDE extension requiring VS Code host — daemon not supported
326
+ display_name: Devin Desktop
327
+ status: stable
328
+ # Devin Desktop uses the Windsurf compatibility surface and requires a VS Code host — daemon not supported
329
329
  daemon_tier: unsupported
330
330
  daemon_pty_adapter: false
331
331
  artifact_paths:
@@ -704,11 +704,13 @@ const BUILT_IN_SEEDS = [
704
704
  },
705
705
  {
706
706
  id: 'windsurf',
707
- aliases: ['devin-desktop', 'devin-local', 'cascade'],
707
+ displayName: 'Devin Desktop',
708
+ status: 'stable',
709
+ aliases: ['devin', 'devin-desktop', 'devin-local', 'cascade'],
708
710
  builtIn: true,
709
711
  surfaces: {
710
- primary: 'windsurf',
711
- compatibility: ['devin-desktop', 'devin-local', 'cascade'],
712
+ primary: 'devin',
713
+ compatibility: ['devin-desktop', 'windsurf', 'devin-local', 'cascade'],
712
714
  precedence: ['.devin/rules/', '.windsurf/rules/', 'AGENTS.md', '.windsurfrules'],
713
715
  related: [
714
716
  {
@@ -716,7 +718,7 @@ const BUILT_IN_SEEDS = [
716
718
  displayName: 'Devin Desktop',
717
719
  relationship: 'same-provider',
718
720
  deployable: true,
719
- aliases: ['windsurf', 'cascade'],
721
+ aliases: ['devin', 'windsurf', 'cascade'],
720
722
  paths: {
721
723
  rules: ['.devin/rules/*.md', '.windsurf/rules/*.md'],
722
724
  skills: [],
@@ -724,7 +726,7 @@ const BUILT_IN_SEEDS = [
724
726
  legacy: ['.windsurfrules'],
725
727
  },
726
728
  notes: [
727
- 'Devin Desktop is the renamed Windsurf local IDE surface; --provider windsurf remains the deployable compatibility id.',
729
+ 'Devin Desktop is the current product name; --provider devin is preferred and --provider windsurf remains a deprecated compatibility id.',
728
730
  '.devin/rules is preferred by Devin Desktop, but AIWG keeps .devin/ as ignored local provider output and currently emits the compatibility surface through .windsurf/ plus AGENTS.md.',
729
731
  ],
730
732
  },
@@ -70,7 +70,7 @@ const MCP_INJECTION_DEFINITIONS = [
70
70
  },
71
71
  {
72
72
  id: 'windsurf',
73
- aliases: ['devin-desktop'],
73
+ aliases: ['devin', 'devin-desktop', 'devin-local', 'cascade'],
74
74
  mcp: {
75
75
  providerId: 'windsurf',
76
76
  includeInSupportedProviders: true,
@@ -288,16 +288,10 @@ async function spawnSandboxWsPty(session, agentId, wsEndpoint, opts) {
288
288
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
289
  let wsMod;
290
290
  try {
291
- wsMod = await (new Function('m', 'return import(m)'))('ws');
291
+ wsMod = await loadFeaturePackage('ws');
292
292
  }
293
293
  catch {
294
- try {
295
- // @ts-expect-error Optional runtime dependency; this workspace does not ship @types/ws.
296
- wsMod = await import('ws');
297
- }
298
- catch {
299
- throw new Error('ws package required for sandbox PTY bridge. Run: npm install ws');
300
- }
294
+ throw new Error('WebSocket support is required for the sandbox PTY bridge; run `aiwg features install webserver`');
301
295
  }
302
296
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
297
  const WS = wsMod.WebSocket ?? wsMod.default?.WebSocket ?? wsMod.default;
@@ -10,15 +10,12 @@
10
10
  * @see src/serve/pty-bridge.ts — PTY WebSocket bridge
11
11
  */
12
12
  import { EventEmitter } from 'events';
13
- import { createRequire } from 'module';
14
- // @xterm/headless is an optional dependency. Load it once at module level
15
- // using createRequire so we can import the CJS bundle from ESM.
16
- const _require = createRequire(import.meta.url);
13
+ import { requireFeaturePackage } from '../features/runtime.js';
17
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
15
  let XtermTerminal;
19
16
  try {
20
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- const xterm = _require('@xterm/headless');
18
+ const xterm = requireFeaturePackage('@xterm/headless');
22
19
  XtermTerminal = xterm.Terminal;
23
20
  }
24
21
  catch {
@@ -48,7 +45,7 @@ export class ScreenReader {
48
45
  disposed = false;
49
46
  constructor(opts) {
50
47
  if (!XtermTerminal) {
51
- throw new Error('@xterm/headless is required for ScreenReader. Install it: npm install @xterm/headless');
48
+ throw new Error('Terminal screen parsing is unavailable; run `aiwg features install terminal`');
52
49
  }
53
50
  this._rows = opts?.rows ?? 24;
54
51
  this._cols = opts?.cols ?? 80;
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { promises as fs } from 'node:fs';
18
18
  import * as path from 'node:path';
19
- import { projectAiwgPath } from '../../config/project-artifacts.js';
19
+ import { projectControlPath } from '../../config/project-artifacts.js';
20
20
  import { buildParallelismSection, replaceOrAppendParallelismBlock } from './parallelism-section.js';
21
21
  import { buildContextFinalizationBlock, replaceOrAppendFinalizationBlock } from './finalization.js';
22
22
  import { buildExternalLinksSection, replaceOrAppendExternalLinksBlock, } from './external-links-section.js';
@@ -79,7 +79,7 @@ export async function buildAiwgMdContent(projectPath, stagedClaudeMdContent) {
79
79
  return replaceOrAppendExternalLinksBlock(withFinalization, externalLinksSection);
80
80
  }
81
81
  // Fallback stub.
82
- const normalizedPath = projectAiwgPath(projectPath, 'AIWG.md');
82
+ const normalizedPath = projectControlPath(projectPath, 'AIWG.md');
83
83
  const normalizedRelative = path.relative(projectPath, normalizedPath).replace(/\\/g, '/');
84
84
  const normalizedLink = normalizedRelative.startsWith('.') ? normalizedRelative : `./${normalizedRelative}`;
85
85
  const stub = [