@skillstech/thunderlang 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.mjs CHANGED
@@ -5,8 +5,8 @@
5
5
  // NOT `dist/` , OpenThunder's scanner excludes dist/node_modules, so proof artifacts must live in a
6
6
  // committed, scannable location. `.intent/` mirrors the ecosystem's dot-dir convention (.openthunder/).
7
7
  //
8
- // intent check <file> parse + semantic diagnostics (exit 1 on error)
9
- // intent graph <file> [--out .intent] contract-graph.json + architecture-graph.json
8
+ // thunder check <file> parse + semantic diagnostics (exit 1 on error)
9
+ // thunder graph <file> [--out .intent] contract-graph.json + architecture-graph.json
10
10
  // intent proof <file> [--out .intent] .intent-proof.json
11
11
  // intent build <file> [--out .intent] [--no-ai] all artifacts + docs + mermaid + testplan
12
12
  //
@@ -69,6 +69,11 @@ import { parseEventLog, serializeEventLog, recordEvent, timeline } from './ai-ev
69
69
  // Recursively collect supported source files, skipping vendored / build dirs.
70
70
  const LIFT_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.rs', '.pl', '.pm'];
71
71
  const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.next', 'build', '.intent', 'coverage', '.vercel']);
72
+ // ThunderLang source files. `.thunder` is the canonical public extension; `.tl` is an
73
+ // accepted shorthand; `.intent` stays supported so legacy IntentLang sources keep working.
74
+ const SOURCE_EXTS = ['.thunder', '.tl', '.intent'];
75
+ const isSourceFile = (name) => SOURCE_EXTS.some((e) => name.endsWith(e));
76
+ const stripSourceExt = (name) => name.replace(/\.(thunder|tl|intent)$/i, '');
72
77
  function collectFiles(root, acc = []) {
73
78
  for (const name of readdirSync(root)) {
74
79
  if (SKIP_DIRS.has(name)) continue;
@@ -80,15 +85,15 @@ function collectFiles(root, acc = []) {
80
85
  return acc;
81
86
  }
82
87
 
83
- // Walk a directory for authored .intent files (skips the .intent/ output dir).
88
+ // Walk a directory for authored source files (skips the .intent/ output dir).
84
89
  function collectIntents(root, acc = []) {
85
90
  const st = statSync(root);
86
- if (!st.isDirectory()) return root.endsWith('.intent') ? [root] : acc;
91
+ if (!st.isDirectory()) return isSourceFile(root) ? [root] : acc;
87
92
  for (const name of readdirSync(root)) {
88
93
  if (SKIP_DIRS.has(name)) continue;
89
94
  const full = join(root, name);
90
95
  if (statSync(full).isDirectory()) collectIntents(full, acc);
91
- else if (name.endsWith('.intent')) acc.push(full);
96
+ else if (isSourceFile(name)) acc.push(full);
92
97
  }
93
98
  return acc;
94
99
  }
@@ -197,7 +202,7 @@ function printDiagnostics(diags) {
197
202
  return errors;
198
203
  }
199
204
 
200
- const HELP = `intent , the deterministic ThunderLang compiler (no AI required)
205
+ const HELP = `thunder , the deterministic ThunderLang compiler (no AI required)
201
206
 
202
207
  usage: intent <command> <file> [options]
203
208
 
@@ -229,7 +234,7 @@ Author & check
229
234
  schema emit the canonical graph schema + diagnostic catalog
230
235
  explain <IL-CODE> explain a diagnostic code (area, severity, what it blocks)
231
236
  rules [--json] list the whole canonical diagnostic catalog
232
- notes <file> [--lens <lens>] [--json] IntentLens: the compiled note blocks by lens (not verification)
237
+ notes <file> [--lens <lens>] [--json] ThunderLens: the compiled note blocks by lens (not verification)
233
238
  docs <file> [--lens <lens>] [--out <dir>] render a mission as Markdown docs (per-audience with --lens)
234
239
  code-actions <file> [--json] available quick-fixes, safety-graded (safe | reviewable)
235
240
  apply-fix <file> [--write] apply the SAFE autocorrects (header aliases, stray colons)
@@ -281,7 +286,7 @@ function main() {
281
286
  else console.log(JSON.stringify(out, null, 2));
282
287
  return;
283
288
  }
284
- // `intent proof --schema` emits the canonical proof envelope JSON Schema (no file needed).
289
+ // `thunder proof --schema` emits the canonical proof envelope JSON Schema (no file needed).
285
290
  // This is the "shared envelope" schema siblings sign (STW) and re-verify (RM/OT) against.
286
291
  if (cmd === 'proof' && args.schema) {
287
292
  console.log(JSON.stringify(intentProofJsonSchema(), null, 2));
@@ -291,11 +296,11 @@ function main() {
291
296
  // drift / tampering) and the proof's claims re-derive from the source.
292
297
  if (cmd === 'verify') {
293
298
  const proofPath = args._[0];
294
- if (!proofPath) { console.error('usage: intent verify <proof.json> [<source.intent>]'); process.exit(2); return; }
299
+ if (!proofPath) { console.error('usage: thunder verify <proof.json> [<source.intent>]'); process.exit(2); return; }
295
300
  let proof;
296
- try { proof = JSON.parse(readFileSync(proofPath, 'utf8')); } catch { console.error('intent verify: proof is not valid JSON'); process.exit(2); return; }
301
+ try { proof = JSON.parse(readFileSync(proofPath, 'utf8')); } catch { console.error('thunder verify: proof is not valid JSON'); process.exit(2); return; }
297
302
  const srcPath = args._[1] || proof.sourceFile;
298
- if (!srcPath || !existsSync(srcPath)) { console.error(`intent verify: source not found (${srcPath || 'none'}). Pass it: intent verify <proof.json> <source.intent>`); process.exit(2); return; }
303
+ if (!srcPath || !existsSync(srcPath)) { console.error(`thunder verify: source not found (${srcPath || 'none'}). Pass it: thunder verify <proof.json> <source.intent>`); process.exit(2); return; }
299
304
  const src = readFileSync(srcPath, 'utf8');
300
305
  const ast = parseIntent(src);
301
306
  const diags = semanticDiagnostics(ast);
@@ -309,7 +314,7 @@ function main() {
309
314
  const valid = structure.valid && hashMatch && semanticMatch && guaranteesMatch && neverMatch;
310
315
  const result = { schema: 'intent-verify-v1', proof: proofPath, source: srcPath, valid, checks: { wellFormed: structure.valid, hashMatch, semanticMatch, guaranteesMatch, neverMatch }, structureErrors: structure.errors };
311
316
  if (args.json) { console.log(JSON.stringify(result, null, 2)); process.exit(valid ? 0 : 1); return; }
312
- console.log(`intent verify ${basename(proofPath)}: ${valid ? 'VALID' : 'FAILED'} (source ${basename(srcPath)})`);
317
+ console.log(`thunder verify ${basename(proofPath)}: ${valid ? 'VALID' : 'FAILED'} (source ${basename(srcPath)})`);
313
318
  if (!structure.valid) { console.log(` X proof is not a well-formed intent-proof-v1 document:`); for (const e of structure.errors) console.log(` ${e.path || '(root)'}: ${e.message}`); }
314
319
  if (!hashMatch) console.log(' X source hash does not match , the source has changed since the proof was generated (drift or tampering).');
315
320
  if (!semanticMatch) console.log(' X the proof claims a different semantic result than the source produces now.');
@@ -320,13 +325,13 @@ function main() {
320
325
  return;
321
326
  }
322
327
 
323
- // Explain a diagnostic code from the canonical catalog. `intent explain IL-DEC-001`.
328
+ // Explain a diagnostic code from the canonical catalog. `thunder explain IL-DEC-001`.
324
329
  if (cmd === 'explain') {
325
330
  const code = file;
326
- if (!code) { console.error('usage: intent explain <IL-CODE>'); process.exit(2); return; }
331
+ if (!code) { console.error('usage: thunder explain <IL-CODE>'); process.exit(2); return; }
327
332
  const rule = ALL_DIAGNOSTICS.find((r) => r.ruleId.toLowerCase() === code.toLowerCase());
328
333
  if (args.json) { console.log(JSON.stringify(rule || { ruleId: code, found: false }, null, 2)); process.exit(rule ? 0 : 1); return; }
329
- if (!rule) { console.error(`intent explain: "${code}" is not in the diagnostic catalog. Run "intent rules" for the full list.`); process.exit(1); return; }
334
+ if (!rule) { console.error(`thunder explain: "${code}" is not in the diagnostic catalog. Run "intent rules" for the full list.`); process.exit(1); return; }
330
335
  console.log(`${rule.ruleId} (area: ${rule.area})`);
331
336
  console.log(` ${rule.summary}`);
332
337
  console.log(` severity: ${rule.severity}${rule.blocks && rule.blocks.length ? ` | blocks: ${rule.blocks.join(', ')}` : ' | does not block a phase'}`);
@@ -338,7 +343,7 @@ function main() {
338
343
  if (args.json) { console.log(JSON.stringify(ALL_DIAGNOSTICS, null, 2)); return; }
339
344
  const byArea = {};
340
345
  for (const r of ALL_DIAGNOSTICS) (byArea[r.area] ||= []).push(r);
341
- console.log(`intent rules: ${ALL_DIAGNOSTICS.length} diagnostics in ${Object.keys(byArea).length} areas\n`);
346
+ console.log(`thunder rules: ${ALL_DIAGNOSTICS.length} diagnostics in ${Object.keys(byArea).length} areas\n`);
342
347
  for (const area of Object.keys(byArea).sort()) {
343
348
  console.log(`${area}`);
344
349
  for (const r of byArea[area]) {
@@ -350,7 +355,7 @@ function main() {
350
355
  return;
351
356
  }
352
357
 
353
- // `intent notes <file> [--lens <lens>] [--json]` , the IntentLens report: the compiled
358
+ // `thunder notes <file> [--lens <lens>] [--json]` , the ThunderLens report: the compiled
354
359
  // semantic comments (`note <lens>:`) grouped by lens, each with its target and source
355
360
  // line. Notes explain meaning for a reader; they are NEVER verification.
356
361
  if (cmd === 'notes') {
@@ -363,7 +368,7 @@ function main() {
363
368
  return;
364
369
  }
365
370
  const scope = args.lens ? ` (lens: ${args.lens})` : '';
366
- console.log(`intent notes ${basename(file)}${scope}: ${notes.length} note${notes.length === 1 ? '' : 's'}`);
371
+ console.log(`thunder notes ${basename(file)}${scope}: ${notes.length} note${notes.length === 1 ? '' : 's'}`);
367
372
  const known = new Set(KNOWN_LENSES);
368
373
  const byLens = {};
369
374
  for (const n of notes) (byLens[n.lens] ||= []).push(n);
@@ -378,7 +383,7 @@ function main() {
378
383
  return;
379
384
  }
380
385
 
381
- // `intent docs <file> [--lens <lens>] [--out <dir>]` , render a mission as Markdown docs.
386
+ // `thunder docs <file> [--lens <lens>] [--out <dir>]` , render a mission as Markdown docs.
382
387
  // With --lens, produce an audience-specific doc with that lens's notes woven inline.
383
388
  if (cmd === 'docs') {
384
389
  if (!file) { console.error('usage: intent docs <file> [--lens <lens>] [--out <dir>]'); process.exit(2); return; }
@@ -394,19 +399,19 @@ function main() {
394
399
  return;
395
400
  }
396
401
 
397
- // `intent code-actions <file> [--json]` , the available quick-fixes, each safety-graded
402
+ // `thunder code-actions <file> [--json]` , the available quick-fixes, each safety-graded
398
403
  // (safe autocorrects + reviewable diagnostic fixes). The IDE lightbulb's data source.
399
404
  if (cmd === 'code-actions') {
400
405
  if (!file) { console.error('usage: intent code-actions <file> [--json]'); process.exit(2); return; }
401
406
  const source = readFileSync(file, 'utf8');
402
407
  const actions = getCodeActions(source, semanticDiagnostics(parseIntent(source)));
403
408
  if (args.json) { console.log(JSON.stringify({ schema: 'intent-code-actions-v1', count: actions.length, actions }, null, 2)); return; }
404
- console.log(`intent code-actions ${basename(file)}: ${actions.length} action${actions.length === 1 ? '' : 's'}`);
409
+ console.log(`thunder code-actions ${basename(file)}: ${actions.length} action${actions.length === 1 ? '' : 's'}`);
405
410
  for (const a of actions) console.log(` [${a.safety}] ${a.kind}${a.line ? ` (line ${a.line})` : ''} ${a.title}`);
406
411
  return;
407
412
  }
408
413
 
409
- // `intent apply-fix <file> [--write]` , apply the SAFE autocorrects only (header aliases,
414
+ // `thunder apply-fix <file> [--write]` , apply the SAFE autocorrects only (header aliases,
410
415
  // stray colons). Reviewable quick-fixes are reported, never applied blindly.
411
416
  if (cmd === 'apply-fix') {
412
417
  if (!file) { console.error('usage: intent apply-fix <file> [--write]'); process.exit(2); return; }
@@ -415,7 +420,7 @@ function main() {
415
420
  const reviewable = getCodeActions(source, semanticDiagnostics(parseIntent(source))).filter((a) => a.safety !== 'safe');
416
421
  if (args.json) { console.log(JSON.stringify({ applied: changes, reviewableRemaining: reviewable.length, changed: fixed !== source }, null, 2)); }
417
422
  else {
418
- console.log(`intent apply-fix ${basename(file)}: ${changes.length} safe fix${changes.length === 1 ? '' : 'es'}${args.write ? ' applied' : ' (dry run; pass --write)'}`);
423
+ console.log(`thunder apply-fix ${basename(file)}: ${changes.length} safe fix${changes.length === 1 ? '' : 'es'}${args.write ? ' applied' : ' (dry run; pass --write)'}`);
419
424
  for (const c of changes) console.log(` line ${c.line}: "${c.from}" -> "${c.to}" [${c.rule}]`);
420
425
  if (reviewable.length) console.log(` ${reviewable.length} reviewable quick-fix(es) left for a human (run: intent code-actions ${basename(file)})`);
421
426
  }
@@ -423,12 +428,12 @@ function main() {
423
428
  return;
424
429
  }
425
430
 
426
- // `intent focus <mission|query|--nodes a,b> [--depth N] [--dir <d>] [--json]` , Intent Lens:
431
+ // `thunder focus <mission|query|--nodes a,b> [--depth N] [--dir <d>] [--json]` , Intent Lens:
427
432
  // a focused Focus Graph + Intent Brief around a selected scope, built over the Atlas.
428
433
  if (cmd === 'focus') {
429
434
  const dir = args.dir || '.';
430
435
  const files = existsSync(dir) && statSync(dir).isDirectory() ? collectIntents(dir) : (file && existsSync(file) ? [file] : []);
431
- if (!files.length) { console.error(`intent focus: no .intent files under ${dir}`); process.exit(2); return; }
436
+ if (!files.length) { console.error(`thunder focus: no .intent files under ${dir}`); process.exit(2); return; }
432
437
  const atlas = buildAtlas(files.map((f) => buildIntentGraph(parseIntent(readFileSync(f, 'utf8')))));
433
438
  // Resolve seeds: --nodes ids, or a mission/feature query, or (default) all missions.
434
439
  let seeds = [];
@@ -437,14 +442,14 @@ function main() {
437
442
  if (args.nodes) { seeds = args.nodes.split(',').map((s) => s.trim()).filter(Boolean); scopeType = 'custom'; scopeTitle = `${seeds.length} selected node(s)`; }
438
443
  else if (file && !existsSync(file)) {
439
444
  const hit = searchAtlas(atlas, file)[0];
440
- if (!hit) { console.error(`intent focus: no Atlas node matches "${file}"`); process.exit(1); return; }
445
+ if (!hit) { console.error(`thunder focus: no Atlas node matches "${file}"`); process.exit(1); return; }
441
446
  seeds = [hit.id]; scopeType = hit.type === 'Mission' ? 'mission' : 'feature'; scopeTitle = hit.title || hit.id;
442
447
  } else { seeds = atlas.missions.map((m) => m.id); scopeType = 'capability'; scopeTitle = 'whole project'; }
443
448
  const scope = makeScope({ type: scopeType, title: scopeTitle, seeds, createdAt: null });
444
449
  const focus = buildFocusGraph(atlas, { seeds, depth: args.depth ? Number(args.depth) : 2, scope });
445
450
  const brief = intentBrief(focus);
446
451
  if (args.json) { console.log(JSON.stringify({ scope, brief, focus }, null, 2)); return; }
447
- console.log(`intent focus , ${scope.title} [${scope.type}] (${scope.scopeId})`);
452
+ console.log(`thunder focus , ${scope.title} [${scope.type}] (${scope.scopeId})`);
448
453
  console.log(` what: ${brief.what || '(unnamed)'}${brief.confidence ? ` confidence: ${brief.confidence}` : ''}`);
449
454
  if (brief.who.length) console.log(` who: ${brief.who.join(', ')}`);
450
455
  console.log(` focus graph: ${focus.overview.nodes} node(s), ${focus.overview.relationships} edge(s), depth ${focus.depth}`);
@@ -457,19 +462,19 @@ function main() {
457
462
  return;
458
463
  }
459
464
 
460
- // `intent comprehension <file|dir> [--observed] [--learning] [--governed] [--json]` , the C0..C7
465
+ // `thunder comprehension <file|dir> [--observed] [--learning] [--governed] [--json]` , the C0..C7
461
466
  // understanding level of each mission (Comprehension Contract). IL scores the intent side (C1..C4);
462
467
  // --observed (OpenThunder/runtime), --learning (Skills Tech Talk), --governed (Guardian) lift the
463
468
  // joint level to C5/C6/C7 when a sibling attaches its evidence.
464
469
  if (cmd === 'comprehension') {
465
470
  const root = file || '.';
466
471
  const files = existsSync(root) && statSync(root).isDirectory() ? collectIntents(root) : (existsSync(root) ? [root] : []);
467
- if (!files.length) { console.error(`intent comprehension: no .intent files under ${root}`); process.exit(2); return; }
472
+ if (!files.length) { console.error(`thunder comprehension: no .intent files under ${root}`); process.exit(2); return; }
468
473
  const opts = { observed: !!args.observed, learningPath: !!args.learning, governed: !!args.governed };
469
474
  const asts = files.map((f) => parseIntent(readFileSync(f, 'utf8')));
470
475
  const report = comprehensionReport(asts, opts);
471
476
  if (args.json) { console.log(JSON.stringify(report, null, 2)); return; }
472
- console.log(`intent comprehension ${root}: ${report.count} mission(s)`);
477
+ console.log(`thunder comprehension ${root}: ${report.count} mission(s)`);
473
478
  console.log(` distribution: ${COMPREHENSION_LEVELS.map((l) => `${l.level}:${report.byLevel[l.level]}`).join(' ')}`);
474
479
  for (const m of report.missions) {
475
480
  console.log(`\n ${m.mission || '(unnamed)'} , ${m.level} ${m.levelName}`);
@@ -482,7 +487,7 @@ function main() {
482
487
  return;
483
488
  }
484
489
 
485
- // `intent twelve-factor <file> [--json]` , score an intent against the 13 principles of
490
+ // `thunder twelve-factor <file> [--json]` , score an intent against the 13 principles of
486
491
  // humanlayer/12-factor-agents (deterministic conformance lens). Per-factor verdict + score.
487
492
  if (cmd === 'twelve-factor' || cmd === '12factor') {
488
493
  if (!file) { console.error('usage: intent twelve-factor <file> [--json]'); process.exit(2); return; }
@@ -500,14 +505,14 @@ function main() {
500
505
  return;
501
506
  }
502
507
 
503
- // `intent gen <file> [--target typescript|csharp|java] [--out <dir>]` , deterministic code scaffold from
508
+ // `thunder gen <file> [--target typescript|csharp|java] [--out <dir>]` , deterministic code scaffold from
504
509
  // intent. Generates the typed contract + the decision logic (already executable) and leaves
505
510
  // honest TODO markers for business logic. No AI. Prints, or writes with --out.
506
511
  if (cmd === 'gen') {
507
512
  if (!file) { console.error(`usage: intent gen <file> [--target ${Object.keys(GENERATORS).join('|')}] [--out <dir>]`); process.exit(2); return; }
508
513
  const target = (args.target || 'typescript').toLowerCase();
509
514
  const generate = GENERATORS[target];
510
- if (!generate) { console.error(`intent gen: unknown target "${target}" (have: ${Object.keys(GENERATORS).join(', ')})`); process.exit(2); return; }
515
+ if (!generate) { console.error(`thunder gen: unknown target "${target}" (have: ${Object.keys(GENERATORS).join(', ')})`); process.exit(2); return; }
511
516
  const ast = parseIntent(readFileSync(file, 'utf8'));
512
517
  const code = generate(ast);
513
518
  if (args.outExplicit) {
@@ -520,27 +525,27 @@ function main() {
520
525
  return;
521
526
  }
522
527
 
523
- // `intent changes [<base>..<head> | <base>] [--json]` , Change Lens: what a branch / PR / commit
528
+ // `thunder changes [<base>..<head> | <base>] [--json]` , Change Lens: what a branch / PR / commit
524
529
  // range changed BY MEANING. git-diffs the .intent files, semantic-diffs each, and reports the
525
530
  // behavior-level changes + regression risk. Default: HEAD vs the working tree.
526
531
  if (cmd === 'changes') {
527
532
  const git = (c) => { try { return execSync(`git ${c}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }); } catch { return null; } };
528
- if (git('rev-parse --is-inside-work-tree') === null) { console.error('intent changes: not a git repository'); process.exit(2); return; }
533
+ if (git('rev-parse --is-inside-work-tree') === null) { console.error('thunder changes: not a git repository'); process.exit(2); return; }
529
534
  const range = file || '';
530
535
  let base, head; // head === null means "the working tree"
531
536
  if (range.includes('..')) { [base, head] = range.split('..'); head = head || 'HEAD'; }
532
537
  else if (range) { base = range; head = null; }
533
538
  else { base = 'HEAD'; head = null; }
534
539
  const diffSpec = head ? `${base} ${head}` : base;
535
- const names = (git(`diff --name-only ${diffSpec} -- "*.intent"`) || '').split('\n').map((s) => s.trim()).filter(Boolean);
536
- if (!names.length) { console.log(`intent changes ${range || `${base}..working-tree`}: no .intent files changed`); return; }
540
+ const names = (git(`diff --name-only ${diffSpec} -- "*.thunder" "*.tl" "*.intent"`) || '').split('\n').map((s) => s.trim()).filter(Boolean);
541
+ if (!names.length) { console.log(`thunder changes ${range || `${base}..working-tree`}: no source files changed`); return; }
537
542
  const readAt = (ref, p) => (ref === null ? (existsSync(p) ? readFileSync(p, 'utf8') : null) : git(`show ${ref}:${p}`));
538
543
  const toGraph = (src) => (src != null ? buildIntentGraph(parseIntent(src)) : null);
539
544
  const pairs = names.map((p) => ({ path: p, before: toGraph(readAt(base, p)), after: toGraph(readAt(head, p)) }));
540
545
  const report = changeReport(pairs);
541
546
  if (args.json) { console.log(JSON.stringify(report, null, 2)); process.exit(report.verdict === 'review' ? 1 : 0); return; }
542
547
  const t = report.totals;
543
- console.log(`intent changes ${range || `${base}..working-tree`}: ${report.verdict.toUpperCase()}`);
548
+ console.log(`thunder changes ${range || `${base}..working-tree`}: ${report.verdict.toUpperCase()}`);
544
549
  console.log(` ${t.files} file(s) , +${t.added} / -${t.removed} / ~${t.changed} nodes${t.invalidatedApprovals ? `, ${t.invalidatedApprovals} approval(s) invalidated` : ''}`);
545
550
  if (report.regressions.length) {
546
551
  console.log(' regression risk (a promise or its proof was removed or weakened):');
@@ -559,18 +564,18 @@ function main() {
559
564
  }
560
565
 
561
566
  // MCP server (Model Context Protocol over stdio) , makes ThunderLang a native tool for AI
562
- // coding agents. Long-running; no file argument. Point an MCP client at `intent mcp`.
567
+ // coding agents. Long-running; no file argument. Point an MCP client at `thunder mcp`.
563
568
  if (cmd === 'mcp') {
564
569
  startMcpServer();
565
570
  return; // keep the process alive on stdin
566
571
  }
567
572
 
568
- // Scaffold a runnable starter mission (deterministic, no AI). `intent init [Name]`.
573
+ // Scaffold a runnable starter mission (deterministic, no AI). `thunder init [Name]`.
569
574
  if (cmd === 'init') {
570
- const name = (file || 'Mission').replace(/\.intent$/i, '');
571
- const target = join(args.out && args.out !== '.intent' ? args.out : '.', `${name}.intent`);
575
+ const name = stripSourceExt(file || 'Mission');
576
+ const target = join(args.out && args.out !== '.intent' ? args.out : '.', `${name}.thunder`);
572
577
  if (existsSync(target) && !args.force) {
573
- console.error(`intent init: ${target} already exists (use --force to overwrite).`);
578
+ console.error(`thunder init: ${target} already exists (use --force to overwrite).`);
574
579
  process.exit(1); return;
575
580
  }
576
581
  const starter = `mission ${name}
@@ -586,7 +591,7 @@ guarantee an example property that must always hold
586
591
  never
587
592
  do something this mission must never do
588
593
 
589
- # A runnable decision. Try: intent run ${name}.intent --inputs '{"age":20}'
594
+ # A runnable decision. Try: thunder run ${name}.intent --inputs '{"age":20}'
590
595
  decision Example
591
596
  inputs
592
597
  age
@@ -596,7 +601,7 @@ decision Example
596
601
  default
597
602
  return Blocked
598
603
 
599
- # Tests live in the file. Try: intent test ${name}.intent
604
+ # Tests live in the file. Try: thunder test ${name}.intent
600
605
  test Example
601
606
  case adult
602
607
  given age 20
@@ -607,13 +612,13 @@ test Example
607
612
  `;
608
613
  if (target.includes('/')) mkdirSync(dirname(target), { recursive: true });
609
614
  writeFileSync(target, starter);
610
- console.log(`intent init: wrote ${target}`);
611
- console.log(` next: intent check ${target} | intent run ${target} --inputs '{"age":20}' | intent test ${target}`);
615
+ console.log(`thunder init: wrote ${target}`);
616
+ console.log(` next: thunder check ${target} | thunder run ${target} --inputs '{"age":20}' | thunder test ${target}`);
612
617
  return;
613
618
  }
614
619
 
615
620
  if (!file && cmd !== 'draft') {
616
- console.error(`intent ${cmd}: missing a file argument. Run "intent help" for usage.`);
621
+ console.error(`thunder ${cmd}: missing a file argument. Run "intent help" for usage.`);
617
622
  process.exit(2);
618
623
  }
619
624
  // IntentLift: lift source CODE into inferred .intent drafts (not intent parsing).
@@ -638,10 +643,10 @@ test Example
638
643
  }
639
644
  if (args.out) {
640
645
  for (const m of res.missions) writeText(args.out, m.outName, m.intentText);
641
- console.log(`intent lift repo ${root} -> ${res.missionsGenerated} mission(s) in ${args.out}`);
646
+ console.log(`thunder lift repo ${root} -> ${res.missionsGenerated} mission(s) in ${args.out}`);
642
647
  console.log(` confidence: ${JSON.stringify(res.confidenceSummary)} | ${res.unknowns} unknown(s) total`);
643
648
  } else {
644
- console.log(`intent lift repo ${root}: ${res.missionsGenerated} mission(s)`);
649
+ console.log(`thunder lift repo ${root}: ${res.missionsGenerated} mission(s)`);
645
650
  for (const m of res.missions) console.log(` ${m.mission} (${m.summary.confidence}) <- ${m.sourceFile}`);
646
651
  }
647
652
  return;
@@ -653,7 +658,7 @@ test Example
653
658
  const res = liftAll(src, { language: args.from || languageForFile(file), file: basename(file) });
654
659
  if (!res.ok) { console.error(res.error); process.exit(1); }
655
660
  if (args.json) { console.log(JSON.stringify(res, null, 2)); return; }
656
- console.log(`intent lift --all ${basename(file)}: ${res.count} mission(s) inferred`);
661
+ console.log(`thunder lift --all ${basename(file)}: ${res.count} mission(s) inferred`);
657
662
  for (const m of res.missions) console.log(` ${m.mission} (${m.fn}, confidence ${m.confidence})`);
658
663
  return;
659
664
  }
@@ -665,7 +670,7 @@ test Example
665
670
  if (args.json) { console.log(JSON.stringify(res.summary, null, 2)); return; }
666
671
  if (args.out) {
667
672
  const p = writeText(args.out, `${slug(res.lifted.mission)}.intent`, res.intentText);
668
- console.log(`intent lift ${basename(file)} -> ${p.replace(process.cwd() + '/', '')}`);
673
+ console.log(`thunder lift ${basename(file)} -> ${p.replace(process.cwd() + '/', '')}`);
669
674
  } else {
670
675
  console.log(res.intentText);
671
676
  }
@@ -680,7 +685,7 @@ test Example
680
685
  // args.out defaults to '.intent'; approve writes back to the file unless a real --out was given.
681
686
  const target = args.out && args.out !== '.intent' ? args.out : file;
682
687
  writeFileSync(target, res.text);
683
- console.log(`intent approve ${basename(file)} -> reviewed: true (${res.approval.source_hash.slice(0, 24)}...)`);
688
+ console.log(`thunder approve ${basename(file)} -> reviewed: true (${res.approval.source_hash.slice(0, 24)}...)`);
684
689
  return;
685
690
  }
686
691
 
@@ -691,7 +696,7 @@ test Example
691
696
  const out = JSON.stringify(pack, null, 2);
692
697
  if (args.out && args.out !== '.intent') {
693
698
  const p = writeText(args.out, `${slug(pack.mission)}.drift-handoff.json`, out);
694
- console.log(`intent handoff ${basename(file)} -> ${p.replace(process.cwd() + '/', '')} (kind ${pack.kind}, approved ${pack.approved})`);
699
+ console.log(`thunder handoff ${basename(file)} -> ${p.replace(process.cwd() + '/', '')} (kind ${pack.kind}, approved ${pack.approved})`);
695
700
  } else {
696
701
  console.log(out);
697
702
  }
@@ -707,7 +712,7 @@ test Example
707
712
  const res = checkDrift(intentText, codeText, { language });
708
713
  if (args.json) { console.log(JSON.stringify(res, null, 2)); }
709
714
  else {
710
- console.log(`intent drift: ${res.status.toUpperCase()} (${res.summary.blocking} blocking)`);
715
+ console.log(`thunder drift: ${res.status.toUpperCase()} (${res.summary.blocking} blocking)`);
711
716
  for (const f of res.findings) console.log(` [${f.level}] ${f.code}: ${f.message}`);
712
717
  }
713
718
  process.exit(res.status === 'drift' ? 1 : 0);
@@ -725,7 +730,7 @@ test Example
725
730
  });
726
731
  const manifest = buildManifest(parsed, { projectId: args.product });
727
732
  if (args.json) { console.log(JSON.stringify(manifest, null, 2)); return; }
728
- console.log(`intent ai list ${root}: ${manifest.summary.total} AI implementation(s)`);
733
+ console.log(`thunder ai list ${root}: ${manifest.summary.total} AI implementation(s)`);
729
734
  for (const im of manifest.implementations) {
730
735
  console.log(` ${im.id.padEnd(28)} ${im.status.padEnd(9)} risk:${im.risk} approval:${im.approval} scope:${im.scope}`);
731
736
  }
@@ -738,7 +743,7 @@ test Example
738
743
  const file = args._[1];
739
744
  if (!file) { console.error('usage: intent ai generate <file.intent> [--from <lang>]'); process.exit(2); return; }
740
745
  const ast = parseIntent(readFileSync(file, 'utf8'));
741
- if (!ast.implementation) { console.error(`intent ai generate: ${basename(file)} has no "implement with ai" block.`); process.exit(2); return; }
746
+ if (!ast.implementation) { console.error(`thunder ai generate: ${basename(file)} has no "implement with ai" block.`); process.exit(2); return; }
742
747
  const prompt = buildImplementationPrompt(ast, { language: args.from || 'typescript' });
743
748
  if (args.out && args.out !== '.intent') { const p = writeText(args.out, `${slug(ast.mission)}.prompt.md`, prompt); console.log(`wrote ${p.replace(process.cwd() + '/', '')}`); }
744
749
  else console.log(prompt);
@@ -765,7 +770,7 @@ test Example
765
770
  });
766
771
  const gate = productionGate(resolved, { allowPending: args.allowPending });
767
772
  if (args.json) { console.log(JSON.stringify({ ...gate, mode: args.mode || 'production', resolved }, null, 2)); process.exit(gate.ok ? 0 : 1); return; }
768
- console.log(`intent ai gate ${root} (${args.mode || 'production'}): ${gate.ok ? 'PASS' : 'BLOCKED'} , ${resolved.length} implementation(s)`);
773
+ console.log(`thunder ai gate ${root} (${args.mode || 'production'}): ${gate.ok ? 'PASS' : 'BLOCKED'} , ${resolved.length} implementation(s)`);
769
774
  for (const r of resolved) console.log(` ${r.id.padEnd(28)} ${r.status}${r.reasons?.[0] ? ` , ${r.reasons[0].code}: ${r.reasons[0].message}` : ''}`);
770
775
  if (!gate.ok) console.log(` ${gate.blocking.length} implementation(s) block production. Use --allow-pending for dev builds.`);
771
776
  process.exit(gate.ok ? 0 : 1);
@@ -777,9 +782,9 @@ test Example
777
782
  if (!file || !id) { console.error('usage: intent ai adopt <targetFile> <id> [--from <lang>]'); process.exit(2); return; }
778
783
  const code = readFileSync(file, 'utf8');
779
784
  const res = adoptRegion(code, id, args.from || languageForFile(file));
780
- if (!res) { console.error(`intent ai adopt: no AI-managed region "${id}" in ${basename(file)}.`); process.exit(2); return; }
785
+ if (!res) { console.error(`thunder ai adopt: no AI-managed region "${id}" in ${basename(file)}.`); process.exit(2); return; }
781
786
  writeFileSync(file, res.code);
782
- console.log(`intent ai adopt ${id} -> human-owned (origin="ai" ownership="human") in ${basename(file)}`);
787
+ console.log(`thunder ai adopt ${id} -> human-owned (origin="ai" ownership="human") in ${basename(file)}`);
783
788
  return;
784
789
  }
785
790
  if (sub === 'approve' || sub === 'reject') {
@@ -790,7 +795,7 @@ test Example
790
795
  const manifest = buildManifest(parsed.map((p) => ({ path: relative(root, p.file), source: '', ast: p.ast })), {});
791
796
  const im = manifest.implementations.find((x) => x.id === id);
792
797
  const ast = parsed.find((p) => (p.ast.implementation?.id || slug(p.ast.mission || '')) === id)?.ast;
793
- if (!im || !ast) { console.error(`intent ai ${sub}: no implementation "${id}" found under ${root}.`); process.exit(2); return; }
798
+ if (!im || !ast) { console.error(`thunder ai ${sub}: no implementation "${id}" found under ${root}.`); process.exit(2); return; }
794
799
  let region = null;
795
800
  if (im.targetLocation && existsSync(join(root, im.targetLocation))) region = parseMarkers(readFileSync(join(root, im.targetLocation), 'utf8')).regions.find((r) => r.id === id) || null;
796
801
  const pf = join(root, im.proofLocation);
@@ -809,7 +814,7 @@ test Example
809
814
  by: args.by, role: args.role, note: args.note,
810
815
  contractHash: contractHash(ast), implementationHash: implementationHash(region.code), at,
811
816
  });
812
- if (error) { console.error(`intent ai ${sub}: ${error}`); process.exit(2); return; }
817
+ if (error) { console.error(`thunder ai ${sub}: ${error}`); process.exit(2); return; }
813
818
  writeJson(join(root, '.intent'), 'ai-approvals.json', next);
814
819
  const event = makeEvent(sub === 'approve' ? 'IntentAiImplementationApproved' : 'IntentAiImplementationRejected', {
815
820
  implementationId: id, missionId: ast.mission, contractHash: contractHash(ast), implementationHash: implementationHash(region.code),
@@ -817,18 +822,18 @@ test Example
817
822
  newStatus: sub === 'approve' ? 'APPROVED' : 'REJECTED',
818
823
  });
819
824
  const logPath = sinkEvent(root, event);
820
- console.log(`intent ai ${sub} ${id} by ${args.by || '(anonymous)'}${args.role ? ` [${args.role}]` : ''} -> ${sub === 'approve' ? 'APPROVED' : 'REJECTED'} (bound to current hashes)`);
825
+ console.log(`thunder ai ${sub} ${id} by ${args.by || '(anonymous)'}${args.role ? ` [${args.role}]` : ''} -> ${sub === 'approve' ? 'APPROVED' : 'REJECTED'} (bound to current hashes)`);
821
826
  console.log(` logged ${event.type} to ${logPath.replace(process.cwd() + '/', '')}`);
822
827
  if (args.json) console.log(JSON.stringify(event, null, 2));
823
828
  return;
824
829
  }
825
- // `intent ai events [dir] [--subject <implId>] [--json]` , read the append-only audit log.
830
+ // `thunder ai events [dir] [--subject <implId>] [--json]` , read the append-only audit log.
826
831
  if (sub === 'events') {
827
832
  const root = args._[1] || '.';
828
833
  const log = readEventLog(root);
829
834
  const events = args.subject ? log.events.filter((e) => e.implementationId === args.subject) : log.events;
830
835
  if (args.json) { console.log(JSON.stringify({ ...log, events }, null, 2)); return; }
831
- console.log(`intent ai events ${root}: ${events.length} event${events.length === 1 ? '' : 's'}${args.subject ? ` for ${args.subject}` : ''}`);
836
+ console.log(`thunder ai events ${root}: ${events.length} event${events.length === 1 ? '' : 's'}${args.subject ? ` for ${args.subject}` : ''}`);
832
837
  for (const e of events) {
833
838
  const who = e.actorId ? ` by ${e.actorId}` : '';
834
839
  const move = e.previousStatus || e.newStatus ? ` (${e.previousStatus || '?'} -> ${e.newStatus || '?'})` : '';
@@ -845,7 +850,7 @@ test Example
845
850
  .find((a) => (a.implementation?.id || slug(a.mission || '')) === id);
846
851
  const policy = parseSelection(ast?.selection || []);
847
852
  const cdir = join(root, '.intent', 'candidates', id);
848
- if (!existsSync(cdir)) { console.error(`intent ai select: no candidates at ${relative(process.cwd(), cdir)}.`); process.exit(2); return; }
853
+ if (!existsSync(cdir)) { console.error(`thunder ai select: no candidates at ${relative(process.cwd(), cdir)}.`); process.exit(2); return; }
849
854
  const candidates = readdirSync(cdir).filter((n) => !n.endsWith('.proof.json') && !n.endsWith('.json')).map((name) => {
850
855
  const code = readFileSync(join(cdir, name), 'utf8');
851
856
  const region = parseMarkers(code).regions.find((r) => r.id === id);
@@ -856,12 +861,12 @@ test Example
856
861
  });
857
862
  const result = selectCandidate(candidates, policy);
858
863
  if (args.json) { console.log(JSON.stringify({ ...result, policy }, null, 2)); return; }
859
- console.log(`intent ai select ${id}: ${result.winner ? result.winner.id : '(none eligible)'} wins (${result.eligibleCount}/${candidates.length} eligible)`);
864
+ console.log(`thunder ai select ${id}: ${result.winner ? result.winner.id : '(none eligible)'} wins (${result.eligibleCount}/${candidates.length} eligible)`);
860
865
  console.log(` policy: prefer ${result.prefer.map((p) => `${p.direction} ${p.metric}`).join(', ')}${policy.requireAllChecks ? '; require all checks' : ''}`);
861
866
  for (const c of result.ranking) console.log(` ${c.id.padEnd(20)} ${JSON.stringify(c.metrics)}${c.checksPassed === false ? ' [checks FAILED]' : ''}`);
862
867
  return;
863
868
  }
864
- console.error(`intent ai ${sub || ''}: IL supports list | generate | gate | adopt | approve | reject | select. OpenThunder runs verification.`);
869
+ console.error(`thunder ai ${sub || ''}: IL supports list | generate | gate | adopt | approve | reject | select. OpenThunder runs verification.`);
865
870
  process.exit(2);
866
871
  return;
867
872
  }
@@ -869,14 +874,14 @@ test Example
869
874
  // Semantic diff: compare two snapshots (dirs or .intent files) by meaning.
870
875
  if (cmd === 'diff') {
871
876
  const b = args._[0]; const a = args._[1];
872
- if (!b || !a) { console.error('usage: intent diff <before-dir|file> <after-dir|file> [--json]'); process.exit(2); return; }
877
+ if (!b || !a) { console.error('usage: thunder diff <before-dir|file> <after-dir|file> [--json]'); process.exit(2); return; }
873
878
  const snap = (p) => {
874
879
  if (statSync(p).isDirectory()) return buildAtlas(collectIntents(p).map((f) => buildIntentGraph(parseIntent(readFileSync(f, 'utf8')))));
875
880
  return buildIntentGraph(parseIntent(readFileSync(p, 'utf8')));
876
881
  };
877
882
  const diff = diffGraphs(snap(b), snap(a));
878
883
  if (args.json) { console.log(JSON.stringify(diff, null, 2)); return; }
879
- console.log(`intent diff ${b} -> ${a}: +${diff.summary.added} / -${diff.summary.removed} / ~${diff.summary.changed} node(s), +${diff.summary.relationshipsAdded} / -${diff.summary.relationshipsRemoved} edge(s)`);
884
+ console.log(`thunder diff ${b} -> ${a}: +${diff.summary.added} / -${diff.summary.removed} / ~${diff.summary.changed} node(s), +${diff.summary.relationshipsAdded} / -${diff.summary.relationshipsRemoved} edge(s)`);
880
885
  if (Object.keys(diff.summary.addedByType).length) console.log(` added: ${JSON.stringify(diff.summary.addedByType)}`);
881
886
  if (Object.keys(diff.summary.removedByType).length) console.log(` removed: ${JSON.stringify(diff.summary.removedByType)}`);
882
887
  for (const c of diff.changedNodes.slice(0, 8)) console.log(` ~ ${c.type} ${c.id}`);
@@ -893,7 +898,7 @@ test Example
893
898
  : buildIntentGraph(parseIntent(readFileSync(p, 'utf8')));
894
899
  const res = mergeGraphs(snap(bp), snap(op), snap(tp));
895
900
  if (args.json) { console.log(JSON.stringify(res, null, 2)); process.exit(res.clean ? 0 : 1); return; }
896
- console.log(`intent merge: ${res.clean ? 'CLEAN' : 'CONFLICTS'} , ${res.summary.nodes} node(s), ${res.summary.conflicts} conflict(s)`);
901
+ console.log(`thunder merge: ${res.clean ? 'CLEAN' : 'CONFLICTS'} , ${res.summary.nodes} node(s), ${res.summary.conflicts} conflict(s)`);
897
902
  for (const c of res.conflicts) console.log(` conflict: ${c.type} ${c.id} (changed differently on both sides)`);
898
903
  process.exit(res.clean ? 0 : 1);
899
904
  return;
@@ -903,10 +908,10 @@ test Example
903
908
  if (cmd === 'run') {
904
909
  const ast = parseIntent(readFileSync(file, 'utf8'));
905
910
  let inputs = {};
906
- if (args.inputs) { try { inputs = JSON.parse(args.inputs); } catch { console.error('intent run: --inputs must be JSON'); process.exit(2); return; } }
911
+ if (args.inputs) { try { inputs = JSON.parse(args.inputs); } catch { console.error('thunder run: --inputs must be JSON'); process.exit(2); return; } }
907
912
  let decisions = ast.decisions || [];
908
913
  if (args.decision) decisions = decisions.filter((d) => slug(d.name) === slug(args.decision));
909
- if (!decisions.length) { console.error('intent run: no decision to run' + (args.decision ? ` matching "${args.decision}"` : '')); process.exit(2); return; }
914
+ if (!decisions.length) { console.error('thunder run: no decision to run' + (args.decision ? ` matching "${args.decision}"` : '')); process.exit(2); return; }
910
915
  const runs = decisions.map((d) => evaluateDecision(d, inputs));
911
916
  if (args.json) { console.log(JSON.stringify(runs.length === 1 ? runs[0] : runs, null, 2)); return; }
912
917
  for (const r of runs) {
@@ -922,8 +927,8 @@ test Example
922
927
  const ast = parseIntent(readFileSync(file, 'utf8'));
923
928
  const r = evaluateOutcomes(ast);
924
929
  if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.missed > 0 ? 1 : 0); return; }
925
- if (r.total === 0) { console.log(`intent outcomes ${basename(file)}: no outcome contracts found.`); return; }
926
- console.log(`intent outcomes ${basename(file)}: ${r.met} met, ${r.missed} missed, ${r.pending} pending`);
930
+ if (r.total === 0) { console.log(`thunder outcomes ${basename(file)}: no outcome contracts found.`); return; }
931
+ console.log(`thunder outcomes ${basename(file)}: ${r.met} met, ${r.missed} missed, ${r.pending} pending`);
927
932
  for (const e of r.evaluations) {
928
933
  const tag = e.status === 'met' ? 'MET ' : e.status === 'missed' ? 'MISSED' : 'PENDING';
929
934
  const detail = e.comparable ? `actual ${e.actual} vs target ${e.target} (${e.direction})${e.improvement != null ? `, ${e.improvement >= 0 ? '+' : ''}${e.improvement} vs baseline` : ''}` : `no measured result yet (target ${e.target})`;
@@ -938,8 +943,8 @@ test Example
938
943
  const ast = parseIntent(readFileSync(file, 'utf8'));
939
944
  const r = analyzeStyle(ast);
940
945
  if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.diagnostics.some((d) => d.severity === 'blocker') ? 1 : 0); return; }
941
- if (r.styleIntents.length === 0) { console.log(`intent style ${basename(file)}: no style intents found.`); return; }
942
- console.log(`intent style ${basename(file)}: ${r.styleIntents.length} style intent(s)`);
946
+ if (r.styleIntents.length === 0) { console.log(`thunder style ${basename(file)}: no style intents found.`); return; }
947
+ console.log(`thunder style ${basename(file)}: ${r.styleIntents.length} style intent(s)`);
943
948
  for (const s of r.styleIntents) {
944
949
  const a11y = s.accessibility ? `${s.accessibility.target} (${s.accessibility.classification}, verified=${s.accessibility.verified})` : 'no target';
945
950
  console.log(` ${s.name} , a11y ${a11y}, ${s.tokens.length} token(s)${s.appliesTo ? `, applies_to ${s.appliesTo}` : ''}`);
@@ -955,8 +960,8 @@ test Example
955
960
  const ast = parseIntent(readFileSync(file, 'utf8'));
956
961
  const r = runTests(ast);
957
962
  if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.ok ? 0 : 1); return; }
958
- if (r.total === 0) { console.log(`intent test ${basename(file)}: no test blocks found.`); return; }
959
- console.log(`intent test ${basename(file)}: ${r.passed}/${r.total} passed`);
963
+ if (r.total === 0) { console.log(`thunder test ${basename(file)}: no test blocks found.`); return; }
964
+ console.log(`thunder test ${basename(file)}: ${r.passed}/${r.total} passed`);
960
965
  for (const c of r.results) {
961
966
  const detail = c.error ? ` ${c.error}`
962
967
  : c.kind === 'lifecycle' ? `expected ${c.expected ?? '(any)'}, got ${c.actual} (valid=${c.valid})`
@@ -993,7 +998,7 @@ test Example
993
998
  if (!graph || !Array.isArray(graph.nodes)) { console.error('intent validate: not an Intent Graph (no nodes[])'); process.exit(2); return; }
994
999
  const v = validateGraph(graph);
995
1000
  if (args.json) { console.log(JSON.stringify(v, null, 2)); process.exit(v.valid ? 0 : 1); return; }
996
- console.log(`intent validate ${basename(file)}: ${v.valid ? 'VALID' : `${v.issues.length} issue(s)`} (${v.version})`);
1001
+ console.log(`thunder validate ${basename(file)}: ${v.valid ? 'VALID' : `${v.issues.length} issue(s)`} (${v.version})`);
997
1002
  for (const i of v.issues) console.log(` [${i.code}] ${i.message}${i.id ? ` (${i.id})` : ''}`);
998
1003
  process.exit(v.valid ? 0 : 1);
999
1004
  return;
@@ -1002,14 +1007,14 @@ test Example
1002
1007
  if (cmd === 'migrate') {
1003
1008
  const raw = readFileSync(file, 'utf8');
1004
1009
  let graph;
1005
- try { graph = JSON.parse(raw); } catch { console.error('intent migrate: input is not valid JSON'); process.exit(2); return; }
1006
- if (!graph || !Array.isArray(graph.nodes)) { console.error('intent migrate: not an Intent Graph (no nodes[])'); process.exit(2); return; }
1010
+ try { graph = JSON.parse(raw); } catch { console.error('thunder migrate: input is not valid JSON'); process.exit(2); return; }
1011
+ if (!graph || !Array.isArray(graph.nodes)) { console.error('thunder migrate: not an Intent Graph (no nodes[])'); process.exit(2); return; }
1007
1012
  let result;
1008
1013
  try { result = migrateGraph(graph, args.to ? { to: args.to } : {}); }
1009
- catch (e) { console.error(`intent migrate: ${e instanceof Error ? e.message : e}`); process.exit(2); return; }
1014
+ catch (e) { console.error(`thunder migrate: ${e instanceof Error ? e.message : e}`); process.exit(2); return; }
1010
1015
  const v = validateGraph(result.graph);
1011
1016
  if (args.json) { console.log(JSON.stringify({ ...result, validation: v }, null, 2)); process.exit(v.valid ? 0 : 1); return; }
1012
- console.log(`intent migrate: ${result.from} -> ${result.to} (${result.migrated ? result.applied.length + ' step(s)' : 'already current'})`);
1017
+ console.log(`thunder migrate: ${result.from} -> ${result.to} (${result.migrated ? result.applied.length + ' step(s)' : 'already current'})`);
1013
1018
  for (const a of result.applied) console.log(` applied ${a.from} -> ${a.to}: ${a.description}`);
1014
1019
  console.log(` validation: ${v.valid ? 'OK' : `${v.issues.length} issue(s)`}`);
1015
1020
  for (const i of v.issues.slice(0, 10)) console.log(` [${i.code}] ${i.message}`);
@@ -1032,7 +1037,7 @@ test Example
1032
1037
  const src = graphToSource(graph);
1033
1038
  if (args.out && args.out !== '.intent') {
1034
1039
  const base = (graph.nodes.find((n) => n.type === 'Mission')?.title) || basename(file).replace(/\.[^.]+$/, '');
1035
- console.log(`intent source: wrote ${writeText(args.out, `${slug(base)}.intent`, src)}`);
1040
+ console.log(`thunder source: wrote ${writeText(args.out, `${slug(base)}.intent`, src)}`);
1036
1041
  } else {
1037
1042
  process.stdout.write(src);
1038
1043
  }
@@ -1044,22 +1049,22 @@ test Example
1044
1049
  const xml = readFileSync(file, 'utf8');
1045
1050
  const fmt = args.format || detectFormat(xml);
1046
1051
  if (!fmt || !IMPORT_FORMATS.includes(fmt)) {
1047
- console.error(`intent import: could not detect format; pass --format <${IMPORT_FORMATS.join('|')}>`);
1052
+ console.error(`thunder import: could not detect format; pass --format <${IMPORT_FORMATS.join('|')}>`);
1048
1053
  process.exit(2); return;
1049
1054
  }
1050
1055
  const report = importReport(xml, fmt);
1051
- if (report == null) { console.error(`intent import: unsupported format "${fmt}"`); process.exit(2); return; }
1056
+ if (report == null) { console.error(`thunder import: unsupported format "${fmt}"`); process.exit(2); return; }
1052
1057
  if (args.json) { console.log(JSON.stringify(report, null, 2)); return; }
1053
1058
  const src = report.source;
1054
1059
  if (args.out && args.out !== '.intent') {
1055
1060
  const base = basename(file).replace(/\.[^.]+$/, '');
1056
1061
  const p = writeText(args.out, `${slug(base)}.intent`, src);
1057
- console.log(`intent import: wrote ${p}`);
1062
+ console.log(`thunder import: wrote ${p}`);
1058
1063
  } else {
1059
1064
  process.stdout.write(src.endsWith('\n') ? src : src + '\n');
1060
1065
  }
1061
1066
  // Fidelity warnings go to stderr, so stdout stays clean for piping the source.
1062
- for (const w of report.warnings) console.error(`intent import: [${w.code}] ${w.message}`);
1067
+ for (const w of report.warnings) console.error(`thunder import: [${w.code}] ${w.message}`);
1063
1068
  return;
1064
1069
  }
1065
1070
 
@@ -1073,9 +1078,9 @@ test Example
1073
1078
  const ast = parseIntent(readFileSync(file, 'utf8'));
1074
1079
  const res = exportIntent(ast, fmt);
1075
1080
  if (args.out && args.out !== '.intent') {
1076
- const name = `${slug(ast.mission || basename(file, '.intent'))}.${res.ext}`;
1081
+ const name = `${slug(ast.mission || stripSourceExt(basename(file)))}.${res.ext}`;
1077
1082
  const p = writeText(args.out, name, res.content);
1078
- console.log(`intent export: wrote ${p}`);
1083
+ console.log(`thunder export: wrote ${p}`);
1079
1084
  } else {
1080
1085
  process.stdout.write(res.content);
1081
1086
  }
@@ -1090,13 +1095,13 @@ test Example
1090
1095
  if (args.search) {
1091
1096
  const hits = searchAtlas(atlas, args.search, { type: args.from });
1092
1097
  if (args.json) { console.log(JSON.stringify(hits, null, 2)); return; }
1093
- console.log(`intent atlas search "${args.search}": ${hits.length} hit(s)`);
1098
+ console.log(`thunder atlas search "${args.search}": ${hits.length} hit(s)`);
1094
1099
  for (const h of hits) console.log(` ${h.type.padEnd(18)} ${h.id}${h.title ? ` , ${h.title}` : ''}`);
1095
1100
  return;
1096
1101
  }
1097
1102
  if (args.expand) {
1098
1103
  const ex = expandNode(atlas, args.expand);
1099
- if (!ex) { console.error(`intent atlas: no node "${args.expand}".`); process.exit(2); return; }
1104
+ if (!ex) { console.error(`thunder atlas: no node "${args.expand}".`); process.exit(2); return; }
1100
1105
  if (args.json) { console.log(JSON.stringify(ex, null, 2)); return; }
1101
1106
  console.log(`${ex.node.type} ${ex.node.id}${ex.node.title ? ` , ${ex.node.title}` : ''}`);
1102
1107
  for (const e of ex.out) console.log(` -> ${e.rel.padEnd(16)} ${e.node.id}`);
@@ -1104,10 +1109,10 @@ test Example
1104
1109
  return;
1105
1110
  }
1106
1111
  if (args.json) { console.log(JSON.stringify(atlas, null, 2)); return; }
1107
- console.log(`intent atlas ${root}: ${atlas.overview.missions} mission(s), ${atlas.overview.nodes} node(s), ${atlas.overview.relationships} edge(s)`);
1112
+ console.log(`thunder atlas ${root}: ${atlas.overview.missions} mission(s), ${atlas.overview.nodes} node(s), ${atlas.overview.relationships} edge(s)`);
1108
1113
  console.log(` ${JSON.stringify(atlas.overview.byType)}`);
1109
1114
  for (const m of atlas.missions) console.log(` mission ${m.id}${m.title ? ` , ${m.title}` : ''}`);
1110
- console.log(' expand a node: intent atlas . --expand <id> | search: --search <query>');
1115
+ console.log(' expand a node: thunder atlas . --expand <id> | search: --search <query>');
1111
1116
  return;
1112
1117
  }
1113
1118
 
@@ -1118,13 +1123,13 @@ test Example
1118
1123
  try {
1119
1124
  intentFiles = collectIntents(root).map((f) => ({ path: relative(root, f), source: readFileSync(f, 'utf8') }));
1120
1125
  } catch (e) {
1121
- console.error(`intent index: cannot read "${root}": ${e instanceof Error ? e.message : e}`);
1126
+ console.error(`thunder index: cannot read "${root}": ${e instanceof Error ? e.message : e}`);
1122
1127
  process.exit(2);
1123
1128
  return;
1124
1129
  }
1125
1130
  const index = buildMissionIndex(intentFiles, { product: args.product });
1126
1131
  if (args.json) { console.log(JSON.stringify(index, null, 2)); return; }
1127
- console.log(`intent index ${root}: ${index.summary.missions} mission(s)`);
1132
+ console.log(`thunder index ${root}: ${index.summary.missions} mission(s)`);
1128
1133
  console.log(` ${JSON.stringify(index.summary.byArea)}`);
1129
1134
  for (const m of index.missions) {
1130
1135
  console.log(` ${m.mission.padEnd(24)} ${String(m.risk).padEnd(7)} G:${m.guarantees} N:${m.neverRules} verify:${m.verification}${m.reviewed ? ' reviewed' : ''}`);
@@ -1134,17 +1139,17 @@ test Example
1134
1139
  return;
1135
1140
  }
1136
1141
 
1137
- // `intent verify-diff <intent> --after <code> [--before <code>]` , the AI-loop gate: prove
1142
+ // `thunder verify-diff <intent> --after <code> [--before <code>]` , the AI-loop gate: prove
1138
1143
  // deterministically which of the intent's guarantees/never-rules a code change upholds or breaks.
1139
1144
  if (cmd === 'verify-diff') {
1140
1145
  const intentText = readFileSync(file, 'utf8');
1141
- if (!args.after) { console.error('usage: intent verify-diff <intent> --after <codeFile> [--before <codeFile>] [--from <lang>]'); process.exit(2); return; }
1146
+ if (!args.after) { console.error('usage: thunder verify-diff <intent> --after <codeFile> [--before <codeFile>] [--from <lang>]'); process.exit(2); return; }
1142
1147
  const after = readFileSync(args.after, 'utf8');
1143
1148
  const before = args.before ? readFileSync(args.before, 'utf8') : null;
1144
1149
  const language = args.from || languageForFile(args.after);
1145
1150
  const r = verifyDiff(intentText, { before, after, language });
1146
1151
  if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.ok ? 0 : 1); return; }
1147
- console.log(`intent verify-diff ${basename(file)} vs ${basename(args.after)}: ${r.verdict} (${r.blocking} blocking, ${r.summary.regressions} regression(s))`);
1152
+ console.log(`thunder verify-diff ${basename(file)} vs ${basename(args.after)}: ${r.verdict} (${r.blocking} blocking, ${r.summary.regressions} regression(s))`);
1148
1153
  for (const f of r.findings) {
1149
1154
  const tag = f.code === 'INTENT_VERIFY_NEVER_VIOLATED' ? 'VIOLATION' : f.regression ? 'REGRESSION' : f.level.toUpperCase();
1150
1155
  console.log(` [${tag}] ${f.message}${f.line ? ` (line ${f.line})` : ''}`);
@@ -1154,7 +1159,7 @@ test Example
1154
1159
  return;
1155
1160
  }
1156
1161
 
1157
- // `intent draft --brief <json|->` , scaffold a rigorous intent draft from a structured brief,
1162
+ // `thunder draft --brief <json|->` , scaffold a rigorous intent draft from a structured brief,
1158
1163
  // plus a review checklist of what a human must still fill in. Prints the draft; --write saves it.
1159
1164
  if (cmd === 'draft') {
1160
1165
  const briefPath = args.brief || (cmd === 'draft' && file && file.endsWith('.json') ? file : null);
@@ -1164,19 +1169,19 @@ test Example
1164
1169
  try { brief = JSON.parse(raw); } catch { console.error('intent draft: --brief is not valid JSON'); process.exit(2); return; }
1165
1170
  const r = draftIntent(brief);
1166
1171
  if (args.json) { console.log(JSON.stringify(r, null, 2)); return; }
1167
- if (args.write) { writeFileSync(args.write, r.source); console.error(`intent draft: wrote ${args.write}`); }
1172
+ if (args.write) { writeFileSync(args.write, r.source); console.error(`thunder draft: wrote ${args.write}`); }
1168
1173
  else process.stdout.write(r.source);
1169
1174
  if (r.review.length) { console.error('\nreview (fill these in , the draft is a proposal, not verified):'); for (const x of r.review) console.error(` - ${x.message}`); }
1170
1175
  return;
1171
1176
  }
1172
1177
 
1173
- // `intent guard <file>` , preview what a runtime guard compiled from this intent enforces:
1178
+ // `thunder guard <file>` , preview what a runtime guard compiled from this intent enforces:
1174
1179
  // which fields it redacts (secrets/PII) and which decisions it can gate at runtime.
1175
1180
  if (cmd === 'guard') {
1176
1181
  const ast = parseIntent(readFileSync(file, 'utf8'));
1177
1182
  const g = guardSummary(ast);
1178
1183
  if (args.json) { console.log(JSON.stringify(g, null, 2)); return; }
1179
- console.log(`intent guard ${basename(file)}:`);
1184
+ console.log(`thunder guard ${basename(file)}:`);
1180
1185
  console.log(` redacts fields ${g.redactsFields.length ? g.redactsFields.join(', ') : '(none declared secret/PII)'}`);
1181
1186
  console.log(` enforces decisions ${g.enforcesDecisions.length ? g.enforcesDecisions.join(', ') : '(none)'}`);
1182
1187
  if (g.neverRules.length) { console.log(' never rules:'); for (const n of g.neverRules) console.log(` - ${n}`); }
@@ -1184,7 +1189,7 @@ test Example
1184
1189
  return;
1185
1190
  }
1186
1191
 
1187
- // `intent ledger <file.json>` , verify the tamper-evident chain, and explain a subject's history
1192
+ // `thunder ledger <file.json>` , verify the tamper-evident chain, and explain a subject's history
1188
1193
  // (why it was built, who approved it, what was assumed/corrected/verified, which risks accepted).
1189
1194
  if (cmd === 'ledger') {
1190
1195
  if (!file) { console.error('usage: intent ledger <ledger.json> [--subject <id>] [--json]'); process.exit(2); return; }
@@ -1194,7 +1199,7 @@ test Example
1194
1199
  if (args.subject) {
1195
1200
  const ex = ledgerExplain(ledger, args.subject);
1196
1201
  if (args.json) { console.log(JSON.stringify({ chain, ...ex }, null, 2)); process.exit(chain.valid ? 0 : 1); return; }
1197
- console.log(`intent ledger ${basename(file)} , ${args.subject} (chain ${chain.valid ? 'VALID' : 'BROKEN'})`);
1202
+ console.log(`thunder ledger ${basename(file)} , ${args.subject} (chain ${chain.valid ? 'VALID' : 'BROKEN'})`);
1198
1203
  if (ex.why.length) { console.log(' why built:'); for (const w of ex.why) console.log(` - ${w}`); }
1199
1204
  if (ex.approvedBy.length) console.log(` approved by: ${ex.approvedBy.join(', ')}`);
1200
1205
  if (ex.assumptions.length) console.log(` assumptions: ${ex.assumptions.length}`);
@@ -1207,12 +1212,12 @@ test Example
1207
1212
  }
1208
1213
  if (args.json) { console.log(JSON.stringify(chain, null, 2)); process.exit(chain.valid ? 0 : 1); return; }
1209
1214
  const n = (ledger.entries || []).length;
1210
- console.log(`intent ledger ${basename(file)}: ${n} entr${n === 1 ? 'y' : 'ies'}, chain ${chain.valid ? 'VALID (tamper-evident)' : `BROKEN at #${chain.brokenAt} , ${chain.reason}`}`);
1215
+ console.log(`thunder ledger ${basename(file)}: ${n} entr${n === 1 ? 'y' : 'ies'}, chain ${chain.valid ? 'VALID (tamper-evident)' : `BROKEN at #${chain.brokenAt} , ${chain.reason}`}`);
1211
1216
  process.exit(chain.valid ? 0 : 1);
1212
1217
  return;
1213
1218
  }
1214
1219
 
1215
- // `intent impact <base> <proposed>` , the Simulator: estimate a change's impact BEFORE building it
1220
+ // `thunder impact <base> <proposed>` , the Simulator: estimate a change's impact BEFORE building it
1216
1221
  // , the deterministic blast radius, the risk it would introduce, contradictions, release risk.
1217
1222
  if (cmd === 'impact') {
1218
1223
  const baseArg = args._[0]; const propArg = args._[1];
@@ -1220,7 +1225,7 @@ test Example
1220
1225
  const collect = (p) => (existsSync(p) && statSync(p).isDirectory() ? collectIntents(p) : [p]).map((f) => ({ file: relative(process.cwd(), f) || f, source: readFileSync(f, 'utf8') }));
1221
1226
  const r = simulateChange(collect(baseArg), collect(propArg));
1222
1227
  if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.summary.safe ? 0 : 1); return; }
1223
- console.log(`intent impact: ${r.summary.safe ? 'SAFE' : 'REVIEW'} (${baseArg} -> ${propArg})`);
1228
+ console.log(`thunder impact: ${r.summary.safe ? 'SAFE' : 'REVIEW'} (${baseArg} -> ${propArg})`);
1224
1229
  console.log(` change touches ${r.changedNodes} node(s); ripples to ${r.deterministicImpact.total} dependent(s)`);
1225
1230
  const bt = Object.entries(r.deterministicImpact.byType);
1226
1231
  if (bt.length) console.log(' deterministic impact by type: ' + bt.map(([t, ns]) => `${ns.length} ${t}`).join(', '));
@@ -1232,7 +1237,7 @@ test Example
1232
1237
  return;
1233
1238
  }
1234
1239
 
1235
- // `intent guardian <before> <after>` , drift detection: what a change did to the intent , which
1240
+ // `thunder guardian <before> <after>` , drift detection: what a change did to the intent , which
1236
1241
  // intent it affects, what risk it introduced, what must be reverified, what learning is stale.
1237
1242
  if (cmd === 'guardian') {
1238
1243
  const beforeArg = args._[0]; const afterArg = args._[1];
@@ -1241,7 +1246,7 @@ test Example
1241
1246
  const r = guardianReport(collect(beforeArg), collect(afterArg));
1242
1247
  if (args.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.verdict === 'needs-attention' ? 1 : 0); return; }
1243
1248
  const c = r.changed;
1244
- console.log(`intent guardian: ${r.verdict.toUpperCase()} (${beforeArg} -> ${afterArg})`);
1249
+ console.log(`thunder guardian: ${r.verdict.toUpperCase()} (${beforeArg} -> ${afterArg})`);
1245
1250
  console.log(` changed +${c.nodesAdded} / -${c.nodesRemoved} / ~${c.nodesChanged} nodes, +${c.relationshipsAdded} / -${c.relationshipsRemoved} relationships`);
1246
1251
  if (r.affectedIntent.length) console.log(` affected ${r.affectedIntent.map((n) => n.title || n.id).join(', ')}`);
1247
1252
  if (r.introducedRisk.length) { console.log(` introduced risk (${r.introducedRisk.length}):`); for (const f of r.introducedRisk.slice(0, 6)) console.log(` [${f.severity}] ${f.ruleId} , ${f.detected}`); }
@@ -1252,31 +1257,31 @@ test Example
1252
1257
  return;
1253
1258
  }
1254
1259
 
1255
- // `intent scan [dir]` , the Scanner spine: intent -> Intent IR -> explainable Fable findings ->
1260
+ // `thunder scan [dir]` , the Scanner spine: intent -> Intent IR -> explainable Fable findings ->
1256
1261
  // risk themes. Deterministic, no AI. --json for the machine report; --ir writes the merged IR.
1257
1262
  // Part 3 focused scan queries , one question each over the Intent IR + Fable findings:
1258
- // `intent risks | gaps | unverified | coverage | unknowns | contradictions [dir] [--json]`.
1263
+ // `thunder risks | gaps | unverified | coverage | unknowns | contradictions [dir] [--json]`.
1259
1264
  if (VIEWS[cmd]) {
1260
1265
  const root = file || '.';
1261
1266
  const targets = existsSync(root) && statSync(root).isDirectory() ? collectIntents(root) : [root];
1262
- if (!targets.length || !existsSync(targets[0])) { console.error(`intent ${cmd}: no .intent files under ${root}`); process.exit(2); return; }
1267
+ if (!targets.length || !existsSync(targets[0])) { console.error(`thunder ${cmd}: no .intent files under ${root}`); process.exit(2); return; }
1263
1268
  const scan = scanProject(targets.map((f) => ({ file: relative(process.cwd(), f) || f, source: readFileSync(f, 'utf8') })));
1264
1269
  const v = VIEWS[cmd](scan);
1265
1270
  if (args.json) { console.log(JSON.stringify(v, null, 2)); return; }
1266
1271
  if (cmd === 'coverage') {
1267
- console.log(`intent coverage ${root}: ${v.verified}/${v.total} claims verified (${v.coverage}%)`);
1272
+ console.log(`thunder coverage ${root}: ${v.verified}/${v.total} claims verified (${v.coverage}%)`);
1268
1273
  for (const c of v.unverified) console.log(` unverified [${c.type}] ${c.title}`);
1269
1274
  process.exit(v.coverage === 100 ? 0 : 1); return;
1270
1275
  }
1271
1276
  if (cmd === 'risks') {
1272
1277
  const s = v.bySeverity;
1273
- console.log(`intent risks ${root}: ${v.count} risk theme(s) , ${s.blocker || 0} blocker, ${s.error || 0} error, ${s.warning || 0} warning, ${s.info || 0} info`);
1278
+ console.log(`thunder risks ${root}: ${v.count} risk theme(s) , ${s.blocker || 0} blocker, ${s.error || 0} error, ${s.warning || 0} warning, ${s.info || 0} info`);
1274
1279
  for (const t of v.themes) console.log(` ${String(t.count).padStart(3)} ${t.category}${t.blocker ? ` (${t.blocker} blocker)` : ''}`);
1275
1280
  for (const r of v.remediationSequence.slice(0, 5)) console.log(` fix [${r.severity}] ${r.ruleId} (${r.count}x) , ${r.remediation}`);
1276
1281
  process.exit((s.blocker || 0) + (s.error || 0) === 0 ? 0 : 1); return;
1277
1282
  }
1278
1283
  // gaps / unverified / unknowns / contradictions , a uniform list
1279
- console.log(`intent ${cmd} ${root}: ${v.count}`);
1284
+ console.log(`thunder ${cmd} ${root}: ${v.count}`);
1280
1285
  for (const g of v.gaps || []) console.log(` [${g.severity}] ${g.ruleId} , ${g.detected}`);
1281
1286
  for (const c of v.claims || []) console.log(` [${c.type}] ${c.title}`);
1282
1287
  for (const u of v.unknowns || []) console.log(` [${u.type}] ${u.title}${u.confidence ? ` (${u.confidence})` : ''}`);
@@ -1291,10 +1296,10 @@ test Example
1291
1296
  const root = file || '.';
1292
1297
  const targets = existsSync(root) && statSync(root).isDirectory() ? collectIntents(root) : [root];
1293
1298
  const result = scanProject(targets.map((f) => ({ file: relative(process.cwd(), f) || f, source: readFileSync(f, 'utf8') })));
1294
- if (args.ir) { writeFileSync(args.ir, JSON.stringify(result.ir, null, 2)); console.error(`intent scan: wrote Intent IR (${result.ir.nodes.length} nodes) to ${args.ir}`); }
1299
+ if (args.ir) { writeFileSync(args.ir, JSON.stringify(result.ir, null, 2)); console.error(`thunder scan: wrote Intent IR (${result.ir.nodes.length} nodes) to ${args.ir}`); }
1295
1300
  if (args.json) { console.log(JSON.stringify(result, null, 2)); process.exit(result.ok ? 0 : 1); return; }
1296
1301
  const s = result.bySeverity;
1297
- console.log(`intent scan ${root}: ${result.totals.findings} finding(s) across ${result.totals.missions} mission(s) in ${result.totals.files} file(s)`);
1302
+ console.log(`thunder scan ${root}: ${result.totals.findings} finding(s) across ${result.totals.missions} mission(s) in ${result.totals.files} file(s)`);
1298
1303
  console.log(` severity ${s.blocker} blocker, ${s.error} error, ${s.warning} warning, ${s.info} info , Intent IR: ${result.ir.nodes.length} nodes`);
1299
1304
  if (result.risks.length) {
1300
1305
  console.log(' risk themes:');
@@ -1308,7 +1313,7 @@ test Example
1308
1313
  return;
1309
1314
  }
1310
1315
 
1311
- // `intent report [dir]` , a repo-wide intent health summary (aggregates every .intent file).
1316
+ // `thunder report [dir]` , a repo-wide intent health summary (aggregates every .intent file).
1312
1317
  // Distinct from `check` (pass/fail gate): counts by severity + area, top codes, and coverage.
1313
1318
  if (cmd === 'report') {
1314
1319
  const root = file || '.';
@@ -1316,7 +1321,7 @@ test Example
1316
1321
  const rep = buildReport(targets.map((f) => ({ file: relative(process.cwd(), f) || f, source: readFileSync(f, 'utf8') })));
1317
1322
  if (args.json) { console.log(JSON.stringify(rep, null, 2)); process.exit(0); return; }
1318
1323
  const c = rep.coverage;
1319
- console.log(`intent report ${root}: ${rep.totals.missions} mission(s) in ${rep.totals.files} file(s), ${rep.totals.diagnostics} diagnostic(s)`);
1324
+ console.log(`thunder report ${root}: ${rep.totals.missions} mission(s) in ${rep.totals.files} file(s), ${rep.totals.diagnostics} diagnostic(s)`);
1320
1325
  console.log(` severity ${rep.bySeverity.blocker} blocker, ${rep.bySeverity.error} error, ${rep.bySeverity.warning} warning, ${rep.bySeverity.info} info`);
1321
1326
  console.log(' coverage '
1322
1327
  + `guarantees verified ${c.guaranteesVerified}/${c.guarantees}${c.guaranteeVerifyRate != null ? ` (${c.guaranteeVerifyRate}%)` : ''}, `
@@ -1334,9 +1339,9 @@ test Example
1334
1339
  return;
1335
1340
  }
1336
1341
 
1337
- // `intent check <file|dir> --format sarif` emits a SARIF 2.1.0 log so ThunderLang
1342
+ // `thunder check <file|dir> --format sarif` emits a SARIF 2.1.0 log so ThunderLang
1338
1343
  // diagnostics show up natively in GitHub/GitLab code scanning and SARIF-aware IDEs.
1339
- // This is a REPORT (exit 0); gate the build with a plain `intent check .` step.
1344
+ // This is a REPORT (exit 0); gate the build with a plain `thunder check .` step.
1340
1345
  if (cmd === 'check' && args.format === 'sarif') {
1341
1346
  const targets = existsSync(file) && statSync(file).isDirectory() ? collectIntents(file) : [file];
1342
1347
  const reports = targets.map((f) => {
@@ -1352,7 +1357,7 @@ test Example
1352
1357
  return;
1353
1358
  }
1354
1359
 
1355
- // `intent check <dir>` recurses and gates every .intent file (self-contained CI, no
1360
+ // `thunder check <dir>` recurses and gates every .intent file (self-contained CI, no
1356
1361
  // wrapper script needed). Errors fail the run; warnings do not.
1357
1362
  if (cmd === 'check' && existsSync(file) && statSync(file).isDirectory()) {
1358
1363
  const files = collectIntents(file);
@@ -1371,12 +1376,12 @@ test Example
1371
1376
  console.log(JSON.stringify({ schema: 'intent-check-batch-v1', root: file, total: reports.length, failed: failed.length, ok: failed.length === 0, files: reports }, null, 2));
1372
1377
  process.exit(failed.length ? 1 : 0);
1373
1378
  }
1374
- console.log(`intent check ${file}: ${reports.length - failed.length}/${reports.length} passed`);
1379
+ console.log(`thunder check ${file}: ${reports.length - failed.length}/${reports.length} passed`);
1375
1380
  for (const r of reports) console.log(` ${r.ok ? 'ok ' : 'ERR'} ${r.file}${r.errors ? ` , ${r.errors} error(s)` : ''}${r.warnings ? ` (${r.warnings} warning(s))` : ''}`);
1376
1381
  process.exit(failed.length ? 1 : 0);
1377
1382
  }
1378
1383
 
1379
- // `intent edit <file>` , apply structural field edits (intent-patch-v1) to the source,
1384
+ // `thunder edit <file>` , apply structural field edits (intent-patch-v1) to the source,
1380
1385
  // preserving comments + formatting. Edits come from --edits <json|-> and/or convenience
1381
1386
  // flags. Prints the result to stdout, or --write applies it in place.
1382
1387
  if (cmd === 'edit') {
@@ -1401,7 +1406,7 @@ test Example
1401
1406
  for (const s of result.skipped) console.error(` skipped: ${s.reason}`);
1402
1407
  if (args.write) {
1403
1408
  if (result.source !== src.replace(/\r\n?/g, '\n')) writeFileSync(file, result.source);
1404
- console.error(`intent edit ${basename(file)}: applied ${result.applied.length}, skipped ${result.skipped.length}.`);
1409
+ console.error(`thunder edit ${basename(file)}: applied ${result.applied.length}, skipped ${result.skipped.length}.`);
1405
1410
  } else if (!args.json) {
1406
1411
  process.stdout.write(result.source);
1407
1412
  }
@@ -1409,7 +1414,7 @@ test Example
1409
1414
  return;
1410
1415
  }
1411
1416
 
1412
- // `intent fmt <file|dir>` , canonical formatting (whitespace only; content + comments
1417
+ // `thunder fmt <file|dir>` , canonical formatting (whitespace only; content + comments
1413
1418
  // preserved). Prints to stdout, or --write in place, or --check for CI (exit 1 if any
1414
1419
  // file is not already formatted).
1415
1420
  if (cmd === 'fmt') {
@@ -1425,18 +1430,18 @@ test Example
1425
1430
  process.stdout.write(formatted);
1426
1431
  }
1427
1432
  if (args.check) {
1428
- if (unformatted.length) { console.error(`intent fmt --check: ${unformatted.length} file(s) not formatted:`); for (const u of unformatted) console.error(` ${u}`); process.exit(1); }
1429
- console.log('intent fmt --check: all formatted.');
1433
+ if (unformatted.length) { console.error(`thunder fmt --check: ${unformatted.length} file(s) not formatted:`); for (const u of unformatted) console.error(` ${u}`); process.exit(1); }
1434
+ console.log('thunder fmt --check: all formatted.');
1430
1435
  process.exit(0);
1431
1436
  }
1432
- if (args.write) console.log(`intent fmt: formatted ${changed} file(s), ${targets.length - changed} already clean.`);
1437
+ if (args.write) console.log(`thunder fmt: formatted ${changed} file(s), ${targets.length - changed} already clean.`);
1433
1438
  return;
1434
1439
  }
1435
1440
 
1436
1441
  const { source, ast, sourceHash, sourceFile } = load(file);
1437
1442
  const generatedAt = new Date().toISOString();
1438
1443
  const diagnostics = semanticDiagnostics(ast);
1439
- const outDir = join(args.out, slug(ast.mission || basename(file, '.intent')));
1444
+ const outDir = join(args.out, slug(ast.mission || stripSourceExt(basename(file))));
1440
1445
 
1441
1446
  // Production gate: a build --mode production refuses to proceed while an AI
1442
1447
  // implementation is not shippable. --allow-pending is for dev builds only.
@@ -1497,7 +1502,7 @@ test Example
1497
1502
  console.log(JSON.stringify(out, null, 2));
1498
1503
  process.exit(errors > 0 ? 1 : 0);
1499
1504
  }
1500
- console.log(`intent check ${sourceFile} (mission: ${ast.mission})`);
1505
+ console.log(`thunder check ${sourceFile} (mission: ${ast.mission})`);
1501
1506
  process.exit(printDiagnostics(diags) > 0 ? 1 : 0);
1502
1507
  }
1503
1508
 
@@ -1526,7 +1531,7 @@ test Example
1526
1531
  console.error(`unknown command: ${cmd}`);
1527
1532
  process.exit(2);
1528
1533
  }
1529
- console.log(`intent ${cmd} ${sourceFile} -> ${outDir}`);
1534
+ console.log(`thunder ${cmd} ${sourceFile} -> ${outDir}`);
1530
1535
  for (const p of generated) console.log(` wrote ${p.replace(process.cwd() + '/', '')}`);
1531
1536
  printDiagnostics(diagnostics);
1532
1537
  }