@aiwg/cli 2026.9.16 → 2026.9.18

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.
@@ -401,6 +401,44 @@ providers:
401
401
  deploy_target: mixed
402
402
  aggregated_output: false
403
403
 
404
+ grok-build:
405
+ display_name: Grok Build
406
+ status: experimental
407
+ # xAI Grok Build CLI/TUI. Distinct from Grok Bot (`grokbot`).
408
+ # Wave 1 (#2575): native kernel skills + AGENTS.md. Agents/rules writers deferred #2577.
409
+ daemon_tier: unsupported
410
+ daemon_pty_adapter: false
411
+ artifact_paths:
412
+ agents: "Indexed: aiwg discover / aiwg show agent (native .grok/agents writer deferred #2577)"
413
+ commands: null
414
+ skills: .grok/skills/
415
+ skills_cross_agent: .grok/.aiwg/skills/ # --copy-all opt-in mirror
416
+ rules: "Indexed: aiwg show rule + AGENTS.md / host .grok/rules (AIWG writer deferred #2577)"
417
+ behaviors: null
418
+ native_features:
419
+ cron: false
420
+ agent_teams: false
421
+ tasks: false
422
+ mcp: true
423
+ behaviors: false
424
+ mission_control: false
425
+ daemon: false
426
+ emulation:
427
+ cron: null
428
+ agent_teams: null
429
+ tasks: aiwg-mc
430
+ mcp: null
431
+ behaviors: aiwg-mc
432
+ mission_control: aiwg-mc
433
+ daemon: null
434
+ hook_wiring:
435
+ at_link_support: false
436
+ hook_file: null
437
+ fallback: full-inject
438
+ context_file: AGENTS.md
439
+ deploy_target: mixed
440
+ aggregated_output: false
441
+
404
442
  hermes:
405
443
  display_name: Hermes
406
444
  status: stable
@@ -14,6 +14,7 @@ import { homedir } from 'node:os';
14
14
  import * as path from 'node:path';
15
15
  import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
16
16
  import { resolveGrokbotSkillsDir } from '../providers/grokbot-paths.js';
17
+ import { resolveGrokHome } from '../providers/grok-build-paths.js';
17
18
  export const hermesHome = resolveHermesHome;
18
19
  function piAgentDir() {
19
20
  const configured = process.env.PI_CODING_AGENT_DIR;
@@ -229,6 +230,18 @@ export const USER_SCOPE_PATHS = {
229
230
  behaviors: '',
230
231
  };
231
232
  },
233
+ get 'grok-build'() {
234
+ // Skills mirror to $GROK_HOME/skills. Agents/rules writers deferred #2577 —
235
+ // leave those user paths empty so mirroring does not invent empty trees.
236
+ const home = resolveGrokHome();
237
+ return {
238
+ agents: '',
239
+ skills: home ? path.join(home, 'skills') : '',
240
+ commands: '',
241
+ rules: '',
242
+ behaviors: '',
243
+ };
244
+ },
232
245
  factory: {
233
246
  // #1164 — Verified against Factory docs (docs.factory.ai/cli/configuration/skills).
234
247
  // Skills primary user-scope path is ~/.factory/skills/, NOT the
@@ -7,6 +7,8 @@ import { inspectInstallation } from '../../installation/manager.mjs';
7
7
  import { getPackageRoot } from '../../channel/manager.mjs';
8
8
  import { getProviderDefinition, normalizeProviderDefinitionId, resolveProviderPathValue, } from '../../providers/provider-definitions.js';
9
9
  import { resolveGrokbotSkillsDir } from '../../providers/grokbot-paths.js';
10
+ import { resolveGrokHome } from '../../providers/grok-build-paths.js';
11
+ import { runGrokInspect } from '../../providers/grok-build-inspect.js';
10
12
  import { diagnoseIntegratedProviderTransformationReceipt } from '../../providers/transformation-receipt-integration.js';
11
13
  import { diagnoseWorkspaceContext, providerContextContract, } from '../../smiths/context-pipeline/workspace-context.js';
12
14
  import { USER_SCOPE_PATHS } from '../scope-resolver.js';
@@ -37,6 +39,11 @@ const RESTART_NOTICES = {
37
39
  action: 'Start a new Grok Bot agent chat (or re-read skills) so deployed AIWG context and skills are visible.',
38
40
  reason: 'Grok Bot skill/context reload behavior is not yet verified; AIWG does not claim live refresh.',
39
41
  },
42
+ 'grok-build': {
43
+ policy: 'restart-required',
44
+ action: 'Restart the Grok Build session so it reloads .grok skills and AGENTS.md.',
45
+ reason: 'Grok Build reload semantics are not yet verified for live refresh; treat deploys as restart-required.',
46
+ },
40
47
  factory: {
41
48
  policy: 'restart-required',
42
49
  action: 'Restart the Factory droid runtime so it reloads deployed droids.',
@@ -435,6 +442,56 @@ export async function verifyProviderDeployment(options) {
435
442
  findings.push(finding(normalized, 'provider-artifacts-missing', 'blocking', `No deployed provider or kernel artifacts were found for ${normalized}.`, `Re-run aiwg use ${options.requestedBundles[0] ?? 'all'} --provider ${normalized}.`, { kernelPath, kernelCount, counts }));
436
443
  }
437
444
  }
445
+ if (normalized === 'grok-build') {
446
+ const expectedSkillNames = [];
447
+ // Prefer validating against kernel skills actually present under .grok/skills.
448
+ try {
449
+ const { readdirSync, existsSync: existsSyncFs } = await import('node:fs');
450
+ const skillsDir = path.resolve(options.projectRoot, '.grok', 'skills');
451
+ if (existsSyncFs(skillsDir)) {
452
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
453
+ if (entry.isDirectory() && existsSyncFs(path.resolve(skillsDir, entry.name, 'SKILL.md'))) {
454
+ expectedSkillNames.push(entry.name);
455
+ }
456
+ }
457
+ }
458
+ }
459
+ catch {
460
+ // best-effort expected set
461
+ }
462
+ const inspect = runGrokInspect({
463
+ cwd: options.projectRoot,
464
+ expected: {
465
+ instructionPaths: ['AGENTS.md'],
466
+ skillNames: expectedSkillNames.slice(0, 32),
467
+ },
468
+ });
469
+ if (inspect.status === 'absent') {
470
+ findings.push(finding(normalized, 'grok-inspect-absent', 'advisory', 'Grok Build CLI (`grok`) is not on PATH; skipped `grok inspect`.', inspect.remediation, { grokHome: resolveGrokHome(), cwd: options.projectRoot }));
471
+ }
472
+ else if (inspect.status === 'failed') {
473
+ const mismatchSummary = (inspect.mismatches ?? [])
474
+ .slice(0, 8)
475
+ .map((item) => `${item.kind}:${item.expected}`)
476
+ .join('; ');
477
+ findings.push(finding(normalized, 'grok-inspect-failed', 'advisory', mismatchSummary
478
+ ? `\`grok inspect\` did not confirm expected AIWG artifacts (${mismatchSummary}).`
479
+ : `\`grok inspect\` exited ${inspect.exitCode ?? 'unknown'}.`, 'Fix the Grok Build installation or redeploy, then re-run aiwg status --probe or aiwg use for grok-build.', {
480
+ exitCode: inspect.exitCode,
481
+ stderr: inspect.stderr.slice(0, 500),
482
+ cwd: inspect.cwd,
483
+ mismatches: inspect.mismatches?.slice(0, 12),
484
+ }));
485
+ }
486
+ else {
487
+ findings.push(finding(normalized, 'grok-inspect-ok', 'info', '`grok inspect` confirmed expected AIWG artifacts in the deployment target.', 'No action required; restart the Grok Build session if newly deployed skills are not visible.', {
488
+ binary: inspect.binary,
489
+ cwd: inspect.cwd,
490
+ parsedKeys: Object.keys(inspect.parsed).slice(0, 20),
491
+ checkedSkills: expectedSkillNames.slice(0, 32),
492
+ }));
493
+ }
494
+ }
438
495
  }
439
496
  findings.push(...await collectRegistryFindings(options, normalized, counts));
440
497
  findings.push(...await collectProviderReceiptFindings(options, normalized));
@@ -173,6 +173,7 @@ export function resolveDelivery(delivery) {
173
173
  * - hermes: MCP sidecar; rate-limit depends on upstream provider, operator
174
174
  * should tune. Conservative 10 default.
175
175
  * - grokbot: desktop multi-agent; conservative 4 until native evidence.
176
+ * - grok-build: experimental; conservative 4 until Wave 2/3 evidence.
176
177
  * - unknown: conservative 4 default.
177
178
  */
178
179
  export const PROVIDER_PARALLELISM_DEFAULTS = {
@@ -189,6 +190,7 @@ export const PROVIDER_PARALLELISM_DEFAULTS = {
189
190
  hermes: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
190
191
  // Desktop multi-agent; conservative until native concurrency evidence exists.
191
192
  grokbot: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
193
+ 'grok-build': { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
192
194
  // Conservative defaults for remaining stable/experimental harnesses (#249).
193
195
  openhuman: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
194
196
  omp: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
@@ -45,7 +45,7 @@ declare abstract class LineAdapter<T extends FileConfig> extends LocalAdapter<T>
45
45
  protected lineDiscovery(kind: string, config: T, request: AdapterRequest<T>, schema: Record<string, unknown>): Promise<AdapterDiscovery>;
46
46
  protected lines(config: T, request: AdapterRequest<T> & {
47
47
  checkpoint?: AdapterCheckpoint;
48
- }, parse: (line: string, ordinal: number) => unknown): AsyncIterable<AdapterRecord>;
48
+ }, parse: (line: string, ordinal: number, maxDepth: number) => unknown): AsyncIterable<AdapterRecord>;
49
49
  }
50
50
  export declare class JsonlAdapter extends LineAdapter<FileConfig> {
51
51
  #private;
@@ -108,6 +108,21 @@ function record(value, ordinal, sourceLocator) {
108
108
  const serialized = typeof value === 'string' ? value : JSON.stringify(value);
109
109
  return { logicalId: `sha256:${sha256Digest(`${sourceLocator}\0${ordinal}\0${serialized}`).value}`, ordinal, value, sourceLocator, contentDigest: sha256Digest(serialized) };
110
110
  }
111
+ function valueDepth(value) {
112
+ let maximum = 0;
113
+ const pending = [{ value, depth: 0 }];
114
+ while (pending.length > 0) {
115
+ const current = pending.pop();
116
+ if (current.value === null || typeof current.value !== 'object')
117
+ continue;
118
+ const depth = current.depth + 1;
119
+ maximum = Math.max(maximum, depth);
120
+ const children = Array.isArray(current.value) ? current.value : Object.values(current.value);
121
+ for (const child of children)
122
+ pending.push({ value: child, depth });
123
+ }
124
+ return maximum;
125
+ }
111
126
  function checkpoint(manifestValue, sourceIdentity, cursor) {
112
127
  return {
113
128
  contractVersion: DATASET_ADAPTER_CONTRACT_VERSION, kind: 'AdapterCheckpoint', adapter: { id: manifestValue.id, version: manifestValue.version },
@@ -227,7 +242,7 @@ class LineAdapter extends LocalAdapter {
227
242
  if (Buffer.byteLength(line) > limits.maxRecordBytes || ordinal >= limits.maxRecords)
228
243
  throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'The record or record count exceeds configured limits.'));
229
244
  if (line.trim() !== '')
230
- yield record(parse(line, ordinal), ordinal, config.path);
245
+ yield record(parse(line, ordinal, limits.maxDepth), ordinal, config.path);
231
246
  ordinal += 1;
232
247
  }
233
248
  }
@@ -243,12 +258,19 @@ export class JsonlAdapter extends LineAdapter {
243
258
  valid(value) { return isFileConfig(value); }
244
259
  discoverPath(config, request) { return this.lineDiscovery('jsonl', config, request, {}); }
245
260
  records(config, request) {
246
- return this.lines(config, request, (line, ordinal) => { try {
247
- return JSON.parse(line);
248
- }
249
- catch {
250
- throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_SCHEMA_DRIFT', `Malformed JSONL record at ordinal ${ordinal}.`));
251
- } });
261
+ return this.lines(config, request, (line, ordinal, maxDepth) => {
262
+ try {
263
+ const value = JSON.parse(line);
264
+ if (valueDepth(value) > maxDepth)
265
+ throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_RESOURCE_LIMIT', `JSONL record at ordinal ${ordinal} exceeds the configured nesting limit.`));
266
+ return value;
267
+ }
268
+ catch (error) {
269
+ if (error instanceof AdapterFailure)
270
+ throw error;
271
+ throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_SCHEMA_DRIFT', `Malformed JSONL record at ordinal ${ordinal}.`));
272
+ }
273
+ });
252
274
  }
253
275
  }
254
276
  function parseCsvLine(line) {
@@ -36,6 +36,7 @@ export interface DatasetConformanceManifest {
36
36
  export interface DatasetConformanceBinding {
37
37
  aiwgCommit: string;
38
38
  fortemiCommit?: string;
39
+ fortemiServerCommit?: string;
39
40
  packageDigests: Record<string, string>;
40
41
  schemaDigests: Record<string, string>;
41
42
  fixtureDigest: string;
@@ -75,7 +76,7 @@ export interface DatasetConformanceReceipt {
75
76
  stableEligible: boolean;
76
77
  };
77
78
  }
78
- export type DatasetConformanceDiagnosticCode = 'CONFORMANCE_MANIFEST_INVALID' | 'CONFORMANCE_CELL_DUPLICATE' | 'CONFORMANCE_REQUIRED_CELL_MISSING' | 'CONFORMANCE_FIXTURE_DIGEST_MISMATCH' | 'CONFORMANCE_RECEIPT_STALE' | 'CONFORMANCE_RECEIPT_UNVERIFIABLE' | 'CONFORMANCE_EVIDENCE_WEAKENED' | 'CONFORMANCE_MOCK_ONLY_STABLE' | 'CONFORMANCE_RESOURCE_ENVELOPE_MISSING' | 'CONFORMANCE_PENDING_STABLE_CELL' | 'CONFORMANCE_RESULT_FAILED' | 'CONFORMANCE_RESULT_DIGEST_MISMATCH' | 'CONFORMANCE_SENSITIVE_VALUE';
79
+ export type DatasetConformanceDiagnosticCode = 'CONFORMANCE_MANIFEST_INVALID' | 'CONFORMANCE_CELL_DUPLICATE' | 'CONFORMANCE_REQUIRED_CELL_MISSING' | 'CONFORMANCE_RESULT_DUPLICATE' | 'CONFORMANCE_RESULT_UNEXPECTED' | 'CONFORMANCE_FIXTURE_DIGEST_MISMATCH' | 'CONFORMANCE_RECEIPT_STALE' | 'CONFORMANCE_RECEIPT_UNVERIFIABLE' | 'CONFORMANCE_EVIDENCE_WEAKENED' | 'CONFORMANCE_MOCK_ONLY_STABLE' | 'CONFORMANCE_RESOURCE_ENVELOPE_MISSING' | 'CONFORMANCE_PENDING_STABLE_CELL' | 'CONFORMANCE_RESULT_FAILED' | 'CONFORMANCE_RESULT_DIGEST_MISMATCH' | 'CONFORMANCE_SENSITIVE_VALUE';
79
80
  export interface DatasetConformanceDiagnostic {
80
81
  code: DatasetConformanceDiagnosticCode;
81
82
  path: string;
@@ -68,7 +68,16 @@ export function verifyConformanceReceipt(manifest, receipt) {
68
68
  diagnostics.push({ code: 'CONFORMANCE_RECEIPT_UNVERIFIABLE', path: '/bindings/aiwgCommit', message: 'AIWG commit binding is invalid.' });
69
69
  if (Object.keys(receipt.bindings.packageDigests).length === 0 || Object.keys(receipt.bindings.schemaDigests).length === 0)
70
70
  diagnostics.push({ code: 'CONFORMANCE_RECEIPT_UNVERIFIABLE', path: '/bindings', message: 'Package and schema digest bindings are required.' });
71
- const resultById = new Map(receipt.results.map(result => [result.cellId, result]));
71
+ const expectedIds = new Set(manifest.cells.map(cell => cell.id));
72
+ const resultById = new Map();
73
+ receipt.results.forEach((result, index) => {
74
+ if (resultById.has(result.cellId))
75
+ diagnostics.push({ code: 'CONFORMANCE_RESULT_DUPLICATE', path: `/results/${index}/cellId`, message: `Cell ${result.cellId} occurs more than once.` });
76
+ else
77
+ resultById.set(result.cellId, result);
78
+ if (!expectedIds.has(result.cellId))
79
+ diagnostics.push({ code: 'CONFORMANCE_RESULT_UNEXPECTED', path: `/results/${index}/cellId`, message: `Cell ${result.cellId} is not declared by the manifest.` });
80
+ });
72
81
  manifest.cells.forEach(cell => {
73
82
  const result = resultById.get(cell.id);
74
83
  if (!result)
@@ -40,6 +40,16 @@ export interface FortemiDatasetLiveReceipt {
40
40
  startedAt: string;
41
41
  endedAt: string;
42
42
  }
43
+ /**
44
+ * Verify the independently captured, mutating Fortemi dataset qualification.
45
+ * The caller supplies the separately persisted run receipt so a wrapper cannot
46
+ * claim a successful execution without the receipt's canonical digest checks.
47
+ */
48
+ export declare function verifyFortemiDatasetExecutionQualification(input: {
49
+ qualification: unknown;
50
+ runReceipt: unknown;
51
+ expectedFortemiCommit: string;
52
+ }): string[];
43
53
  /** Read-only discovery. It never invokes a Fortemi tool, even when the proposed contract is present. */
44
54
  export declare function qualifyFortemiDatasetLivePreflight(input: {
45
55
  client: McpClientLike;
@@ -3,6 +3,7 @@ import { link, mkdir, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
4
  import { FORTEMI_DATASET_EXECUTION_TOOL } from "./fortemi-dataset-execution.js";
5
5
  import { endpointFingerprint, fortemiReceiptDigest, } from "../storage/fortemi-qualification-receipt.js";
6
+ import { verifyFortemiDatasetRunReceipt } from "./fortemi-run-receipt.js";
6
7
  export const FORTEMI_DATASET_LIVE_CONTRACT = "aiwg.fortemi-dataset-live-qualification/v2";
7
8
  const LEGACY_LIVE_CONTRACT = "aiwg.fortemi-dataset-live-qualification/v1";
8
9
  export const FORTEMI_DATASET_CAPABILITIES_TOOL = "dataset_capabilities";
@@ -19,6 +20,18 @@ const REQUIRED_TOOLS = [FORTEMI_DATASET_EXECUTION_TOOL];
19
20
  const REQUIRED_ACTIONS = ["capabilities", "preview", "execute", "status", "checkpoint", "cancel", "resume", "retry", "verify", "archive"];
20
21
  const MAX_TOOL_COUNT = 256;
21
22
  const MAX_SCHEMA_BYTES = 1_048_576;
23
+ const SENSITIVE = /(?:(?:password|passwd|api[-_]?key|authorization)\s*[:=]\s*[^\s"}]+|bearer\s+[a-z0-9._~+/-]+|-----BEGIN [A-Z ]+PRIVATE KEY-----)/iu;
24
+ const EXECUTION_QUALIFICATION_CONTRACT = "fortemi.lane-b.dataset-live-qualification.v1";
25
+ const EXECUTION_CHECKS = [
26
+ "published API identity and clean migrated destination",
27
+ "MCP initialize and capability discovery agree",
28
+ "schema-bound preview has no side effects",
29
+ "execute commits one synthetic record with valid redacted receipt",
30
+ "status, checkpoint, resume, and exact in-process replay are stable",
31
+ "fresh MCP process reconstructs byte-equivalent durable replay receipt",
32
+ "namespace-scoped archive cleanup is complete and idempotent",
33
+ ];
34
+ const EXECUTION_BOUNDARY = "Synthetic single-record Community dataset execution against a private ephemeral PostgreSQL destination. No shared Fortemi, vLLM, Ollama, GPU, personal container, or persistent data is used.";
22
35
  function record(value) {
23
36
  return value !== null && typeof value === "object" && !Array.isArray(value)
24
37
  ? value
@@ -28,6 +41,127 @@ function exactKeys(value, allowed) {
28
41
  const keys = Object.keys(value);
29
42
  return (keys.length === allowed.length && keys.every((key) => allowed.includes(key)));
30
43
  }
44
+ /**
45
+ * Verify the independently captured, mutating Fortemi dataset qualification.
46
+ * The caller supplies the separately persisted run receipt so a wrapper cannot
47
+ * claim a successful execution without the receipt's canonical digest checks.
48
+ */
49
+ export function verifyFortemiDatasetExecutionQualification(input) {
50
+ const errors = [];
51
+ const qualification = record(input.qualification);
52
+ const runReceipt = record(input.runReceipt);
53
+ if (!COMMIT.test(input.expectedFortemiCommit))
54
+ return ["CONFORMANCE_INVALID_FORTEMI_COMMIT"];
55
+ if (!qualification || !runReceipt)
56
+ return ["CONFORMANCE_FORTEMI_EXECUTION_EVIDENCE_INVALID"];
57
+ if (!exactKeys(qualification, [
58
+ "schemaVersion", "status", "boundedUnit", "source", "release",
59
+ "executableSha256", "checks", "mcpSessions", "boundary", "apiPid",
60
+ "health", "migrations", "descriptorSha256", "run", "finalDatabase",
61
+ "apiExit", "apiPidAbsent", "apiOutputBytes", "apiOutputSha256",
62
+ "scratchRemoved",
63
+ ]))
64
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_EVIDENCE_SHAPE_INVALID");
65
+ const source = qualification.source;
66
+ const release = qualification.release;
67
+ const checks = qualification.checks;
68
+ const sessions = qualification.mcpSessions;
69
+ const health = record(qualification.health);
70
+ const run = record(qualification.run);
71
+ const archive = record(run?.archive);
72
+ const finalDatabase = record(qualification.finalDatabase);
73
+ const apiExit = record(qualification.apiExit);
74
+ const receiptBindings = record(runReceipt.bindings);
75
+ const receiptCounts = record(runReceipt.counts);
76
+ const receiptEnvelope = record(runReceipt.resourceEnvelope);
77
+ const receiptRedaction = record(runReceipt.redaction);
78
+ if (SENSITIVE.test(JSON.stringify(qualification)) || SENSITIVE.test(JSON.stringify(runReceipt)))
79
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_SENSITIVE_VALUE");
80
+ if (qualification.schemaVersion !== EXECUTION_QUALIFICATION_CONTRACT ||
81
+ qualification.status !== "PASS" ||
82
+ source !== input.expectedFortemiCommit ||
83
+ typeof release !== "string" ||
84
+ !/^v\d{4}\.\d+\.\d+$/u.test(release) ||
85
+ typeof qualification.boundedUnit !== "string" ||
86
+ !/^fortemi-local-test-1000-[0-9a-f-]+\.service$/u.test(qualification.boundedUnit) ||
87
+ typeof qualification.apiPid !== "number" ||
88
+ !Number.isInteger(qualification.apiPid) || qualification.apiPid < 1 ||
89
+ qualification.boundary !== EXECUTION_BOUNDARY ||
90
+ typeof qualification.executableSha256 !== "string" ||
91
+ !/^[0-9a-f]{64}$/u.test(qualification.executableSha256) ||
92
+ typeof qualification.descriptorSha256 !== "string" ||
93
+ !/^[0-9a-f]{64}$/u.test(qualification.descriptorSha256) ||
94
+ typeof qualification.apiOutputSha256 !== "string" ||
95
+ !/^[0-9a-f]{64}$/u.test(qualification.apiOutputSha256))
96
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_BINDING_INVALID");
97
+ if (!Array.isArray(checks) ||
98
+ checks.length !== EXECUTION_CHECKS.length ||
99
+ !EXECUTION_CHECKS.every((check, index) => checks[index] === check))
100
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_CHECKS_INCOMPLETE");
101
+ if (!Array.isArray(sessions) ||
102
+ sessions.length !== 2 ||
103
+ !sessions.every((value, index) => {
104
+ const session = record(value);
105
+ return session &&
106
+ exactKeys(session, ["label", "pid", "stderrBytes", "closed"]) &&
107
+ session.label === (index === 0 ? "initial" : "restart") &&
108
+ typeof session.pid === "number" && Number.isInteger(session.pid) && session.pid > 0 &&
109
+ typeof session.stderrBytes === "number" && Number.isInteger(session.stderrBytes) && session.stderrBytes >= 0 &&
110
+ session.stderrBytes <= 65_536 && session.closed === true;
111
+ }))
112
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_SESSION_INVALID");
113
+ if (!health || health.status !== "healthy" || health.git_sha !== source ||
114
+ health.version !== String(release).slice(1) ||
115
+ record(health.lifecycle)?.ready !== true ||
116
+ typeof qualification.migrations !== "number" ||
117
+ !Number.isInteger(qualification.migrations) || qualification.migrations < 1)
118
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_RUNTIME_INVALID");
119
+ const receiptErrors = verifyFortemiDatasetRunReceipt(runReceipt);
120
+ if (receiptErrors.length)
121
+ errors.push("CONFORMANCE_FORTEMI_RUN_RECEIPT_INVALID", ...receiptErrors);
122
+ if (!run || !archive ||
123
+ !exactKeys(run, [
124
+ "runId", "namespaceId", "requestDigest", "receiptDigest", "state",
125
+ "verification", "checkpointSequence", "archive", "contentSha256",
126
+ "sourceContentRetained",
127
+ ]) ||
128
+ !exactKeys(archive, [
129
+ "namespaceId", "archived", "alreadyArchived", "unresolved", "complete",
130
+ "reasonCodes",
131
+ ]) ||
132
+ run.runId !== runReceipt.runId ||
133
+ run.namespaceId !== runReceipt.namespaceId ||
134
+ run.requestDigest !== runReceipt.requestDigest ||
135
+ run.receiptDigest !== runReceipt.receiptDigest ||
136
+ run.state !== runReceipt.state ||
137
+ run.verification !== "verified" ||
138
+ runReceipt.verification !== "verified" ||
139
+ run.checkpointSequence !== 1 ||
140
+ run.sourceContentRetained !== false ||
141
+ archive.namespaceId !== run.namespaceId || archive.complete !== true ||
142
+ archive.archived !== 1 || archive.alreadyArchived !== 0 ||
143
+ !Array.isArray(archive.unresolved) || archive.unresolved.length !== 0 ||
144
+ !Array.isArray(archive.reasonCodes) || archive.reasonCodes.length !== 0 ||
145
+ receiptBindings?.sourceRevision !== source ||
146
+ receiptCounts?.attempted !== 1 || receiptCounts?.committed !== 1 ||
147
+ receiptCounts?.rejected !== 0 || receiptEnvelope?.maxRecords !== 1 ||
148
+ receiptEnvelope?.allowOutboundNetwork !== false ||
149
+ receiptRedaction?.sourceContentIncluded !== false ||
150
+ receiptRedaction?.logicalIdentifiersIncluded !== false ||
151
+ receiptRedaction?.connectionDetailsIncluded !== false)
152
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_RECEIPT_MISMATCH");
153
+ if (!finalDatabase || finalDatabase.visibleNotes !== 0 ||
154
+ !exactKeys(finalDatabase, ["visibleNotes", "archivedNotes"]) ||
155
+ finalDatabase.archivedNotes !== 1 || !apiExit || apiExit.code !== 0 ||
156
+ !exactKeys(apiExit, ["code", "signal"]) ||
157
+ apiExit.signal !== null || qualification.apiPidAbsent !== true ||
158
+ qualification.scratchRemoved !== true ||
159
+ typeof qualification.apiOutputBytes !== "number" ||
160
+ !Number.isInteger(qualification.apiOutputBytes) ||
161
+ qualification.apiOutputBytes < 0 || qualification.apiOutputBytes > 262_144)
162
+ errors.push("CONFORMANCE_FORTEMI_EXECUTION_CLEANUP_INVALID");
163
+ return [...new Set(errors)];
164
+ }
31
165
  function material(receipt) {
32
166
  const { receiptDigest: _digest, ...rest } = receipt;
33
167
  return rest;
@@ -401,6 +401,44 @@ providers:
401
401
  deploy_target: mixed
402
402
  aggregated_output: false
403
403
 
404
+ grok-build:
405
+ display_name: Grok Build
406
+ status: experimental
407
+ # xAI Grok Build CLI/TUI. Distinct from Grok Bot (`grokbot`).
408
+ # Wave 1 (#2575): native kernel skills + AGENTS.md. Agents/rules writers deferred #2577.
409
+ daemon_tier: unsupported
410
+ daemon_pty_adapter: false
411
+ artifact_paths:
412
+ agents: "Indexed: aiwg discover / aiwg show agent (native .grok/agents writer deferred #2577)"
413
+ commands: null
414
+ skills: .grok/skills/
415
+ skills_cross_agent: .grok/.aiwg/skills/ # --copy-all opt-in mirror
416
+ rules: "Indexed: aiwg show rule + AGENTS.md / host .grok/rules (AIWG writer deferred #2577)"
417
+ behaviors: null
418
+ native_features:
419
+ cron: false
420
+ agent_teams: false
421
+ tasks: false
422
+ mcp: true
423
+ behaviors: false
424
+ mission_control: false
425
+ daemon: false
426
+ emulation:
427
+ cron: null
428
+ agent_teams: null
429
+ tasks: aiwg-mc
430
+ mcp: null
431
+ behaviors: aiwg-mc
432
+ mission_control: aiwg-mc
433
+ daemon: null
434
+ hook_wiring:
435
+ at_link_support: false
436
+ hook_file: null
437
+ fallback: full-inject
438
+ context_file: AGENTS.md
439
+ deploy_target: mixed
440
+ aggregated_output: false
441
+
404
442
  hermes:
405
443
  display_name: Hermes
406
444
  status: stable
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Grok Build verification via `grok inspect` when the binary is available.
3
+ * Presence-only remediation when absent — never invents inspect output.
4
+ *
5
+ * @issue #2575
6
+ */
7
+ import { accessSync, constants as fsConstants } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { spawnSync } from 'node:child_process';
10
+ export function grokExecutableCandidates(env, cwd, platform = process.platform) {
11
+ const pathApi = platform === 'win32' ? path.win32 : path.posix;
12
+ const pathDelimiter = platform === 'win32' ? ';' : ':';
13
+ const pathDirs = (env.PATH ?? env.Path ?? process.env.PATH ?? '')
14
+ .split(pathDelimiter)
15
+ // An empty PATH segment means the child cwd on POSIX and Windows.
16
+ .map((entry) => entry || '.');
17
+ const suffixes = platform === 'win32'
18
+ ? [...(env.PATHEXT || env.Pathext || '.COM;.EXE;.BAT;.CMD')
19
+ .split(';')
20
+ .map((entry) => entry.trim())
21
+ .filter(Boolean), '']
22
+ : [''];
23
+ const candidates = new Set();
24
+ for (const dir of pathDirs) {
25
+ const absoluteDir = pathApi.isAbsolute(dir) ? dir : pathApi.resolve(cwd, dir);
26
+ for (const suffix of suffixes)
27
+ candidates.add(pathApi.join(absoluteDir, `grok${suffix}`));
28
+ }
29
+ return [...candidates];
30
+ }
31
+ function whichGrok(env, cwd) {
32
+ // Resolve from the supplied environment and deployment cwd without a shell.
33
+ for (const candidate of grokExecutableCandidates(env, cwd)) {
34
+ try {
35
+ accessSync(candidate, process.platform === 'win32' ? fsConstants.F_OK : fsConstants.X_OK);
36
+ return candidate;
37
+ }
38
+ catch {
39
+ // try next
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ function tryParseJson(text) {
45
+ const trimmed = text.trim();
46
+ if (!trimmed)
47
+ return null;
48
+ try {
49
+ const value = JSON.parse(trimmed);
50
+ return value && typeof value === 'object' && !Array.isArray(value)
51
+ ? value
52
+ : null;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function asRecordArray(value) {
59
+ if (!Array.isArray(value))
60
+ return [];
61
+ return value.filter((entry) => Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry));
62
+ }
63
+ function pathMentions(haystack, needle) {
64
+ const normalizedHay = haystack.replace(/\\/g, '/');
65
+ const normalizedNeedle = needle.replace(/\\/g, '/');
66
+ return normalizedHay.includes(normalizedNeedle);
67
+ }
68
+ /**
69
+ * Validate a `grok inspect --json` report against artifacts AIWG intended to deploy.
70
+ * Shape follows xAI InspectReport (camelCase): projectInstructions, skills, agents, configSources.
71
+ */
72
+ export function validateGrokInspectReport(parsed, expected = {}) {
73
+ const mismatches = [];
74
+ const instructions = asRecordArray(parsed.projectInstructions);
75
+ const skills = asRecordArray(parsed.skills);
76
+ const agents = asRecordArray(parsed.agents);
77
+ const configSources = parsed.configSources;
78
+ const layers = configSources && typeof configSources === 'object' && !Array.isArray(configSources)
79
+ ? asRecordArray(configSources.layers)
80
+ : [];
81
+ const hasReportShape = Array.isArray(parsed.projectInstructions)
82
+ || Array.isArray(parsed.skills)
83
+ || Array.isArray(parsed.agents)
84
+ || Boolean(configSources);
85
+ if (!hasReportShape) {
86
+ mismatches.push({
87
+ kind: 'shape',
88
+ expected: 'InspectReport fields (projectInstructions/skills/agents/configSources)',
89
+ detail: `parsed keys: ${Object.keys(parsed).slice(0, 20).join(',') || '(none)'}`,
90
+ });
91
+ return mismatches;
92
+ }
93
+ for (const instructionPath of expected.instructionPaths ?? []) {
94
+ const found = instructions.some((entry) => typeof entry.path === 'string' && pathMentions(entry.path, instructionPath));
95
+ if (!found) {
96
+ mismatches.push({
97
+ kind: 'instructions',
98
+ expected: instructionPath,
99
+ detail: `not listed in projectInstructions (${instructions.length} entries)`,
100
+ });
101
+ }
102
+ }
103
+ for (const skillName of expected.skillNames ?? []) {
104
+ const found = skills.some((entry) => (typeof entry.name === 'string' && entry.name === skillName)
105
+ || (typeof entry.source === 'object' && entry.source !== null
106
+ && typeof entry.source.path === 'string'
107
+ && pathMentions(String(entry.source.path), skillName)));
108
+ if (!found) {
109
+ mismatches.push({
110
+ kind: 'skills',
111
+ expected: skillName,
112
+ detail: `not listed in skills (${skills.length} entries)`,
113
+ });
114
+ }
115
+ }
116
+ for (const agentName of expected.agentNames ?? []) {
117
+ const found = agents.some((entry) => typeof entry.name === 'string' && entry.name === agentName);
118
+ if (!found) {
119
+ mismatches.push({
120
+ kind: 'agents',
121
+ expected: agentName,
122
+ detail: `not listed in agents (${agents.length} entries)`,
123
+ });
124
+ }
125
+ }
126
+ for (const substring of expected.configPathSubstrings ?? []) {
127
+ const found = layers.some((entry) => typeof entry.path === 'string' && pathMentions(entry.path, substring));
128
+ if (!found) {
129
+ mismatches.push({
130
+ kind: 'config',
131
+ expected: substring,
132
+ detail: `not listed in configSources.layers (${layers.length} entries)`,
133
+ });
134
+ }
135
+ }
136
+ return mismatches;
137
+ }
138
+ const ABSENT_REMEDIATION = 'Install the Grok Build CLI so `grok` is on PATH, then re-run verification. '
139
+ + 'Until then AIWG only confirms provider registration and on-disk .grok paths — it does not invent inspect output.';
140
+ function isRunOptions(value) {
141
+ if (!value || typeof value !== 'object')
142
+ return false;
143
+ const keys = Object.keys(value);
144
+ if (keys.length === 0)
145
+ return false;
146
+ return keys.every((key) => key === 'env' || key === 'cwd' || key === 'expected');
147
+ }
148
+ /**
149
+ * Run `grok inspect` (prefer `--json` when the CLI accepts it).
150
+ * Accepts either a ProcessEnv (legacy) or an options bag with env/cwd/expected.
151
+ */
152
+ export function runGrokInspect(envOrOptions = process.env) {
153
+ const options = isRunOptions(envOrOptions)
154
+ ? envOrOptions
155
+ : { env: envOrOptions };
156
+ const env = options.env ?? process.env;
157
+ const cwd = options.cwd ?? process.cwd();
158
+ const binary = whichGrok(env, cwd);
159
+ if (!binary) {
160
+ return { status: 'absent', remediation: ABSENT_REMEDIATION };
161
+ }
162
+ // Prefer machine-readable inspect; fall back to plain inspect if --json is rejected.
163
+ // Use length/index checks — tuple `includes()` narrows incorrectly under TS (TS2345).
164
+ const argSets = [
165
+ ['inspect', '--json'],
166
+ ['inspect'],
167
+ ];
168
+ for (let i = 0; i < argSets.length; i++) {
169
+ const args = argSets[i];
170
+ const wantsJson = args.length > 1 && args[1] === '--json';
171
+ const result = spawnSync(binary, [...args], {
172
+ encoding: 'utf8',
173
+ env,
174
+ cwd,
175
+ timeout: 15_000,
176
+ maxBuffer: 2_000_000,
177
+ // npm exposes Grok through a .cmd shim on Windows; Node requires a shell
178
+ // for .cmd/.bat launchers. Native .exe installs stay shell-free.
179
+ shell: process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(binary),
180
+ });
181
+ if (result.error && result.error.code === 'ENOENT') {
182
+ return { status: 'absent', remediation: ABSENT_REMEDIATION };
183
+ }
184
+ const stdout = result.stdout || '';
185
+ const stderr = result.stderr || '';
186
+ if (result.status === 0) {
187
+ const parsed = tryParseJson(stdout);
188
+ if (!parsed) {
189
+ return {
190
+ status: 'failed',
191
+ binary,
192
+ exitCode: 0,
193
+ stderr,
194
+ stdout,
195
+ cwd,
196
+ mismatches: [{
197
+ kind: 'shape',
198
+ expected: 'JSON InspectReport',
199
+ detail: 'exit 0 but stdout was not a JSON object',
200
+ }],
201
+ };
202
+ }
203
+ const mismatches = validateGrokInspectReport(parsed, options.expected);
204
+ if (mismatches.length > 0) {
205
+ return {
206
+ status: 'failed',
207
+ binary,
208
+ exitCode: 0,
209
+ stderr,
210
+ stdout,
211
+ cwd,
212
+ mismatches,
213
+ };
214
+ }
215
+ return {
216
+ status: 'ok',
217
+ binary,
218
+ stdout,
219
+ parsed,
220
+ cwd,
221
+ mismatches: [],
222
+ };
223
+ }
224
+ if (wantsJson && /unknown|unrecognized|invalid/i.test(`${stderr}\n${stdout}`)) {
225
+ continue;
226
+ }
227
+ return {
228
+ status: 'failed',
229
+ binary,
230
+ exitCode: result.status,
231
+ stderr,
232
+ stdout,
233
+ cwd,
234
+ };
235
+ }
236
+ return {
237
+ status: 'failed',
238
+ binary,
239
+ exitCode: null,
240
+ stderr: 'grok inspect did not succeed',
241
+ stdout: '',
242
+ cwd,
243
+ };
244
+ }
245
+ //# sourceMappingURL=grok-build-inspect.js.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Grok Build home/path resolution — documented defaults with fail-closed overrides.
3
+ *
4
+ * Default `$GROK_HOME` is `~/.grok` (absolute after expand). Relative overrides,
5
+ * bare `~`, root-only paths, and null bytes are rejected.
6
+ *
7
+ * Distinct from Grok Bot (`grokbot` / `AIWG_GROKBOT_SKILLS_DIR`): never share
8
+ * roots or invent `~/.grokbot` / `.cursor` paths.
9
+ *
10
+ * @see https://docs.x.ai/build/settings
11
+ * @issue #2575
12
+ */
13
+ import { homedir } from 'node:os';
14
+ import * as path from 'node:path';
15
+ export const GROK_HOME_ENV = 'GROK_HOME';
16
+ export function isGrokFilesystemRoot(resolved, pathFlavor = path) {
17
+ return pathFlavor.parse(resolved).root === resolved;
18
+ }
19
+ function expandHomePrefix(raw, userHome = homedir()) {
20
+ if (raw === '~')
21
+ return raw;
22
+ if (raw.startsWith('~/'))
23
+ return path.join(userHome, raw.slice(2));
24
+ return raw;
25
+ }
26
+ /**
27
+ * Resolve Grok Build home directory.
28
+ * When unset, defaults to `~/.grok` (expanded). Explicit env must be absolute or `~/…`.
29
+ */
30
+ export function resolveGrokHomeResult(env = process.env, userHome = homedir()) {
31
+ const raw = (env[GROK_HOME_ENV] || '').trim();
32
+ const fromEnv = Boolean(raw);
33
+ const candidate = fromEnv ? raw : path.join(userHome, '.grok');
34
+ const expanded = expandHomePrefix(candidate, userHome);
35
+ if (expanded === '~' || expanded.startsWith('~')) {
36
+ return {
37
+ ok: false,
38
+ reason: 'not-absolute',
39
+ message: `${GROK_HOME_ENV} must be an absolute path or ~/… (got '${raw || '(default)'}'). Bare '~' is rejected.`,
40
+ };
41
+ }
42
+ if (!path.isAbsolute(expanded)) {
43
+ return {
44
+ ok: false,
45
+ reason: 'not-absolute',
46
+ message: `${GROK_HOME_ENV} must be an absolute path (got '${raw}'). Relative overrides are rejected.`,
47
+ };
48
+ }
49
+ if (raw.includes('\0') || expanded.includes('\0')) {
50
+ return {
51
+ ok: false,
52
+ reason: 'traversal',
53
+ message: `${GROK_HOME_ENV} contains an invalid path character.`,
54
+ };
55
+ }
56
+ const resolved = path.resolve(expanded);
57
+ if (!resolved || isGrokFilesystemRoot(resolved)) {
58
+ return {
59
+ ok: false,
60
+ reason: 'root',
61
+ message: `${GROK_HOME_ENV} resolves to filesystem root; refuse to deploy there.`,
62
+ };
63
+ }
64
+ return { ok: true, path: resolved, source: fromEnv ? 'env' : 'default' };
65
+ }
66
+ export function resolveGrokHome(env = process.env, userHome = homedir()) {
67
+ const result = resolveGrokHomeResult(env, userHome);
68
+ return result.ok ? result.path : null;
69
+ }
70
+ export function grokBuildProjectPaths() {
71
+ return {
72
+ skills: '.grok/skills',
73
+ agents: '.grok/agents',
74
+ rules: '.grok/rules',
75
+ hooks: '.grok/hooks',
76
+ config: '.grok/config.toml',
77
+ agentsMd: 'AGENTS.md',
78
+ };
79
+ }
80
+ export function grokBuildUserPaths(grokHome) {
81
+ return {
82
+ skills: path.join(grokHome, 'skills'),
83
+ agents: path.join(grokHome, 'agents'),
84
+ };
85
+ }
86
+ export function grokHomeRemediation(env = process.env) {
87
+ const result = resolveGrokHomeResult(env);
88
+ if (result.ok)
89
+ return `${GROK_HOME_ENV}=${result.path} (${result.source})`;
90
+ return result.message;
91
+ }
92
+ //# sourceMappingURL=grok-build-paths.js.map
@@ -21,6 +21,7 @@ const ProviderDefinitionSchema = z.object({
21
21
  'deepseek-harness',
22
22
  'factory',
23
23
  'grokbot',
24
+ 'grok-build',
24
25
  'hermes',
25
26
  'opencode',
26
27
  'openclaw',
@@ -38,7 +39,7 @@ const ProviderDefinitionSchema = z.object({
38
39
  upstream: z.object({
39
40
  source: z.string().url(),
40
41
  version: z.string().min(1),
41
- revision: z.string().regex(/^[0-9a-f]{40}$/),
42
+ revision: z.string().regex(/^[0-9a-f]{40}$/).refine((value) => value !== '0'.repeat(40), { message: 'upstream.revision must be a real reviewed SHA, not all-zero' }),
42
43
  runtime: z.string().min(1),
43
44
  lastVerified: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
44
45
  }).optional(),
@@ -143,6 +144,7 @@ export const PROVIDER_IDS = [
143
144
  'deepseek-harness',
144
145
  'factory',
145
146
  'grokbot',
147
+ 'grok-build',
146
148
  'hermes',
147
149
  'opencode',
148
150
  'openclaw',
@@ -218,6 +220,24 @@ const CONTEXT_CONTRACTS = {
218
220
  lastVerified: '2026-09-15',
219
221
  },
220
222
  },
223
+ 'grok-build': {
224
+ // config.toml is configuration (MCP/plugins/permissions), not a startup/context file.
225
+ // Hierarchical instructions: AGENTS.md + .grok/rules/*.md, root-to-cwd, deeper wins.
226
+ startupFiles: ['AGENTS.md', '.grok/rules/*.md'],
227
+ precedence: [
228
+ 'provider/system',
229
+ 'global ~/.grok instruction sources',
230
+ 'root-to-cwd AGENTS.md and .grok/rules/*.md (deeper files win on conflicts)',
231
+ '$GROK_HOME for user skills and user config',
232
+ ],
233
+ loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
234
+ bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: true, support: 'supported',
235
+ verification: {
236
+ method: 'xAI project-rules docs (AGENTS.md + .grok/rules hierarchical discovery); grok inspect when installed',
237
+ source: 'https://docs.x.ai/build/features/project-rules',
238
+ lastVerified: '2026-09-20',
239
+ },
240
+ },
221
241
  hermes: {
222
242
  startupFiles: ['.hermes.md', 'AGENTS.md'], precedence: ['provider/system', '.hermes.md', 'AGENTS.md'],
223
243
  loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
@@ -632,6 +652,72 @@ const BUILT_IN_SEEDS = [
632
652
  },
633
653
  matrixRef: 'grokbot',
634
654
  },
655
+ {
656
+ id: 'grok-build',
657
+ displayName: 'Grok Build',
658
+ aliases: [],
659
+ status: 'experimental',
660
+ builtIn: true,
661
+ upstream: {
662
+ source: 'https://github.com/xai-org/grok-build',
663
+ version: '1.0.38',
664
+ // Reviewed public tree (docs + inspect schema), not an all-zero placeholder.
665
+ revision: '4247f661689354b831191f11eeeac8424993fe3d',
666
+ runtime: 'Grok Build CLI',
667
+ lastVerified: '2026-09-20',
668
+ },
669
+ surfaces: {
670
+ primary: 'grok-build',
671
+ compatibility: [],
672
+ // Agents/rules native writers deferred #2577; host still discovers those dirs.
673
+ precedence: ['AGENTS.md', '.grok/skills/', '.grok/rules/ (host; AIWG writer deferred #2577)', '.grok/config.toml (config only)', '$GROK_HOME'],
674
+ related: [],
675
+ },
676
+ detection: { env: ['GROK_HOME'], process: ['grok'], capabilityId: 'grok-build' },
677
+ paths: {
678
+ deployTarget: 'mixed',
679
+ artifacts: {
680
+ // Wave 1 writes skills + AGENTS.md only; agents/rules indexed until #2577.
681
+ agents: null,
682
+ commands: null,
683
+ skills: '.grok/skills',
684
+ rules: null,
685
+ behaviors: null,
686
+ },
687
+ kernelSkills: '.grok/skills',
688
+ contextDiscovery: {
689
+ agents: '.grok/agents',
690
+ skills: '.grok/skills',
691
+ rules: '.grok/rules',
692
+ behaviors: null,
693
+ },
694
+ configFile: '.grok/config.toml',
695
+ contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
696
+ },
697
+ smithPaths: {
698
+ agents: null,
699
+ commands: null,
700
+ skills: '.grok/skills',
701
+ rules: null,
702
+ fileExtension: '.md',
703
+ configFile: '.grok/config.toml',
704
+ aggregated: false,
705
+ },
706
+ skillNamespace: {
707
+ deploymentGroup: 'deep-recursion',
708
+ pathType: 'project',
709
+ skillsBaseDir: '.grok/skills',
710
+ subdirLayout: true,
711
+ },
712
+ adapters: {
713
+ agentFormat: 'agents-md',
714
+ hookBridge: null,
715
+ mcpInjection: null,
716
+ contextAggregation: 'agents-md',
717
+ ruleFormat: 'agents-md-section',
718
+ },
719
+ matrixRef: 'grok-build',
720
+ },
635
721
  {
636
722
  id: 'hermes',
637
723
  aliases: [],
@@ -13,6 +13,7 @@ const PROVIDER_EXECUTABLES = {
13
13
  'deepseek-harness': ['dsh'],
14
14
  factory: ['droid'],
15
15
  // Grok Bot has no verified CLI executable name; omit rather than invent.
16
+ 'grok-build': ['grok'],
16
17
  hermes: ['hermes'],
17
18
  opencode: ['opencode'],
18
19
  openclaw: ['openclaw'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.9.16",
3
+ "version": "2026.9.18",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,7 +15,7 @@
15
15
  "$defs": {
16
16
  "version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" }, "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, "time": { "type": "string", "format": "date-time", "pattern": "Z$" },
17
17
  "digestMap": { "type": "object", "minProperties": 1, "additionalProperties": { "$ref": "#/$defs/digest" } },
18
- "bindings": { "type": "object", "additionalProperties": false, "required": ["aiwgCommit", "packageDigests", "schemaDigests", "fixtureDigest", "configurationDigest"], "properties": { "aiwgCommit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})$" }, "fortemiCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "packageDigests": { "$ref": "#/$defs/digestMap" }, "schemaDigests": { "$ref": "#/$defs/digestMap" }, "fixtureDigest": { "$ref": "#/$defs/digest" }, "descriptorDigest": { "$ref": "#/$defs/digest" }, "configurationDigest": { "$ref": "#/$defs/digest" } } },
18
+ "bindings": { "type": "object", "additionalProperties": false, "required": ["aiwgCommit", "packageDigests", "schemaDigests", "fixtureDigest", "configurationDigest"], "properties": { "aiwgCommit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})$" }, "fortemiCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "fortemiServerCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "packageDigests": { "$ref": "#/$defs/digestMap" }, "schemaDigests": { "$ref": "#/$defs/digestMap" }, "fixtureDigest": { "$ref": "#/$defs/digest" }, "descriptorDigest": { "$ref": "#/$defs/digest" }, "configurationDigest": { "$ref": "#/$defs/digest" } } },
19
19
  "evidence": { "type": "object", "additionalProperties": false, "required": ["kind", "reference", "digest"], "properties": { "kind": { "enum": ["fixture", "real-source", "cross-repo", "live-qualification"] }, "reference": { "type": "string", "minLength": 1 }, "digest": { "$ref": "#/$defs/digest" } } },
20
20
  "result": { "type": "object", "additionalProperties": false, "required": ["cellId", "status", "evidence", "observed"], "properties": { "cellId": { "type": "string", "minLength": 1 }, "status": { "enum": ["passed", "failed", "pending"] }, "diagnostic": { "type": "string", "minLength": 1 }, "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } }, "observed": { "type": "object", "additionalProperties": false, "required": ["networkAttempts"], "properties": { "records": { "type": "integer", "minimum": 0 }, "bytes": { "type": "integer", "minimum": 0 }, "durationMs": { "type": "integer", "minimum": 0 }, "networkAttempts": { "type": "integer", "minimum": 0 } } } } }
21
21
  }
@@ -20,7 +20,7 @@
20
20
  * --rules-only Deploy only rules (skip agents)
21
21
  * --dry-run Show what would be deployed without writing
22
22
  * --force Overwrite existing files
23
- * --provider <name> Target provider: antigravity (agy), claude (default), openai, codex, cursor, opencode, copilot, factory, grokbot, pi, omp, deepseek-harness (dsh), warp, devin, hermes, openhuman, or openclaw
23
+ * --provider <name> Target provider: antigravity (agy), claude (default), openai, codex, cursor, opencode, copilot, factory, grokbot, grok-build, pi, omp, deepseek-harness (dsh), warp, devin, hermes, openhuman, or openclaw
24
24
  * --model <name> Override model for all tiers (blanket)
25
25
  * --reasoning-model <name> Override model for reasoning tasks
26
26
  * --coding-model <name> Override model for coding tasks
@@ -123,7 +123,7 @@ const PROVIDER_ALIASES = {
123
123
  'dsh': 'deepseek-harness',
124
124
  };
125
125
 
126
- const AVAILABLE_PROVIDERS = ['antigravity', 'claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'pi', 'omp', 'deepseek-harness', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman', 'grokbot'];
126
+ const AVAILABLE_PROVIDERS = ['antigravity', 'claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'pi', 'omp', 'deepseek-harness', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman', 'grokbot', 'grok-build'];
127
127
 
128
128
  const UNSUPPORTED_PROVIDER_HINTS = {
129
129
  'devin-cli': [
@@ -651,7 +651,8 @@ Providers (all deploy agents, commands, skills, and rules):
651
651
  Paths: AGENTS.md, .agents/skills/, .dsh/aiwg.cordis.patch.yml
652
652
  devin - Devin Desktop (preferred; aliases: devin-desktop, windsurf)
653
653
  Paths: .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
654
- grokbot - Grok Bot (experimental; AGENTS.md bridge; skills only with AIWG_GROKBOT_SKILLS_DIR)
654
+ grokbot - Grok Bot (AGENTS.md bridge; skills only with AIWG_GROKBOT_SKILLS_DIR)
655
+ grok-build - Grok Build (experimental; .grok paths + $GROK_HOME; no bare grok alias)
655
656
  hermes - Hermes Agent (MCP-based integration)
656
657
  Skills: $HERMES_HOME/skills/ (user-global; defaults to ~/.hermes/skills/) | Agents: AGENTS.md
657
658
  Commands/Rules: served via MCP, not file-deployed
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Grok Build provider (experimental).
3
+ * Project skills under `.grok/skills`; user home via $GROK_HOME (default ~/.grok).
4
+ * Distinct from grokbot. No bare `grok` alias.
5
+ *
6
+ * Wave 1: native kernel skills + AGENTS.md bridge. Agents and rules are
7
+ * indexed/deferred until #2577 (Grok discovers `.grok/agents` and
8
+ * `.grok/rules`, but AIWG does not yet emit qualified writers for them).
9
+ *
10
+ * @issue #2575
11
+ */
12
+
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import {
16
+ createAgentsMdFromTemplate,
17
+ deploySkillsWithKernelRouting,
18
+ collectFrameworkArtifacts,
19
+ getAddonSkillDirs,
20
+ normalizeDeploymentMode,
21
+ resolveAiwgRoot,
22
+ } from './base.mjs';
23
+
24
+ export const name = 'grok-build';
25
+ export const aliases = [];
26
+ export const paths = {
27
+ // Agents/rules dirs exist in Grok Build, but Wave 1 does not write them.
28
+ agents: '',
29
+ skills: '.grok/skills',
30
+ rules: '',
31
+ hooks: '.grok/hooks',
32
+ config: '.grok/config.toml',
33
+ };
34
+ export const kernelSkillsPath = '.grok/skills';
35
+ /** Standard-tier opt-in mirror (`--copy-all`) lives under the project .aiwg tree. */
36
+ export const standardSkillsPath = '.grok/.aiwg/skills';
37
+
38
+ export const support = {
39
+ agents: 'indexed', // deferred native writer until #2577
40
+ commands: 'indexed', // discoverable through the AIWG index; no native command writer
41
+ skills: 'native',
42
+ rules: 'indexed', // deferred native writer until #2577; host still loads AGENTS.md + .grok/rules hierarchically
43
+ };
44
+
45
+ export const capabilities = {
46
+ skills: true,
47
+ rules: false, // indexed/deferred — do not claim native rule transforms yet
48
+ yamlFormat: false,
49
+ aggregatedOutput: false,
50
+ homeDirectoryDeploy: true,
51
+ parallelCommandAndSkillSurfaces: false,
52
+ };
53
+
54
+ const GROK_HOME_ENV = 'GROK_HOME';
55
+
56
+ export function resolveGrokHome(env = process.env, userHome = os.homedir()) {
57
+ const raw = (env[GROK_HOME_ENV] || '').trim();
58
+ const candidate = raw || path.join(userHome, '.grok');
59
+ if (candidate === '~') return null;
60
+ let expanded = candidate;
61
+ if (candidate.startsWith('~/')) expanded = path.join(userHome, candidate.slice(2));
62
+ if (!path.isAbsolute(expanded)) return null;
63
+ const resolved = path.resolve(expanded);
64
+ if (!resolved || path.parse(resolved).root === resolved) return null;
65
+ return resolved;
66
+ }
67
+
68
+ export function createAgentsMd(target, srcRoot, dryRun) {
69
+ createAgentsMdFromTemplate(
70
+ target,
71
+ resolveAiwgRoot(srcRoot) || srcRoot,
72
+ 'grok-build/AGENTS.md.aiwg-template',
73
+ dryRun,
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Kernel skills → `.grok/skills` (always).
79
+ * Standard skills → index-driven by default; `.grok/.aiwg/skills` with `--copy-all`.
80
+ */
81
+ export function deploySkills(skillDirs, targetDir, opts = {}) {
82
+ const kernelDest = path.join(targetDir, kernelSkillsPath);
83
+ const standardDest = path.join(targetDir, standardSkillsPath);
84
+ return deploySkillsWithKernelRouting(skillDirs, standardDest, kernelDest, {
85
+ ...opts,
86
+ copyStandardSkills: opts.copyStandardSkills === true,
87
+ });
88
+ }
89
+
90
+ export function deployAgents() {
91
+ return 0;
92
+ }
93
+
94
+ export function deployRules() {
95
+ return 0;
96
+ }
97
+
98
+ export async function postDeploy(target, opts = {}) {
99
+ if (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)) {
100
+ createAgentsMd(target, opts.srcRoot, opts.dryRun);
101
+ }
102
+ if (!opts.quiet) {
103
+ const home = resolveGrokHome(opts.env || process.env);
104
+ console.log(
105
+ `Grok Build (experimental): kernel skills → ${kernelSkillsPath}; ` +
106
+ `standard skills index-driven (opt-in mirror → ${standardSkillsPath} with --copy-all); ` +
107
+ `agents/rules indexed until #2577; user home → ${home || '(unresolved GROK_HOME)'}. ` +
108
+ 'Distinct from grokbot.',
109
+ );
110
+ }
111
+ }
112
+
113
+ export function getFileExtension() {
114
+ return '.md';
115
+ }
116
+
117
+ export async function deploy(opts) {
118
+ const mode = normalizeDeploymentMode(opts.mode);
119
+ const skillDirs = [...getAddonSkillDirs(opts.srcRoot)];
120
+ skillDirs.push(
121
+ ...collectFrameworkArtifacts(opts.srcRoot, mode, {
122
+ includeAgents: false,
123
+ includeCommands: false,
124
+ includeSkills: true,
125
+ includeRules: false,
126
+ }).skills,
127
+ );
128
+ let count = 0;
129
+ if (!opts.commandsOnly && !opts.rulesOnly) {
130
+ const result = deploySkills(skillDirs, opts.target, opts);
131
+ count += (result?.kernel ?? 0) + (result?.standardCopied ?? 0);
132
+ }
133
+ await postDeploy(opts.target, opts);
134
+ return count;
135
+ }
136
+
137
+ export default {
138
+ name,
139
+ aliases,
140
+ paths,
141
+ kernelSkillsPath,
142
+ standardSkillsPath,
143
+ support,
144
+ capabilities,
145
+ resolveGrokHome,
146
+ createAgentsMd,
147
+ deploySkills,
148
+ deployAgents,
149
+ deployRules,
150
+ postDeploy,
151
+ getFileExtension,
152
+ deploy,
153
+ };