@aiwg/cli 2026.9.3 → 2026.9.5

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 (95) hide show
  1. package/README.md +21 -0
  2. package/THIRD_PARTY_NOTICES.md +16 -0
  3. package/agentic/code/providers/capability-matrix.yaml +45 -3
  4. package/agentic/code/providers/deepseek-harness/README.md +8 -0
  5. package/agentic/code/providers/deepseek-harness/aiwg.cordis.patch.yml +10 -0
  6. package/agentic/code/providers/model-capabilities.v1.json +11 -0
  7. package/agentic/code/providers/model-catalog.v1.json +8 -0
  8. package/bin/aiwg.mjs +1 -0
  9. package/dist/src/api/index.d.ts +14 -0
  10. package/dist/src/api/index.js +14 -0
  11. package/dist/src/artifacts/corpus-tools/source-types.js +1 -0
  12. package/dist/src/artifacts/index-files.js +17 -2
  13. package/dist/src/artifacts/repair.js +55 -6
  14. package/dist/src/catalog/cli.js +21 -7
  15. package/dist/src/catalog/cli.mjs +22 -7
  16. package/dist/src/cli/agent-spawn.js +10 -1
  17. package/dist/src/cli/handlers/artifacts.js +22 -3
  18. package/dist/src/cli/handlers/help.js +3 -0
  19. package/dist/src/cli/handlers/index.js +5 -1
  20. package/dist/src/cli/handlers/models.js +2 -2
  21. package/dist/src/cli/handlers/output-mode.js +1 -1
  22. package/dist/src/cli/handlers/runtime-info.js +1 -1
  23. package/dist/src/cli/handlers/sessions.js +55 -15
  24. package/dist/src/cli/handlers/steward.js +1 -1
  25. package/dist/src/cli/handlers/subcommands.js +5 -0
  26. package/dist/src/cli/handlers/use.js +37 -0
  27. package/dist/src/cli/handlers/writer-profile.js +110 -0
  28. package/dist/src/cli/handlers/writing.js +122 -0
  29. package/dist/src/cli/router.js +5 -1
  30. package/dist/src/config/project-artifacts-runtime.mjs +33 -1
  31. package/dist/src/config/project-artifacts.js +1 -1
  32. package/dist/src/dataset/fortemi-dataset-execution.d.ts +23 -0
  33. package/dist/src/dataset/fortemi-dataset-execution.js +158 -0
  34. package/dist/src/dataset/fortemi-live-qualification.d.ts +4 -2
  35. package/dist/src/dataset/fortemi-live-qualification.js +20 -21
  36. package/dist/src/dataset/fortemi-run-receipt.d.ts +44 -0
  37. package/dist/src/dataset/fortemi-run-receipt.js +74 -0
  38. package/dist/src/dataset/index.d.ts +2 -0
  39. package/dist/src/dataset/index.js +2 -0
  40. package/dist/src/extensions/commands/definitions.js +24 -0
  41. package/dist/src/extensions/manifest.js +3 -0
  42. package/dist/src/mcp/server.mjs +2 -0
  43. package/dist/src/mcp/tools/writer-profiles.mjs +40 -0
  44. package/dist/src/models/model-capabilities.v1.json +11 -0
  45. package/dist/src/models/model-catalog.v1.json +8 -0
  46. package/dist/src/models/provider-policy.js +1 -1
  47. package/dist/src/network-analysis/analyzer.js +667 -0
  48. package/dist/src/network-analysis/citations.js +107 -0
  49. package/dist/src/network-analysis/forensics.js +132 -0
  50. package/dist/src/network-analysis/governance.js +216 -0
  51. package/dist/src/network-analysis/index.js +10 -0
  52. package/dist/src/network-analysis/probe.js +405 -0
  53. package/dist/src/network-analysis/recipes.js +88 -0
  54. package/dist/src/network-analysis/research.js +181 -0
  55. package/dist/src/network-analysis/termshark.js +252 -0
  56. package/dist/src/network-analysis/verification.js +171 -0
  57. package/dist/src/output-modes/registry.js +37 -6
  58. package/dist/src/output-modes/runtime.js +164 -28
  59. package/dist/src/providers/capability-matrix.yaml +45 -3
  60. package/dist/src/providers/provider-definitions.js +49 -0
  61. package/dist/src/providers/provider-inventory.js +1 -0
  62. package/dist/src/providers/transformation-receipt.js +3 -2
  63. package/dist/src/sessions/adapters/deepseek-harness.js +178 -0
  64. package/dist/src/sessions/batch-import.js +7 -0
  65. package/dist/src/sessions/contracts.js +2 -1
  66. package/dist/src/sessions/index.js +1 -0
  67. package/dist/src/sessions/workspace-discovery.js +5 -1
  68. package/dist/src/skills/deployer.js +6 -6
  69. package/dist/src/smiths/context-pipeline/workspace-context.js +7 -0
  70. package/dist/src/writing/channel-packs.js +13 -0
  71. package/dist/src/writing/contextual-diagnostics.js +142 -0
  72. package/dist/src/writing/example-generator.js +7 -6
  73. package/dist/src/writing/exemplar-selection.js +186 -0
  74. package/dist/src/writing/fidelity.js +61 -0
  75. package/dist/src/writing/validation-engine.js +32 -15
  76. package/dist/src/writing/voice-evaluation.js +301 -0
  77. package/dist/src/writing/voice-revision.js +201 -0
  78. package/dist/src/writing/writer-migration.js +216 -0
  79. package/dist/src/writing/writer-profile-legacy.js +145 -0
  80. package/dist/src/writing/writer-profile-store.js +117 -0
  81. package/dist/src/writing/writer-profile.js +222 -0
  82. package/dist/src/writing/writing-brief.js +166 -0
  83. package/dist/src/writing/writing-channels.js +63 -0
  84. package/dist/src/writing/writing-consumer.js +39 -0
  85. package/dist/src/writing/writing-receipt.js +266 -0
  86. package/package.json +1 -1
  87. package/schemas/dataset/fortemi-live-qualification-receipt.v2.schema.json +196 -0
  88. package/schemas/dataset/fortemi-run-receipt/validation-1.0.1/authority.json +12 -0
  89. package/schemas/dataset/fortemi-run-receipt/validation-1.0.1/run-receipt.schema.json +819 -0
  90. package/tools/agents/deploy-agents.mjs +7 -3
  91. package/tools/agents/providers/antigravity.mjs +1 -1
  92. package/tools/agents/providers/base.mjs +3 -2
  93. package/tools/agents/providers/deepseek-harness.mjs +66 -0
  94. package/tools/agents/providers/hermes.mjs +1 -1
  95. package/tools/agents/providers/openhuman.mjs +2 -2
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
2
2
  import { dirname, isAbsolute, resolve, } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
- import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
4
+ import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
5
5
  const JSON_CONTRACT_VERSION = '1.0.0';
6
6
  async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
7
7
  const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
@@ -55,6 +55,7 @@ Options:
55
55
  --provider-home <path> Override the provider home root (testing/portable homes)
56
56
  --codex-root <path> Explicitly authorize a shared Codex sessions/export root
57
57
  --omp-root <path> Explicitly authorize an OMP profile sessions root
58
+ --dsh-root <path> Explicitly authorize a DeepSeek Harness sessions root
58
59
  --confirm, --yes Confirm a persistent discovered batch import
59
60
  --lock-wait-ms <n> Maximum import-lease wait (default 5000)
60
61
  --inactivity-threshold <duration> Historical inactivity threshold (default 24h)
@@ -63,13 +64,39 @@ Options:
63
64
  --consumer <id> Select a named memory consumer for promotion
64
65
  --workspace <id>, --tag <tag>, --limit <n>, --cursor <n>
65
66
  --page-size <n> Extraction scan page size (default 250, maximum 500)
66
- --max-documents <n> Explicit extraction safety limit; returns a partial receipt`;
67
+ --max-documents <n> Explicit extraction safety limit; returns a partial receipt
68
+
69
+ Search filters:
70
+ --date-from <rfc3339>, --date-to <rfc3339>
71
+ --participant <actor>, --model <id>, --role <role>, --tool <name>
72
+ --entity <entity>, --sensitivity <class>, --extraction-state <state>
73
+ --control-events exclude|include|only (default: exclude)
74
+ Query syntax: FTS5 terms, quoted phrases, prefixes, AND/OR/NOT
75
+ Follow the opaque nextCursor with the same query and filters
76
+
77
+ Analytics / forensics:
78
+ --session <id>, --date-from <rfc3339>, --date-to <rfc3339>
79
+ --actor <id>, --participant <id>, --tool <name>, --status <status>
80
+ --provider <id>, --tag <tag>, --sensitivity <class>, --extraction-state <state>
81
+ --group-by tool|session|provider, --limit <1..5000>
82
+ --authorize-forensics Required for each authorized forensic invocation
83
+ --markdown Render a sanitized forensic timeline table`;
84
+ function printHelp(ctx, exitCode = EXIT.ok) {
85
+ if (ctx.args.includes('--json'))
86
+ emit(envelope('sessions.help', 'ok', { usage: HELP }, null));
87
+ else
88
+ console.log(HELP);
89
+ return { exitCode };
90
+ }
67
91
  export const sessionsHandler = {
68
92
  id: 'sessions',
69
93
  name: 'Sessions',
70
94
  description: 'Manage the normalized session catalog (the singular `session` command remains the launcher)',
71
95
  category: 'project',
72
96
  aliases: [],
97
+ async help(ctx) {
98
+ return printHelp(ctx);
99
+ },
73
100
  async execute(ctx) {
74
101
  const json = ctx.args.includes('--json');
75
102
  let parsed;
@@ -86,11 +113,8 @@ export const sessionsHandler = {
86
113
  return { exitCode: normalized.exitCode, message: normalized.error.message };
87
114
  }
88
115
  if (!parsed.command || parsed.flags.has('--help') || parsed.flags.has('-h')) {
89
- if (json)
90
- emit(envelope('sessions.help', 'ok', { usage: HELP }, null));
91
- else
92
- console.log(HELP);
93
- return { exitCode: parsed.command ? EXIT.ok : EXIT.usage };
116
+ const explicitHelp = parsed.flags.has('--help') || parsed.flags.has('-h');
117
+ return printHelp(ctx, explicitHelp ? EXIT.ok : EXIT.usage);
94
118
  }
95
119
  try {
96
120
  const result = await executeCommand(ctx, parsed);
@@ -623,7 +647,7 @@ async function importSource(ctx, args) {
623
647
  if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
624
648
  && provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
625
649
  && provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
626
- && provider !== 'openhuman' && provider !== 'pi' && provider !== 'omp' && provider !== 'warp' && provider !== 'devin-desktop') {
650
+ && provider !== 'openhuman' && provider !== 'pi' && provider !== 'omp' && provider !== 'deepseek-harness' && provider !== 'warp' && provider !== 'devin-desktop') {
627
651
  throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
628
652
  }
629
653
  const sourceId = requiredValue(args, '--source-id');
@@ -641,9 +665,10 @@ async function importSource(ctx, args) {
641
665
  const isOpenHuman = provider === 'openhuman';
642
666
  const isPi = provider === 'pi';
643
667
  const isOmp = provider === 'omp';
668
+ const isDsh = provider === 'deepseek-harness';
644
669
  const isWarp = provider === 'warp';
645
670
  const isDevinDesktop = provider === 'devin-desktop';
646
- const adapter = isOmp ? new OmpSessionAdapter() : isClaude
671
+ const adapter = isDsh ? new DeepSeekHarnessSessionAdapter() : isOmp ? new OmpSessionAdapter() : isClaude
647
672
  ? new ClaudeSessionAdapter()
648
673
  : isCodex
649
674
  ? new CodexSessionAdapter()
@@ -668,7 +693,7 @@ async function importSource(ctx, args) {
668
693
  : isDevinDesktop
669
694
  ? new DevinDesktopSessionAdapter()
670
695
  : new GenericSessionInterchangeAdapter();
671
- const locatorClass = isOmp ? 'omp-session-v3-jsonl' : isClaude
696
+ const locatorClass = isDsh ? 'deepseek-harness-session-v2-jsonl' : isOmp ? 'omp-session-v3-jsonl' : isClaude
672
697
  ? (input.endsWith('.hooks.jsonl') ? 'claude-hook-jsonl' : 'claude-transcript-jsonl')
673
698
  : isCodex
674
699
  ? (input.endsWith('.app-server.jsonl') ? 'codex-app-server-jsonl' : 'codex-rollout-jsonl')
@@ -700,7 +725,7 @@ async function importSource(ctx, args) {
700
725
  const probe = await adapter.inspect(selectedSource);
701
726
  const source = SessionSourceSchema.parse({
702
727
  contractVersion: SESSION_CONTRACT_VERSION, sourceId, provider,
703
- providerProfile: isOmp ? 'native-title-slot-v3' : isClaude
728
+ providerProfile: isDsh ? 'native-session-v2-jsonl' : isOmp ? 'native-title-slot-v3' : isClaude
704
729
  ? 'documented-local-jsonl'
705
730
  : isCodex
706
731
  ? 'app-server-v2-rollout-fallback'
@@ -724,7 +749,7 @@ async function importSource(ctx, args) {
724
749
  ? 'opt-in-cascade-transcript-hook'
725
750
  : 'manual-interchange',
726
751
  locatorClass, redactedLocator: redactSourceLocator(input),
727
- adapterVersion: isOmp ? OMP_ADAPTER_VERSION : isClaude
752
+ adapterVersion: isDsh ? DEEPSEEK_HARNESS_ADAPTER_VERSION : isOmp ? OMP_ADAPTER_VERSION : isClaude
728
753
  ? CLAUDE_ADAPTER_VERSION
729
754
  : isCodex
730
755
  ? CODEX_ADAPTER_VERSION
@@ -751,11 +776,11 @@ async function importSource(ctx, args) {
751
776
  disposition: isWarp
752
777
  ? 'manual-only'
753
778
  : isClaude || isCodex || isCopilot || isCursor || isFactory || isHermes
754
- || isOpenCode || isOpenClaw || isOpenHuman || isDevinDesktop || isOmp
779
+ || isOpenCode || isOpenClaw || isOpenHuman || isDevinDesktop || isOmp || isDsh || isPi
755
780
  ? 'implemented' : 'manual-only',
756
781
  operationalState: probe.operationalState,
757
782
  consistency: probe.consistency, authorizedAt: new Date().toISOString(),
758
- extensions: isOmp ? { 'native.omp': {} } : isClaude
783
+ extensions: isDsh ? { 'native.deepseek-harness': {} } : isOmp ? { 'native.omp': {} } : isClaude
759
784
  ? { 'native.claude': {} }
760
785
  : isCodex
761
786
  ? { 'native.codex': {} }
@@ -821,6 +846,8 @@ async function discoverWorkspace(ctx, args) {
821
846
  : undefined,
822
847
  ompRoot: args.values.has('--omp-root')
823
848
  ? resolve(ctx.cwd, args.values.get('--omp-root')) : undefined,
849
+ dshRoot: args.values.has('--dsh-root')
850
+ ? resolve(ctx.cwd, args.values.get('--dsh-root')) : undefined,
824
851
  codexRoot: args.values.has('--codex-root')
825
852
  ? resolve(ctx.cwd, args.values.get('--codex-root'))
826
853
  : undefined,
@@ -1029,6 +1056,19 @@ function providerDisposition(provider) {
1029
1056
  },
1030
1057
  };
1031
1058
  }
1059
+ if (provider === 'deepseek-harness') {
1060
+ return {
1061
+ provider, disposition: 'implemented', operationalState: 'available',
1062
+ supportedOperations: ['discover', 'inspect', 'stream'], acquisitionModes: ['jsonl'],
1063
+ reasonCode: null,
1064
+ remediation: 'Authorize an explicit DeepSeek Harness raw JSONL sessions root. Compressed .zstd histories must be exported as raw JSONL first.',
1065
+ evidence: {
1066
+ adapterVersion: DEEPSEEK_HARNESS_ADAPTER_VERSION,
1067
+ verifiedAt: '2026-09-05',
1068
+ documentation: 'https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/session/session-persistence-jsonl',
1069
+ },
1070
+ };
1071
+ }
1032
1072
  if (provider === 'pi') {
1033
1073
  return {
1034
1074
  provider, disposition: 'implemented', operationalState: 'available',
@@ -1138,7 +1178,7 @@ function parseArgs(argv) {
1138
1178
  '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
1139
1179
  '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
1140
1180
  '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
1141
- '--manifest', '--provider-home', '--codex-root', '--omp-root', '--lock-wait-ms', '--min-coverage', '--gap',
1181
+ '--manifest', '--provider-home', '--codex-root', '--omp-root', '--dsh-root', '--lock-wait-ms', '--min-coverage', '--gap',
1142
1182
  '--inactivity-threshold',
1143
1183
  '--control-events',
1144
1184
  '--session', '--status', '--actor', '--group-by',
@@ -206,7 +206,7 @@ async function handleSteward(args, ctx) {
206
206
  aiwg steward permissions migrate --apply Back up and atomically normalize config
207
207
 
208
208
  Providers:
209
- antigravity (agy), claude-code, codex, copilot, cursor, factory, opencode, pi, omp, warp, windsurf, hermes, openclaw
209
+ antigravity (agy), claude-code, codex, copilot, cursor, deepseek-harness (dsh), factory, opencode, pi, omp, warp, windsurf, hermes, openclaw
210
210
 
211
211
  Features:
212
212
  cron, agent_teams, tasks, mcp, behaviors, mission_control, daemon
@@ -173,6 +173,11 @@ export const catalogHandler = {
173
173
  description: "Model catalog commands (list, info, search)",
174
174
  category: "catalog",
175
175
  aliases: [],
176
+ async help() {
177
+ const { printCatalogHelp } = await import("../../catalog/cli.mjs");
178
+ printCatalogHelp();
179
+ return { exitCode: 0 };
180
+ },
176
181
  async execute(ctx) {
177
182
  try {
178
183
  // Dynamic import to avoid loading catalog dependencies unless needed
@@ -249,6 +249,36 @@ function resolveFrameworkDir(framework) {
249
249
  * aiwg-dev is contributor-only tooling — not for end users.
250
250
  */
251
251
  export const USE_ALL_DISALLOW = new Set(['aiwg-dev']);
252
+ /** Full-framework setup requires the corpus omitted by the lightweight CLI. */
253
+ async function bundledSetupPrerequisiteMessage(frameworkRoot) {
254
+ let packageName;
255
+ try {
256
+ packageName = JSON.parse(await fs.readFile(path.join(frameworkRoot, 'package.json'), 'utf8')).name;
257
+ }
258
+ catch {
259
+ // Embedded callers and source fixtures need not have package metadata.
260
+ return undefined;
261
+ }
262
+ if (packageName !== '@aiwg/cli')
263
+ return undefined;
264
+ const hasCorpus = (await Promise.all(['frameworks', 'addons'].map(async (kind) => {
265
+ try {
266
+ return (await fs.stat(path.join(frameworkRoot, 'agentic/code', kind))).isDirectory();
267
+ }
268
+ catch {
269
+ return false;
270
+ }
271
+ }))).every(Boolean);
272
+ if (hasCorpus)
273
+ return undefined;
274
+ return [
275
+ 'Bundled framework setup requires the full aiwg package. This @aiwg/cli installation does not include framework and addon sources.',
276
+ 'Replace the lightweight global package, then rerun your setup command:',
277
+ ' npm uninstall -g @aiwg/cli',
278
+ ' npm install -g aiwg',
279
+ '@aiwg/cli can still query signed web resources and deploy external project-local bundles.',
280
+ ].join('\n');
281
+ }
252
282
  /**
253
283
  * Discover all addon names from the filesystem, minus the disallow list.
254
284
  */
@@ -2306,6 +2336,13 @@ export class UseHandler {
2306
2336
  if (framework === 'cockpit') {
2307
2337
  return installCockpit(ctx, remainingArgs);
2308
2338
  }
2339
+ // Check before auto-init, global staging, or deployment can alter a project.
2340
+ // Web lookup does not materialize the corpus required by bundled setup.
2341
+ if (VALID_FRAMEWORKS.includes(framework)) {
2342
+ const prerequisite = await bundledSetupPrerequisiteMessage(ctx.frameworkRoot || await getFrameworkRoot());
2343
+ if (prerequisite)
2344
+ return { exitCode: 1, message: prerequisite };
2345
+ }
2309
2346
  // Structured logger for this invocation. Records go to both stderr (if
2310
2347
  // verbose level) and ~/.aiwg/logs/aiwg-YYYY-MM-DD.jsonl with full
2311
2348
  // provenance (invocation_id, aiwg_version, git_sha, etc.). #925.
@@ -0,0 +1,110 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { WriterProfileStore } from '../../writing/writer-profile-store.js';
4
+ import { compileWriterProfile, exportWriterProfile, inspectWriterProfile, revokeWriterSample } from '../../writing/writer-profile.js';
5
+ function argumentsFor(args) {
6
+ const positional = [];
7
+ const flags = {};
8
+ for (let i = 0; i < args.length; i++) {
9
+ const arg = args[i];
10
+ if (!arg.startsWith('--')) {
11
+ positional.push(arg);
12
+ continue;
13
+ }
14
+ if (!['--scope', '--output', '--revision', '--mode'].includes(arg) || flags[arg] !== undefined)
15
+ throw new Error('Unknown or duplicate writer-profile option');
16
+ const value = args[++i];
17
+ if (!value || value.startsWith('--'))
18
+ throw new Error('Missing writer-profile option value');
19
+ flags[arg] = value;
20
+ }
21
+ const scope = flags['--scope'] ?? 'project';
22
+ if (scope !== 'project' && scope !== 'user')
23
+ throw new Error('Scope must be project or user');
24
+ return { positional, flags, scope };
25
+ }
26
+ async function execute(ctx) {
27
+ const { positional: [action, id, extra, ...rest], flags, scope } = argumentsFor(ctx.args);
28
+ if (rest.length)
29
+ throw new Error('Too many writer-profile arguments');
30
+ const store = new WriterProfileStore({ cwd: ctx.cwd, scope });
31
+ let result;
32
+ const revision = () => {
33
+ const raw = flags['--revision'];
34
+ if (raw === undefined || !/^(0|[1-9]\d*)$/.test(raw) || !Number.isSafeInteger(Number(raw)))
35
+ throw new Error('A nonnegative --revision is required');
36
+ return Number(raw);
37
+ };
38
+ const destination = () => {
39
+ if (!flags['--output'])
40
+ throw new Error('--output is required; profile content is never printed');
41
+ return path.resolve(ctx.cwd, flags['--output']);
42
+ };
43
+ if (ctx.dryRun)
44
+ return { exitCode: 1, message: 'writer-profile does not support dry-run; use inspect before changing a profile' };
45
+ if (action === 'list' && !id)
46
+ result = await store.list();
47
+ else if (action === 'import' && id && !extra) {
48
+ const input = JSON.parse(await readFile(path.resolve(ctx.cwd, id), 'utf8'));
49
+ result = inspectWriterProfile(await store.save(input, revision()));
50
+ }
51
+ else if (action === 'inspect' && id && !extra)
52
+ result = inspectWriterProfile(await store.read(id));
53
+ else if (action === 'version' && id && !extra) {
54
+ const profile = await store.read(id);
55
+ result = { schemaVersion: profile.schemaVersion, version: profile.version, revision: profile.revision };
56
+ }
57
+ else if (action === 'delete' && id && !extra) {
58
+ await store.delete(id, revision());
59
+ result = { deleted: true };
60
+ }
61
+ else if (action === 'revoke' && id && extra) {
62
+ const profile = await store.read(id);
63
+ result = inspectWriterProfile(await store.save(revokeWriterSample(profile, extra), revision()));
64
+ }
65
+ else if ((action === 'export' || action === 'compile') && id && !extra) {
66
+ const file = destination();
67
+ const profile = await store.read(id);
68
+ let output;
69
+ if (action === 'compile') {
70
+ const compiled = compileWriterProfile(profile);
71
+ output = compiled.profile;
72
+ result = { written: true, fallback: compiled.fallback, diagnostics: compiled.diagnostics, activated: false };
73
+ }
74
+ else {
75
+ const mode = flags['--mode'] ?? 'shared';
76
+ if (mode !== 'shared' && mode !== 'private')
77
+ throw new Error('Export mode must be shared or private');
78
+ output = exportWriterProfile(profile, mode);
79
+ result = { written: true, mode };
80
+ }
81
+ await writeFile(file, JSON.stringify(output, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
82
+ }
83
+ else
84
+ throw new Error('Invalid writer-profile command; see aiwg writer-profile --help');
85
+ return { exitCode: 0, rawOutput: true, message: JSON.stringify(result, null, 2) };
86
+ }
87
+ export const writerProfileHandler = {
88
+ id: 'writer-profile', name: 'Writer Profiles', description: 'Manage opt-in author-controlled writer profile sidecars', category: 'project', aliases: [],
89
+ async help() {
90
+ return { exitCode: 0, rawOutput: true, message: [
91
+ 'aiwg writer-profile list|inspect <id>|version <id> [--scope project|user]',
92
+ 'aiwg writer-profile import <sidecar.json> --revision 0 [--scope project|user]',
93
+ 'aiwg writer-profile import <updated.json> --revision <current> [--scope project|user]',
94
+ 'aiwg writer-profile export <id> --output <new-file> [--mode shared|private] [--scope project|user]',
95
+ 'aiwg writer-profile compile <id> --output <new-file> [--scope project|user]',
96
+ 'aiwg writer-profile revoke <id> <sample-id> --revision <current> [--scope project|user]',
97
+ 'aiwg writer-profile delete <id> --revision <current> [--scope project|user]',
98
+ 'Import and compile do not select an output mode. Export destinations must not exist.',
99
+ ].join('\n') };
100
+ },
101
+ async execute(ctx) {
102
+ try {
103
+ return await execute(ctx);
104
+ }
105
+ catch {
106
+ return { exitCode: 1, message: 'Writer profile operation failed. Check the command, schema, file permissions and current revision; sample content is omitted.' };
107
+ }
108
+ },
109
+ };
110
+ //# sourceMappingURL=writer-profile.js.map
@@ -0,0 +1,122 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { randomUUID } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { parseWritingBrief, prepareWritingBrief, applyProofreadCorrections, writingBriefHash } from '../../writing/writing-brief.js';
5
+ import { assessWritingFidelity } from '../../writing/fidelity.js';
6
+ import { WriterProfileStore } from '../../writing/writer-profile-store.js';
7
+ import { compileWriterProfile } from '../../writing/writer-profile.js';
8
+ import { createWritingReceipt, writeWritingReceipt } from '../../writing/writing-receipt.js';
9
+ import { resolveOutputModes } from '../../output-modes/registry.js';
10
+ import { resolveProjectAiwgDirForWrite } from '../../config/project-artifacts.js';
11
+ import { readAiwgConfig } from '../../config/aiwg-config.js';
12
+ import { resolveArtifactOutputs, recordArtifactOutputProvenance } from '../../artifacts/output-policy.js';
13
+ async function execute(ctx) {
14
+ const [action, ...args] = ctx.args;
15
+ if (!['plan', 'proofread'].includes(action))
16
+ throw new Error('Expected writing plan or proofread');
17
+ const flags = {};
18
+ const corrections = [];
19
+ for (let index = 0; index < args.length; index++) {
20
+ const key = args[index], value = args[++index];
21
+ if (!['--brief', '--profile', '--channel', '--output', '--correction'].includes(key) || !value || value.startsWith('--'))
22
+ throw new Error('Invalid writing command option');
23
+ if (key === '--correction')
24
+ corrections.push(value);
25
+ else {
26
+ if (flags[key])
27
+ throw new Error('Duplicate writing command option');
28
+ flags[key] = value;
29
+ }
30
+ }
31
+ if (!flags['--brief'] || !flags['--profile'])
32
+ throw new Error('--brief and --profile are required');
33
+ const channel = flags['--channel'] ?? 'engineering';
34
+ if (!['article', 'social', 'email', 'engineering', 'conversation'].includes(channel))
35
+ throw new Error('Unknown writing channel');
36
+ const brief = parseWritingBrief(JSON.parse(await readFile(path.resolve(ctx.cwd, flags['--brief']), 'utf8')));
37
+ const resolved = await resolveOutputModes(ctx.cwd, ctx.frameworkRoot, [`writer-${flags['--profile']}`]);
38
+ const selected = resolved.modes.find(mode => mode.id === `writer-${flags['--profile']}`);
39
+ if (!selected || !['project', 'user'].includes(selected.source))
40
+ throw new Error('A stored writer sidecar is required');
41
+ const profile = await new WriterProfileStore({ cwd: ctx.cwd, scope: selected.source }).read(flags['--profile']);
42
+ const compiled = compileWriterProfile(profile);
43
+ let selectedCorrections = [];
44
+ let output;
45
+ let extension;
46
+ let validators;
47
+ if (action === 'plan') {
48
+ if (corrections.length)
49
+ throw new Error('Correction selection is only valid for proofreading');
50
+ output = JSON.stringify(prepareWritingBrief(brief, { profileId: profile.id, channel: channel }), null, 2) + '\n';
51
+ extension = 'json';
52
+ validators = [{ id: 'writing-brief-schema', version: '1', outcome: 'pass' }];
53
+ }
54
+ else {
55
+ if (brief.operation !== 'proofread-only')
56
+ throw new Error('Proofreading requires a proofread-only brief');
57
+ selectedCorrections = corrections.length ? corrections : brief.permissions.corrections.map(correction => correction.id);
58
+ const result = applyProofreadCorrections(brief, selectedCorrections);
59
+ if (!result.valid)
60
+ throw new Error('Authorized corrections require review');
61
+ const original = brief.inputs.find(input => input.id === brief.sourceInputId).text;
62
+ const selectedBrief = { ...brief, permissions: { ...brief.permissions, corrections: brief.permissions.corrections.filter(correction => selectedCorrections.includes(correction.id)) } };
63
+ const fidelity = assessWritingFidelity(original, result.text, selectedBrief);
64
+ if (fidelity.outcome === 'fail')
65
+ throw new Error('Proofreading fidelity guard rejected the result');
66
+ output = result.text;
67
+ extension = 'txt';
68
+ validators = [{ id: 'authorized-proofread-corrections', version: '1', outcome: 'pass' }];
69
+ if (fidelity.outcome === 'pass')
70
+ validators.push({ id: 'conservative-literal-review', version: '1', outcome: 'pass' });
71
+ }
72
+ const config = await readAiwgConfig(ctx.cwd);
73
+ const destinations = resolveArtifactOutputs({ project: config?.artifact_outputs, supportedDestinations: ['local-file'], explicitDestinations: flags['--output'] ? ['local-file'] : [] });
74
+ if (flags['--output'] && !destinations.presentations.includes('local-file'))
75
+ throw new Error('Local presentation export is disabled by project artifact policy');
76
+ if (ctx.dryRun)
77
+ return { exitCode: 0, rawOutput: true, message: JSON.stringify({ valid: true, action, writes: false, selected: resolved.modes.map(mode => mode.id), modelExecution: 'none' }) };
78
+ const id = `wr-${randomUUID()}`;
79
+ const root = resolveProjectAiwgDirForWrite(ctx.cwd);
80
+ const canonical = path.join(root, 'writing', 'outputs', `${id}.${extension}`);
81
+ const receipt = createWritingReceipt({
82
+ id, operation: brief.operation,
83
+ profile: { id: profile.id, version: profile.version, revision: profile.revision, cacheEpoch: profile.cacheEpoch, compiledModeSha256: writingBriefHash(JSON.stringify(compiled.profile)), fallback: compiled.fallback },
84
+ modes: resolved.modes.map(({ source: _source, sourcePath: _sourcePath, scope: _scope, ...mode }) => ({ id: mode.id, version: mode.version, profileSha256: writingBriefHash(JSON.stringify(mode)) })),
85
+ state: { selected: resolved.modes.map(mode => mode.id), delivered: [], applied: [], validated: [], deliveredTo: 'none', fallback: 'none' },
86
+ operationConfig: { action: action, correctionIds: selectedCorrections, channel: channel },
87
+ modelPrompt: { execution: 'none', promptSha256: writingBriefHash(JSON.stringify(brief)) },
88
+ inputs: [{ id: 'brief', role: 'brief', sha256: writingBriefHash(JSON.stringify(brief)) }, ...brief.inputs.map(input => ({ id: `input-${writingBriefHash(input.id)}`, role: input.kind, sha256: input.sha256 }))],
89
+ output: { sha256: writingBriefHash(output), path: canonical },
90
+ budget: { limit: 0, used: 0, unit: 'tokens', measurement: 'exact', tokenizerId: 'no-model-call', tokenizerVersion: '1' },
91
+ fallback: { applied: false }, validators, authorAcceptance: { status: 'pending' },
92
+ });
93
+ await mkdir(path.dirname(canonical), { recursive: true, mode: 0o700 });
94
+ await writeFile(canonical, output, { flag: 'wx', mode: 0o600 });
95
+ const recorded = await writeWritingReceipt(ctx.cwd, receipt);
96
+ let presentation;
97
+ if (flags['--output']) {
98
+ presentation = path.resolve(ctx.cwd, flags['--output']);
99
+ await writeFile(presentation, output, { flag: 'wx', mode: 0o600 });
100
+ await recordArtifactOutputProvenance(root, { canonicalPath: canonical, presentationDestination: 'local-file', presentationReference: presentation, authority: 'explicit-task' });
101
+ }
102
+ return { exitCode: 0, rawOutput: true, message: JSON.stringify({ canonical, receipt: recorded.path, presentation, action, selected: resolved.modes.map(mode => mode.id), appliedModes: [], modelExecution: 'none', publicationApproval: false }) };
103
+ }
104
+ export const writingHandler = {
105
+ id: 'writing', name: 'Writing', description: 'Prepare grounded writing plans and apply authorized proofreading corrections', category: 'project', aliases: [],
106
+ async help() {
107
+ return { exitCode: 0, rawOutput: true, message: [
108
+ 'aiwg writing plan --brief <brief.json> --profile <id> [--channel article|social|email|engineering|conversation] [--output <new-file>]',
109
+ 'aiwg writing proofread --brief <brief.json> --profile <id> [--correction <id>]... [--output <new-file>]',
110
+ 'Proofreading applies listed author-authorized corrections (all by default), without a model or voice rewrite.',
111
+ 'Canonical output and a receipt are written first. Optional local exports require a new file and project-policy allowance.',
112
+ 'Mode selections are inspected; this command does not claim provider response interception or publication approval.',
113
+ ].join('\n') };
114
+ },
115
+ async execute(ctx) { try {
116
+ return await execute(ctx);
117
+ }
118
+ catch {
119
+ return { exitCode: 1, message: 'Writing operation failed. Check the brief, profile, correction IDs, output destination and artifact policy. Private source text is omitted; any completed canonical artifacts remain available.' };
120
+ } },
121
+ };
122
+ //# sourceMappingURL=writing.js.map
@@ -77,7 +77,11 @@ export function getHookRegistry() {
77
77
  export async function run(args, options = {}) {
78
78
  const started = process.hrtime.bigint();
79
79
  const registry = await initRouter();
80
- const [rawCommand, ...commandArgs] = args;
80
+ // Normalize `help <command>` into the same non-executing help path.
81
+ const routedArgs = args[0] === 'help' && args[1] && !args[1].startsWith('-')
82
+ ? [args[1], ...args.slice(2), '--help']
83
+ : args;
84
+ const [rawCommand, ...commandArgs] = routedArgs;
81
85
  // No command - show help
82
86
  if (!rawCommand) {
83
87
  const helpHandler = registry.handlerMap.get('help');
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { homedir } from 'os';
9
- import { existsSync, readFileSync } from 'fs';
9
+ import { existsSync, readFileSync, statSync } from 'fs';
10
10
  import { isAbsolute, join, resolve } from 'path';
11
11
 
12
12
  export const DEFAULT_PROJECT_AIWG_DIR = '.aiwg';
@@ -75,10 +75,42 @@ export function resolveProjectControlDir(projectDir) {
75
75
  return resolve(projectDir, DEFAULT_PROJECT_AIWG_DIR);
76
76
  }
77
77
 
78
+ export function isProjectArtifactRootExternal(projectDir, env = process.env) {
79
+ return resolve(resolveProjectAiwgDir(projectDir, env)) !== resolve(resolveProjectControlDir(projectDir));
80
+ }
81
+
82
+ /**
83
+ * Resolve an artifact root for a payload write. Default local mode may create
84
+ * `.aiwg`; an explicitly external root must already be mounted/attached so a
85
+ * disconnected mount cannot be silently replaced by a new local directory.
86
+ */
87
+ export function resolveProjectAiwgDirForWrite(projectDir, env = process.env) {
88
+ const artifactRoot = resolveProjectAiwgDir(projectDir, env);
89
+ if (isProjectArtifactRootExternal(projectDir, env)) {
90
+ let available = false;
91
+ try {
92
+ available = existsSync(artifactRoot) && statSync(artifactRoot).isDirectory();
93
+ } catch {
94
+ available = false;
95
+ }
96
+ if (!available) {
97
+ throw new Error(
98
+ `Configured external AIWG artifact root is unavailable: ${artifactRoot}. ` +
99
+ 'Reconnect or attach the external corpus; AIWG will not fall back to repository-local .aiwg payload.',
100
+ );
101
+ }
102
+ }
103
+ return artifactRoot;
104
+ }
105
+
78
106
  export function projectAiwgPath(projectDir, ...segments) {
79
107
  return join(resolveProjectAiwgDir(projectDir), ...segments);
80
108
  }
81
109
 
110
+ export function projectAiwgWritePath(projectDir, ...segments) {
111
+ return join(resolveProjectAiwgDirForWrite(projectDir), ...segments);
112
+ }
113
+
82
114
  export function projectControlPath(projectDir, ...segments) {
83
115
  return join(resolveProjectControlDir(projectDir), ...segments);
84
116
  }
@@ -5,6 +5,6 @@
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
- export { AIWG_ARTIFACTS_PATH_ENV, DEFAULT_PROJECT_AIWG_DIR, PROJECT_AIWG_LOCATION_FILE, expandProjectArtifactPath, parseProjectArtifactLocation, projectAiwgPath, projectControlPath, readProjectArtifactLocation, resolveProjectAiwgDir, resolveProjectControlDir, } from './project-artifacts-runtime.mjs';
8
+ export { AIWG_ARTIFACTS_PATH_ENV, DEFAULT_PROJECT_AIWG_DIR, PROJECT_AIWG_LOCATION_FILE, expandProjectArtifactPath, parseProjectArtifactLocation, projectAiwgPath, projectAiwgWritePath, projectControlPath, readProjectArtifactLocation, resolveProjectAiwgDir, resolveProjectAiwgDirForWrite, resolveProjectControlDir, isProjectArtifactRootExternal, } from './project-artifacts-runtime.mjs';
9
9
  export { PROJECT_CONTROL_PLANE_FILES, auditProjectArtifactHealth, } from './project-artifacts-health.mjs';
10
10
  //# sourceMappingURL=project-artifacts.js.map
@@ -0,0 +1,23 @@
1
+ import type { McpClientLike } from "../storage/backends/fortemi.js";
2
+ import { type FortemiDatasetRunReceipt } from "./fortemi-run-receipt.js";
3
+ export declare const FORTEMI_DATASET_EXECUTION_TOOL = "manage_dataset_execution";
4
+ export declare function fortemiDatasetRequestDigest(request: Record<string, unknown>): string;
5
+ /** MCP transport binding; receipt verification is implemented independently in AIWG. */
6
+ export declare class FortemiDatasetExecutionClient {
7
+ private readonly client;
8
+ private readonly runs;
9
+ constructor(client: McpClientLike);
10
+ private call;
11
+ capabilities(): Promise<Record<string, unknown>>;
12
+ preview(request: Record<string, unknown>): Promise<Record<string, unknown>>;
13
+ /** The caller must approve the exact digest returned by read-only preview. */
14
+ execute(request: Record<string, unknown>, approvedRequestDigest: string): Promise<FortemiDatasetRunReceipt>;
15
+ private verifyResult;
16
+ private knownRun;
17
+ retry(runId: string, action?: "retry" | "resume"): Promise<FortemiDatasetRunReceipt>;
18
+ status(runId: string): Promise<Record<string, unknown>>;
19
+ checkpoint(runId: string): Promise<Record<string, unknown>>;
20
+ cancel(runId: string): Promise<Record<string, unknown>>;
21
+ archive(runId: string): Promise<Record<string, unknown>>;
22
+ }
23
+ //# sourceMappingURL=fortemi-dataset-execution.d.ts.map