geotechcli 0.4.76 → 0.4.78

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/commands/ai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAolDpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmU5D;AAMD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwChE;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgDzD;AA4GD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuX3D;AAMD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0J1D;AAMD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuF5D"}
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/commands/ai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAw3DpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmU5D;AAMD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwChE;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgDzD;AA4GD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuX3D;AAMD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0J1D;AAMD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuF5D"}
@@ -3,7 +3,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } fr
3
3
  import { join, relative, resolve } from 'node:path';
4
4
  import chalk from 'chalk';
5
5
  import ora from 'ora';
6
- import { buildLLMConfig, DEFAULT_LLM_VISION_MODEL, analyzeCoreBox, classifyRMRFromImage, classifySoilFromDescription, ingestBoreholeLogDocument, inspectPdfDocument, queryGBRDocument, interpretSensorImage, runAgent, runSwarm, AgentConversation, loadProject, getProjectAgentContext, addAgentSession, addArtifact, addNote, saveNamedDataset, saveDerivedParameter, setActiveAnalysisContext, generateReport, generateReportFromCaseFile, buildProjectWorkflowReport, renderReportAsPdf, renderReportAsDocx, buildSwarmSessionProjectRecord, persistSwarmCaseFile, persistCaseFileEvidence, analyzeWorkspace, resolveWorkspaceRoot, buildProjectWorkflowRouterPrompt, generateText, routeProjectWorkflowRequest, runProjectWorkflow, } from '@geotechcli/core';
6
+ import { buildLLMConfig, DEFAULT_LLM_VISION_MODEL, analyzeCoreBox, classifyRMRFromImage, classifySoilFromDescription, ingestBoreholeLogDocument, inspectPdfDocument, queryGBRDocument, interpretSensorImage, runAgent, runSwarm, AgentConversation, loadProject, getProjectAgentContext, addAgentSession, addArtifact, addNote, saveNamedDataset, saveDerivedParameter, setActiveAnalysisContext, generateReport, generateReportFromCaseFile, buildProjectWorkflowReport, renderReportAsPdf, renderReportAsDocx, buildSwarmSessionProjectRecord, persistSwarmCaseFile, persistCaseFileEvidence, analyzeWorkspace, resolveWorkspaceRoot, buildProjectWorkflowRouterPrompt, generateText, routeProjectWorkflowRequest, runProjectWorkflow, analyzeSignalFile, } from '@geotechcli/core';
7
7
  import { heading, keyValue, renderJSON, renderRichText, success, error, warn, renderTable, info } from '../ui/terminal.js';
8
8
  import { addGlobalFlags, getGlobalFlags } from '../util/flags.js';
9
9
  import { estimateHostedBetaVisionBodyBytes, formatByteSize, HOSTED_BETA_REQUEST_LIMIT_BYTES, readVisionInput, readVisionPdfPageInputs, resolveStructuredOutputTarget, HOSTED_BETA_REQUEST_SAFE_BYTES, } from '../util/vision-output.js';
@@ -29,6 +29,8 @@ function summarizeWorkspaceManifestForAgent(manifest) {
29
29
  'Calculation/verifier pre-check:',
30
30
  `- Status: ${manifest.verifier.status}`,
31
31
  `- Findings: ${manifest.verifier.summary.blocking} blocking, ${manifest.verifier.summary.review} review, ${manifest.verifier.summary.info} info`,
32
+ `- Calculation routes: ${manifest.verifier.calculationReadiness.summary.ready} ready, ${manifest.verifier.calculationReadiness.summary.readyWithAssumptions} assumption-bound, ${manifest.verifier.calculationReadiness.summary.blocked} blocked`,
33
+ ...manifest.verifier.calculationReadiness.workflows.slice(0, 8).map((workflow) => `- ${workflow.workflow}: ${workflow.status}; missing=${workflow.missing.join(', ') || 'none'}; command=${workflow.commandTemplate}`),
32
34
  ...manifest.verifier.findings.slice(0, 8).map((finding) => `- ${finding.severity}/${finding.code}: ${finding.message}`),
33
35
  ].join('\n')
34
36
  : '';
@@ -79,6 +81,11 @@ const PROJECT_AGENT_TASKS = [
79
81
  label: 'Ground-model interpretation',
80
82
  prompt: 'Interpret the evidence-bound GroundModel from the attached workspace manifest. Summarize strata, groundwater, uncertainty, and what engineering workflows are ready.',
81
83
  },
84
+ {
85
+ task: 'calculation-readiness',
86
+ label: 'Calculation readiness and draft routing',
87
+ prompt: 'Summarize calculation readiness from the attached workspace manifest. Separate ready, assumption-bound, and blocked bearing, settlement, pile, liquefaction, slope, and FEM draft workflows. Recommend only validated deterministic GeotechCLI commands and missing user inputs.',
88
+ },
82
89
  {
83
90
  task: 'risk-analysis',
84
91
  label: 'Risk analysis',
@@ -94,6 +101,11 @@ const PROJECT_AGENT_TASKS = [
94
101
  label: 'Preliminary recommendations',
95
102
  prompt: 'Prepare preliminary geotechnical recommendations from the attached workspace manifest. Clearly mark what is evidence-backed, assumption-bound, or blocked by missing inputs.',
96
103
  },
104
+ {
105
+ task: 'signal-analysis',
106
+ label: 'Signal analysis',
107
+ prompt: 'Route monitoring, instrumentation, settlement, piezometer, inclinometer, vibration, and load-test files into deterministic geotech signal analyze commands. Summarize available sources, missing threshold assumptions, and review gates without inventing signal metrics.',
108
+ },
97
109
  {
98
110
  task: 'visualization',
99
111
  label: 'Visualizations and maps',
@@ -138,6 +150,46 @@ function normalizeProjectTask(value) {
138
150
  case 'interpretation':
139
151
  case 'ground-model-interpretation':
140
152
  return 'ground-model';
153
+ case 'calculation':
154
+ case 'calculations':
155
+ case 'calculation-readiness':
156
+ case 'calculation-routing':
157
+ case 'calc-readiness':
158
+ case 'design-readiness':
159
+ case 'bearing':
160
+ case 'bearing-capacity':
161
+ case 'settlement':
162
+ case 'pile':
163
+ case 'pile-capacity':
164
+ case 'liquefaction':
165
+ case 'slope':
166
+ case 'slope-stability':
167
+ case 'fem-readiness':
168
+ case 'fem-foundation':
169
+ case 'fem-foundation-settlement':
170
+ case 'fem-excavation':
171
+ case 'fem-excavation-deformation':
172
+ case 'fem-tunnel':
173
+ case 'fem-tunnel-settlement':
174
+ case 'fem-tunnel-volume-loss-settlement':
175
+ case 'fem-shaft':
176
+ case 'fem-shaft-deformation':
177
+ case 'fem-pile-group':
178
+ case 'fem-pile-group-elastic-interaction':
179
+ case 'fem-slope':
180
+ case 'fem-slope-embankment':
181
+ case 'fem-slope-embankment-deformation':
182
+ case 'fem-embankment':
183
+ case 'fem-retaining-wall':
184
+ case 'fem-retaining-wall-excavation-support':
185
+ case 'fem-excavation-support':
186
+ case 'fem-seepage':
187
+ case 'fem-seepage-groundwater-coupling':
188
+ case 'fem-groundwater-coupling':
189
+ case 'fem-staged-settlement':
190
+ case 'fem-staged-settlement-consolidation':
191
+ case 'fem-consolidation':
192
+ return 'calculation-readiness';
141
193
  case 'risk':
142
194
  case 'risk-analysis':
143
195
  return 'risk-analysis';
@@ -149,6 +201,24 @@ function normalizeProjectTask(value) {
149
201
  case 'recommendations':
150
202
  case 'foundation-recommendations':
151
203
  return 'recommendations';
204
+ case 'signal':
205
+ case 'signals':
206
+ case 'signal-analysis':
207
+ case 'signal-analytics':
208
+ case 'monitoring':
209
+ case 'monitoring-analysis':
210
+ case 'time-series':
211
+ case 'timeseries':
212
+ case 'instrumentation':
213
+ case 'piezometer':
214
+ case 'piezometers':
215
+ case 'inclinometer':
216
+ case 'inclinometers':
217
+ case 'vibration':
218
+ case 'load-test':
219
+ case 'load-tests':
220
+ case 'pile-load-test':
221
+ return 'signal-analysis';
152
222
  case 'viz':
153
223
  case 'visualize':
154
224
  case 'visualization':
@@ -162,7 +232,9 @@ function normalizeProjectTask(value) {
162
232
  }
163
233
  }
164
234
  function projectTaskPrompt(task, customPrompt) {
165
- const taskDef = PROJECT_AGENT_TASKS.find((item) => item.task === task) ?? PROJECT_AGENT_TASKS[6];
235
+ const taskDef = PROJECT_AGENT_TASKS.find((item) => item.task === task)
236
+ ?? PROJECT_AGENT_TASKS.find((item) => item.task === 'custom-question')
237
+ ?? PROJECT_AGENT_TASKS[0];
166
238
  if (task === 'custom-question' && customPrompt?.trim()) {
167
239
  return `Project-aware custom question: ${customPrompt.trim()}`;
168
240
  }
@@ -198,9 +270,11 @@ function inferProjectIntent(rawPrompt, selectedTask) {
198
270
  };
199
271
  add('data-quality', /\b(?:data quality|inventory|missing data|quality report|duplicate)\b/);
200
272
  add('ground-model', /\b(?:ground model|interpret|strata|stratigraphy|lithology|hydrogeology)\b/);
273
+ add('calculation-readiness', /\b(?:calculation readiness|calculation route|calculation routing|design readiness|design route|bearing(?: capacity| calculation| readiness)?|settlement(?: calculation| readiness| design| route| workflow)|pile(?: capacity| calculation| readiness)?|liquefaction(?: calculation| readiness)?|slope(?: stability| calculation| readiness)?|fem(?: draft| readiness| foundation settlement| excavation deformation)?|ready for calculation|ready for design)\b/);
201
274
  add('risk-analysis', /\b(?:risk|hazard|limitation|uncertainty|mitigation)\b/);
202
275
  add('anomaly-detection', /\b(?:anomal\w*|conflict|outlier|inconsistent|inconsistency)\b/);
203
276
  add('recommendations', /\b(?:recommend|foundation option|advice|next action)\b/);
277
+ add('signal-analysis', /\b(?:signal analysis|signal analytics|monitoring analysis|time[-\s]?series|instrumentation|piezometer|pore pressure|inclinometer|vibration|accelerometer|settlement monitoring|monitoring trend|threshold|trigger level|load[-\s]?test)\b/);
204
278
  add('visualization', /\b(?:visual\w*|map|plot|chart|section|profile|strip log)\b/);
205
279
  const uniqueTasks = [...new Set(tasks)];
206
280
  if (uniqueTasks.length === 0) {
@@ -224,6 +298,9 @@ function projectReadinessFromManifest(manifest) {
224
298
  const hasGroundModel = Boolean(manifest.groundModel && manifest.groundModel.stats.evidenceRefs > 0);
225
299
  const hasCoordinates = Boolean(manifest.groundModel?.map?.points?.length);
226
300
  const hasVerifier = Boolean(manifest.verifier);
301
+ const signalSources = (manifest.summary.datasetTypes['monitoring-time-series'] ?? 0)
302
+ + (manifest.summary.datasetTypes['signal-record'] ?? 0)
303
+ + (manifest.summary.datasetTypes['pile-load-test'] ?? 0);
227
304
  const calculationWorkflows = manifest.verifier?.calculationReadiness.workflows ?? [];
228
305
  const readyWorkflowCount = calculationWorkflows.filter((workflow) => workflow.status !== 'blocked').length;
229
306
  const verifierMissing = calculationWorkflows.flatMap((workflow) => workflow.missing).slice(0, 8);
@@ -256,6 +333,15 @@ function projectReadinessFromManifest(manifest) {
256
333
  : ['Risk analysis needs GroundModel verification output.'],
257
334
  missing: hasVerifier ? verifierMissing : ['GroundModel verifier output'],
258
335
  },
336
+ {
337
+ task: 'calculation-readiness',
338
+ label: 'Calculation readiness and draft routing',
339
+ status: readyWorkflowCount > 0 ? 'partially_ready' : hasVerifier ? 'blocked' : 'blocked',
340
+ reasons: hasVerifier
341
+ ? [`${readyWorkflowCount} calculation workflow(s) are ready or assumption-bound; ${calculationWorkflows.filter((workflow) => workflow.status === 'blocked').length} blocked.`]
342
+ : ['Calculation readiness needs GroundModel verifier output.'],
343
+ missing: hasVerifier ? verifierMissing : ['GroundModel verifier output'],
344
+ },
259
345
  {
260
346
  task: 'anomaly-detection',
261
347
  label: 'Anomaly detection / data conflicts',
@@ -274,6 +360,15 @@ function projectReadinessFromManifest(manifest) {
274
360
  : ['No downstream calculation workflow is ready yet.'],
275
361
  missing: verifierMissing,
276
362
  },
363
+ {
364
+ task: 'signal-analysis',
365
+ label: 'Signal analysis',
366
+ status: signalSources > 0 ? 'partially_ready' : 'blocked',
367
+ reasons: signalSources > 0
368
+ ? [`${signalSources} monitoring/signal/load-test source(s) can be routed into deterministic signal analysis.`]
369
+ : ['Signal analysis needs settlement, piezometer, inclinometer, vibration, load-test, or time-series data.'],
370
+ missing: signalSources > 0 ? ['project-specific thresholds / trigger levels'] : ['monitoring/signal CSV, TSV, or XLSX files'],
371
+ },
277
372
  {
278
373
  task: 'visualization',
279
374
  label: 'Visualizations and maps',
@@ -490,6 +585,177 @@ function relativeArtifactPath(rootPath, filePath) {
490
585
  const rel = relative(rootPath, filePath);
491
586
  return rel && !rel.startsWith('..') ? rel : filePath;
492
587
  }
588
+ const SIGNAL_WORKFLOW_DATASET_TYPES = new Set(['monitoring-time-series', 'signal-record', 'pile-load-test']);
589
+ function detectSignalWorkflowSources(manifest) {
590
+ const sources = [];
591
+ const seen = new Set();
592
+ for (const file of manifest.files) {
593
+ const schemas = file.schemas ?? [];
594
+ const matchingSchemas = schemas.filter((schema) => SIGNAL_WORKFLOW_DATASET_TYPES.has(schema.datasetType));
595
+ if (matchingSchemas.length > 0) {
596
+ for (const schema of matchingSchemas) {
597
+ const key = `${file.absolutePath}#${schema.sheetName ?? ''}`;
598
+ if (seen.has(key))
599
+ continue;
600
+ seen.add(key);
601
+ sources.push({
602
+ file,
603
+ label: schema.sheetName ? `${file.path}#${schema.sheetName}` : file.path,
604
+ signalType: inferSignalWorkflowType(file, schema),
605
+ sheetName: schema.sheetName,
606
+ });
607
+ }
608
+ continue;
609
+ }
610
+ if (SIGNAL_WORKFLOW_DATASET_TYPES.has(file.classification.datasetType)) {
611
+ const key = `${file.absolutePath}#`;
612
+ if (seen.has(key))
613
+ continue;
614
+ seen.add(key);
615
+ sources.push({
616
+ file,
617
+ label: file.path,
618
+ signalType: inferSignalWorkflowType(file),
619
+ });
620
+ }
621
+ }
622
+ return sources;
623
+ }
624
+ function inferSignalWorkflowType(file, schema) {
625
+ if (schema?.datasetType === 'pile-load-test' || file.classification.datasetType === 'pile-load-test')
626
+ return 'load-test';
627
+ const roles = new Set(schema?.columns.flatMap((column) => column.roles) ?? []);
628
+ if (roles.has('settlement'))
629
+ return 'settlement';
630
+ if (roles.has('pore_pressure'))
631
+ return 'piezometer';
632
+ if (roles.has('inclination'))
633
+ return 'inclinometer';
634
+ if (roles.has('vibration'))
635
+ return 'vibration';
636
+ const text = [file.path, file.classification.datasetType, ...file.classification.signals].join(' ').toLowerCase();
637
+ if (/\b(load[-_\s]?test|pile[-_\s]?load)\b/.test(text))
638
+ return 'load-test';
639
+ if (/\b(settlement|heave|subsidence)\b/.test(text))
640
+ return 'settlement';
641
+ if (/\b(piezometer|pore[-_\s]?pressure|groundwater|water[-_\s]?level)\b/.test(text))
642
+ return 'piezometer';
643
+ if (/\b(inclinometer|inclination|tilt|deflection)\b/.test(text))
644
+ return 'inclinometer';
645
+ if (/\b(vibration|accelerometer|seismic|fft|psd)\b/.test(text))
646
+ return 'vibration';
647
+ return 'unknown';
648
+ }
649
+ function signalArtifactSlug(value) {
650
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 64) || 'signal';
651
+ }
652
+ async function persistSignalAnalysisArtifacts(options) {
653
+ if (options.workflowRun.task !== 'signal-analysis')
654
+ return;
655
+ const sources = detectSignalWorkflowSources(options.manifest).slice(0, 12);
656
+ if (sources.length === 0)
657
+ return;
658
+ const signalDir = join(options.runDir, 'signals');
659
+ mkdirSync(signalDir, { recursive: true });
660
+ const indexEntries = [];
661
+ let successCount = 0;
662
+ let failureCount = 0;
663
+ for (const [index, source] of sources.entries()) {
664
+ const artifactPath = join(signalDir, `${String(index + 1).padStart(2, '0')}-${signalArtifactSlug(source.label)}.analysis.json`);
665
+ const analyzeOptions = {
666
+ ...(source.signalType !== 'unknown' ? { type: source.signalType } : {}),
667
+ ...(source.sheetName ? { sheetName: source.sheetName } : {}),
668
+ maxRows: 5000,
669
+ };
670
+ try {
671
+ const result = await analyzeSignalFile(source.file.absolutePath, analyzeOptions);
672
+ writeFileSync(artifactPath, JSON.stringify(result, null, 2), 'utf-8');
673
+ successCount += 1;
674
+ const relativePath = relativeArtifactPath(options.plan.workspace.rootPath, artifactPath);
675
+ const status = result.warnings.length > 0 ? 'review' : 'pass';
676
+ indexEntries.push({
677
+ source: source.file.path,
678
+ absolutePath: source.file.absolutePath,
679
+ sheetName: source.sheetName,
680
+ status,
681
+ signalType: result.signalType,
682
+ rowsAnalyzed: result.source.rowsAnalyzed,
683
+ rowsRejected: result.source.rowsRejected,
684
+ series: result.series.length,
685
+ thresholdFlags: result.thresholdFlags.length,
686
+ missingIntervals: result.missingIntervals.length,
687
+ warnings: result.warnings,
688
+ artifact: relativePath,
689
+ });
690
+ options.workflowRun.artifacts.push({
691
+ kind: 'json',
692
+ path: relativePath,
693
+ description: `Deterministic signal analysis for ${source.label}`,
694
+ });
695
+ options.workflowRun.trace.steps.push({
696
+ type: 'tool_call',
697
+ name: 'signal.analyze_file',
698
+ status,
699
+ detail: `Analyzed ${source.label} into ${relativePath}.`,
700
+ });
701
+ options.workflowRun.toolCalls.push({
702
+ type: 'tool_call',
703
+ tool: 'signal.analyze_file',
704
+ status,
705
+ summary: `Analyzed ${source.label}: ${result.source.rowsAnalyzed} rows, ${result.series.length} series, ${result.thresholdFlags.length} threshold flag(s), ${result.missingIntervals.length} missing interval(s).`,
706
+ });
707
+ }
708
+ catch (err) {
709
+ failureCount += 1;
710
+ const message = err instanceof Error ? err.message : String(err);
711
+ indexEntries.push({
712
+ source: source.file.path,
713
+ absolutePath: source.file.absolutePath,
714
+ sheetName: source.sheetName,
715
+ status: 'blocked',
716
+ signalType: source.signalType,
717
+ error: message,
718
+ });
719
+ options.workflowRun.trace.steps.push({
720
+ type: 'tool_call',
721
+ name: 'signal.analyze_file',
722
+ status: 'blocked',
723
+ detail: `Could not analyze ${source.label}: ${message}`,
724
+ });
725
+ options.workflowRun.toolCalls.push({
726
+ type: 'tool_call',
727
+ tool: 'signal.analyze_file',
728
+ status: 'blocked',
729
+ summary: `Could not analyze ${source.label}: ${message}`,
730
+ });
731
+ }
732
+ }
733
+ if (failureCount > 0) {
734
+ options.workflowRun.status = successCount > 0 && options.workflowRun.status !== 'blocked' ? 'review' : 'blocked';
735
+ }
736
+ const indexPath = join(signalDir, 'index.json');
737
+ const indexPayload = {
738
+ schemaVersion: 'geotech.signal-analysis-artifact-index.v1',
739
+ generatedAt: options.workflowRun.generatedAt,
740
+ runId: options.workflowRun.runId,
741
+ task: options.workflowRun.task,
742
+ sources: indexEntries,
743
+ };
744
+ writeFileSync(indexPath, JSON.stringify(indexPayload, null, 2), 'utf-8');
745
+ const relativeIndexPath = relativeArtifactPath(options.plan.workspace.rootPath, indexPath);
746
+ options.workflowRun.artifacts.push({
747
+ kind: 'json',
748
+ path: relativeIndexPath,
749
+ description: 'Deterministic signal analysis artifact index',
750
+ });
751
+ options.workflowRun.summary.push(`Signal analyses persisted: ${successCount}/${sources.length} source(s) under ${relativeArtifactPath(options.plan.workspace.rootPath, signalDir)}.`);
752
+ options.workflowRun.toolCalls.push({
753
+ type: 'tool_call',
754
+ tool: 'signal.analysis_artifacts',
755
+ status: failureCount > 0 ? 'review' : 'pass',
756
+ summary: `Persisted ${successCount}/${sources.length} deterministic signal analysis artifact(s).`,
757
+ });
758
+ }
493
759
  async function requestProjectWorkflowRouteProposal(options) {
494
760
  const routePrompt = buildProjectWorkflowRouterPrompt({
495
761
  prompt: options.prompt,
@@ -573,7 +839,7 @@ function renderProjectAwarePlan(plan, flags) {
573
839
  }
574
840
  console.log('');
575
841
  warn('No model-heavy workflow has run yet. Choose a task with --task, or ask a project question with --workspace.');
576
- console.log(chalk.gray(' Next actions: geotech agent --task data-quality | --task ground-model | --task risk-analysis | --task anomaly-detection | --task recommendations | --task visualization'));
842
+ console.log(chalk.gray(' Next actions: geotech agent --task data-quality | --task ground-model | --task calculation-readiness | --task risk-analysis | --task anomaly-detection | --task recommendations | --task signal-analysis | --task visualization'));
577
843
  success(`Project state written to ${relativeArtifactPath(plan.workspace.rootPath, join(plan.workspace.rootPath, '.geotech'))}`);
578
844
  }
579
845
  async function renderAndPersistProjectWorkflow(plan, manifest, task, flags) {
@@ -583,11 +849,12 @@ async function renderAndPersistProjectWorkflow(plan, manifest, task, flags) {
583
849
  runId: plan.runId,
584
850
  now: plan.project.generatedAt,
585
851
  });
586
- const report = buildProjectWorkflowReport(workflowRun);
587
852
  const runDir = join(plan.workspace.rootPath, '.geotech', 'runs', plan.runId);
588
853
  const resultPath = join(runDir, 'workflow_result.json');
589
854
  const reportPath = join(runDir, 'workflow_report.md');
590
855
  const tracePath = join(runDir, 'workflow_trace.json');
856
+ await persistSignalAnalysisArtifacts({ plan, manifest, workflowRun, runDir });
857
+ const report = buildProjectWorkflowReport(workflowRun);
591
858
  writeFileSync(resultPath, JSON.stringify(workflowRun, null, 2), 'utf-8');
592
859
  writeFileSync(reportPath, report.fullMarkdown, 'utf-8');
593
860
  writeFileSync(tracePath, JSON.stringify(workflowRun.trace, null, 2), 'utf-8');
@@ -660,12 +927,13 @@ async function renderAndPersistProjectWorkflowRoute(plan, manifest, route, flags
660
927
  runId: workflowRunId,
661
928
  now: plan.project.generatedAt,
662
929
  });
663
- const report = buildProjectWorkflowReport(workflowRun);
664
930
  const runDir = join(plan.workspace.rootPath, '.geotech', 'runs', workflowRun.runId);
665
931
  mkdirSync(runDir, { recursive: true });
666
932
  const resultPath = join(runDir, 'workflow_result.json');
667
933
  const reportPath = join(runDir, 'workflow_report.md');
668
934
  const tracePath = join(runDir, 'workflow_trace.json');
935
+ await persistSignalAnalysisArtifacts({ plan, manifest, workflowRun, runDir });
936
+ const report = buildProjectWorkflowReport(workflowRun);
669
937
  writeFileSync(resultPath, JSON.stringify(workflowRun, null, 2), 'utf-8');
670
938
  writeFileSync(reportPath, report.fullMarkdown, 'utf-8');
671
939
  writeFileSync(tracePath, JSON.stringify(workflowRun.trace, null, 2), 'utf-8');
@@ -1791,14 +2059,14 @@ function renderSwarmStep(step, json, quiet = false) {
1791
2059
  }
1792
2060
  export function registerAgentCommand(program) {
1793
2061
  const cmd = new Command('agent')
1794
- .description('Agentic AI - reasons about your problem and executes real calculations')
2062
+ .description('Agentic AI - routes evidence into deterministic tools; FEM execution remains human-invoked and experimental')
1795
2063
  .argument('[task...]', 'Engineering task in natural language')
1796
2064
  .option('--swarm', 'Use the role-based multi-agent swarm planner and specialist review loop')
1797
2065
  .option('--skills', 'Enable installed skill tools for this session')
1798
2066
  .option('--project <id>', 'Load and persist context to a stored project')
1799
2067
  .option('--workspace <dir>', 'Scan a local workspace and attach its manifest summary to the agent task')
1800
2068
  .option('--no-workspace', 'Disable automatic project-aware workspace discovery')
1801
- .option('--task <task>', 'Run a project-aware task: data-quality, ground-model, risk-analysis, anomaly-detection, recommendations, visualization')
2069
+ .option('--task <task>', 'Run a project-aware task: data-quality, ground-model, calculation-readiness, risk-analysis, anomaly-detection, recommendations, signal-analysis, visualization')
1802
2070
  .option('--route-with-model', 'Let the configured LLM propose a validated workflow route when deterministic routing needs selection')
1803
2071
  .option('--plan-only', 'Scan the workspace, write .geotech project state, and show workflow readiness without calling an LLM')
1804
2072
  .option('--refresh', 'Refresh the deterministic workspace manifest and .geotech project state')