agent-inspect 6.29.0 → 6.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/docs/API.md +4 -0
  4. package/docs/CLI.md +21 -1
  5. package/docs/LIMITATIONS.md +2 -1
  6. package/docs/NETWORK-BEHAVIOR.md +8 -0
  7. package/docs/SAFE-TRACE-SHARING.md +1 -1
  8. package/docs/STANDARDS.md +1 -1
  9. package/docs/TRACE-CONTRACTS.md +11 -0
  10. package/package.json +2 -2
  11. package/packages/cli/dist/{chunk-D7PT6TXM.mjs → chunk-UK4HD5M3.mjs} +126 -26
  12. package/packages/cli/dist/chunk-UK4HD5M3.mjs.map +1 -0
  13. package/packages/cli/dist/index.cjs +240 -106
  14. package/packages/cli/dist/index.cjs.map +1 -1
  15. package/packages/cli/dist/index.mjs +108 -82
  16. package/packages/cli/dist/index.mjs.map +1 -1
  17. package/packages/cli/dist/{src-QONEBW2F.mjs → src-CMPIJGUE.mjs} +3 -3
  18. package/packages/cli/dist/{src-QONEBW2F.mjs.map → src-CMPIJGUE.mjs.map} +1 -1
  19. package/packages/core/dist/advanced.cjs +138 -25
  20. package/packages/core/dist/advanced.cjs.map +1 -1
  21. package/packages/core/dist/advanced.d.cts +13 -2
  22. package/packages/core/dist/advanced.d.ts +13 -2
  23. package/packages/core/dist/advanced.mjs +109 -33
  24. package/packages/core/dist/advanced.mjs.map +1 -1
  25. package/packages/core/dist/checks.cjs +240 -35
  26. package/packages/core/dist/checks.cjs.map +1 -1
  27. package/packages/core/dist/checks.d.cts +2 -2
  28. package/packages/core/dist/checks.d.ts +2 -2
  29. package/packages/core/dist/checks.mjs +3 -3
  30. package/packages/core/dist/{chunk-TNOPL75V.mjs → chunk-5RF7FYGL.mjs} +3 -3
  31. package/packages/core/dist/{chunk-TNOPL75V.mjs.map → chunk-5RF7FYGL.mjs.map} +1 -1
  32. package/packages/core/dist/{chunk-3B24355S.mjs → chunk-65LFRW2T.mjs} +3 -3
  33. package/packages/core/dist/{chunk-3B24355S.mjs.map → chunk-65LFRW2T.mjs.map} +1 -1
  34. package/packages/core/dist/{chunk-MVRZ7JBH.mjs → chunk-COZPMOUF.mjs} +3 -3
  35. package/packages/core/dist/{chunk-MVRZ7JBH.mjs.map → chunk-COZPMOUF.mjs.map} +1 -1
  36. package/packages/core/dist/{chunk-LGTPSHH2.mjs → chunk-QZIXYP7U.mjs} +243 -38
  37. package/packages/core/dist/chunk-QZIXYP7U.mjs.map +1 -0
  38. package/packages/core/dist/{chunk-TIGBAVED.mjs → chunk-XRI3GB6I.mjs} +42 -5
  39. package/packages/core/dist/chunk-XRI3GB6I.mjs.map +1 -0
  40. package/packages/core/dist/exporters.cjs +10 -5
  41. package/packages/core/dist/exporters.cjs.map +1 -1
  42. package/packages/core/dist/exporters.d.cts +12 -2
  43. package/packages/core/dist/exporters.d.ts +12 -2
  44. package/packages/core/dist/exporters.mjs +13 -8
  45. package/packages/core/dist/exporters.mjs.map +1 -1
  46. package/packages/core/dist/{index-C61p7wBR.d.cts → index-BaQ48jrq.d.cts} +9 -1
  47. package/packages/core/dist/{index-DTO2iGeq.d.ts → index-CrnEOoRu.d.ts} +9 -1
  48. package/packages/core/dist/index.cjs +38 -1
  49. package/packages/core/dist/index.cjs.map +1 -1
  50. package/packages/core/dist/index.mjs +4 -4
  51. package/packages/core/dist/writers.mjs +1 -1
  52. package/packages/cli/dist/chunk-D7PT6TXM.mjs.map +0 -1
  53. package/packages/core/dist/chunk-LGTPSHH2.mjs.map +0 -1
  54. package/packages/core/dist/chunk-TIGBAVED.mjs.map +0 -1
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createSafetyOversizedAttributeRule, resolveTraceDir, parseDuration, buildSessionIndex, resolveBundleRunIds, getTraceFilePath, openTrace, sha256Hex, collectTraceSchemaVersions, aggregateBundleSafeStatus, bundleFailsOnSafety, bundleRunAssetRelativePath, buildPlaceholderArtifact, EVIDENCE_HTML_FILENAME, EVIDENCE_MANIFEST_FILENAME, buildBundleMetadata, buildBundleSummaryMarkdown, buildEvidenceHtmlShell, buildEvidenceProvenanceViewHtml, EVIDENCE_FORMAT_VERSION, inferEvidenceFileRole, buildEvidenceSafetyViewHtml, buildEvidenceDiffViewHtml, buildEvidenceCircuitViewHtml, buildEvidenceOutcomesViewHtml, buildEvidenceContractsViewHtml, buildEvidenceToolsLlmViewHtml, buildEvidenceCausalFailureViewHtml, buildEvidenceTimelineViewHtml, buildEvidenceTreeViewHtml, buildEvidenceManifest, serializeEvidenceManifest, buildZipArchive, assertBundlePathContained, enrichSessionRunRecord, normalizeBundleOutputPath, sanitizeBundleRunId, defaultBundleOutputPath, runTraceChecks, assertEvidenceRelativePath, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, TraceReadError, resolveRedactionProfile, applyProfileMetadataCaps, Redactor, flattenTree, escapeHtml, safeString, compactAttributes, stableJson, escapeMarkdown, truncateStringForProfile, extractMetadata, filterTraces, truncateName, buildRunSummary, formatDuration, formatTimestamp, renderErrorLine, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, createBaselineRegressionRule, summarizeSemanticParity, buildEvidenceCiPackage, verifyEvidenceDirectory, diffTraceEvents, renderRunDiff, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, formatStepLabel, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, resolveSuiteTemplate, defaultSuiteConfigTemplate, loadSuiteConfig, validateSuiteConfig, runSuite, renderSuiteReport, analyzeCohort, renderCohortReport, gateHasThresholds, runGate, renderGateReport, persistedInspectEventsToTraceEvents, renderStepLine, getIndent, TreeBuilder, zeroKinds, validateEvent, traceEventToPersistedInspectEvent, isPersistedInspectEvent, createRunStatusRule, createStructureOrphanRule, createStructureCycleRule, createRunDurationRule, createMaxStepDurationRule, createRequireCompletedRule, createStallDetectionRule, createObservedOutcomeRule, createRunDepthRule, createToolUsageRule, createLlmUsageRule, createStructureRelationshipRule, createStructureParallelWidthRule, summarizeObservedOutcomes, extractOutcomesFromTraceEvents, renderObservedOutcomesMarkdown, renderObservedOutcomesHtml, DEFAULT_SUITE_ARTIFACTS_DIR, parseCohortMetricList, COHORT_METRIC_IDS, parseGateList, nanoid, diffRuns, manualTraceEventsToComparableRun } from './chunk-D7PT6TXM.mjs';
2
+ import { createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createSafetyOversizedAttributeRule, resolveTraceDir, parseDuration, buildSessionIndex, resolveBundleRunIds, getTraceFilePath, openTrace, sha256Hex, collectTraceSchemaVersions, aggregateBundleSafeStatus, bundleFailsOnSafety, bundleRunAssetRelativePath, buildPlaceholderArtifact, EVIDENCE_HTML_FILENAME, EVIDENCE_MANIFEST_FILENAME, buildBundleMetadata, buildBundleSummaryMarkdown, buildEvidenceHtmlShell, buildEvidenceProvenanceViewHtml, EVIDENCE_FORMAT_VERSION, inferEvidenceFileRole, buildEvidenceSafetyViewHtml, buildEvidenceDiffViewHtml, buildEvidenceCircuitViewHtml, buildEvidenceOutcomesViewHtml, buildEvidenceContractsViewHtml, buildEvidenceToolsLlmViewHtml, buildEvidenceCausalFailureViewHtml, buildEvidenceTimelineViewHtml, buildEvidenceTreeViewHtml, buildEvidenceManifest, serializeEvidenceManifest, buildZipArchive, assertBundlePathContained, enrichSessionRunRecord, normalizeBundleOutputPath, sanitizeBundleRunId, defaultBundleOutputPath, runTraceChecks, assertEvidenceRelativePath, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, TraceReadError, resolveRedactionProfile, applyProfileMetadataCaps, Redactor, flattenTree, escapeHtml, safeString, compactAttributes, stableJson, escapeMarkdown, truncateStringForProfile, extractMetadata, filterTraces, truncateName, buildRunSummary, formatDuration, formatTimestamp, renderErrorLine, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, createBaselineRegressionRule, summarizeSemanticParity, buildEvidenceCiPackage, verifyEvidenceDirectory, diffTraceEvents, renderRunDiff, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, formatStepLabel, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, resolveSuiteTemplate, defaultSuiteConfigTemplate, loadSuiteConfig, validateSuiteConfig, runSuite, renderSuiteReport, analyzeCohort, renderCohortReport, gateHasThresholds, runGate, renderGateReport, persistedInspectEventsToTraceEvents, renderStepLine, getIndent, TreeBuilder, zeroKinds, validateEvent, traceEventToPersistedInspectEvent, isPersistedInspectEvent, createRunStatusRule, createStructureOrphanRule, createStructureCycleRule, createRunDurationRule, createMaxStepDurationRule, createRequireCompletedRule, createStallDetectionRule, createObservedOutcomeRule, createRunDepthRule, createToolUsageRule, createLlmUsageRule, createStructureRelationshipRule, createStructureParallelWidthRule, summarizeObservedOutcomes, extractOutcomesFromTraceEvents, renderObservedOutcomesMarkdown, renderObservedOutcomesHtml, DEFAULT_SUITE_ARTIFACTS_DIR, parseCohortMetricList, COHORT_METRIC_IDS, parseGateList, nanoid, diffRuns, manualTraceEventsToComparableRun } from './chunk-UK4HD5M3.mjs';
3
3
  import { realpathSync, constants as constants$1, existsSync, readFileSync } from 'fs';
4
4
  import path19 from 'path';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
6
  import { Command, Option } from 'commander';
7
- import { readFile, stat, mkdir, writeFile, unlink, appendFile, access, rm, open, readdir, constants } from 'fs/promises';
7
+ import { readFile, stat, mkdir, writeFile, unlink, appendFile, access, rm, open, readdir, lstat, constants } from 'fs/promises';
8
8
  import process2, { stdin, stdout } from 'process';
9
9
  import crypto, { createHash } from 'crypto';
10
10
  import { execFile, spawn } from 'child_process';
@@ -13,7 +13,7 @@ import { createServer } from 'http';
13
13
  import { createRequire } from 'module';
14
14
 
15
15
  // package.json
16
- var version = "6.29.0";
16
+ var version = "6.29.2";
17
17
 
18
18
  // packages/cli/src/trace-dir-scale.ts
19
19
  var TRACE_COUNT_WARN = 1e3;
@@ -3397,7 +3397,7 @@ async function newestTraceMtimeMs(traceDir) {
3397
3397
  }
3398
3398
  async function loadSessionRuns(traceDir) {
3399
3399
  try {
3400
- const mod = await import('./src-QONEBW2F.mjs');
3400
+ const mod = await import('./src-CMPIJGUE.mjs');
3401
3401
  const dbPath = mod.resolveIndexDbPath(traceDir);
3402
3402
  const status = mod.indexStatus(dbPath);
3403
3403
  if (!status.healthy) {
@@ -4396,15 +4396,14 @@ var REDACTION_POLICY_LIMITS = {
4396
4396
  maxPatterns: 32,
4397
4397
  maxPatternLength: 128,
4398
4398
  maxPatternIdLength: 64,
4399
- maxTypedQuantifier: 64
4399
+ /** Maximum policy file size before parse (bytes). */
4400
+ maxPolicyFileBytes: 64 * 1024
4400
4401
  };
4401
- var SAFE_TYPED_PATTERN = /^[A-Za-z0-9_@./:=<>\-[\]()|+*?{},\\\s^$]+$/;
4402
+ var ALLOWED_TOP_LEVEL = /* @__PURE__ */ new Set(["version", "extraKeys", "patterns"]);
4403
+ var ALLOWED_PATTERN_FIELDS = /* @__PURE__ */ new Set(["id", "type", "value", "severity"]);
4402
4404
  function isRecord6(value) {
4403
4405
  return value !== null && typeof value === "object" && !Array.isArray(value);
4404
4406
  }
4405
- function escapeRegExp(value) {
4406
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4407
- }
4408
4407
  function rejectRemotePolicyPath(policyPath) {
4409
4408
  const trimmed = policyPath.trim();
4410
4409
  if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) {
@@ -4433,43 +4432,23 @@ function validateExtraKey(key, index) {
4433
4432
  }
4434
4433
  return key;
4435
4434
  }
4436
- function assertBoundedTypedPattern(pattern, label) {
4437
- if (pattern.length === 0) throw new Error(`${label} must be non-empty.`);
4438
- if (pattern.length > REDACTION_POLICY_LIMITS.maxPatternLength) {
4439
- throw new Error(
4440
- `${label} exceeds max length ${REDACTION_POLICY_LIMITS.maxPatternLength}.`
4441
- );
4442
- }
4443
- if (!SAFE_TYPED_PATTERN.test(pattern)) {
4444
- throw new Error(
4445
- `${label} contains unsupported characters for bounded typed patterns.`
4446
- );
4447
- }
4448
- if (/\(\?/.test(pattern) || /\\[1-9]/.test(pattern)) {
4449
- throw new Error(
4450
- `${label} rejects lookaround, backreferences, and nested quantifiers.`
4451
- );
4452
- }
4453
- if (/\([^)]*[+*{][^)]*\)[+*{]/.test(pattern) || /\[[^\]]*[+*{][^\]]*\][+*{]/.test(pattern)) {
4454
- throw new Error(`${label} rejects nested quantifiers.`);
4455
- }
4456
- if (/\.[*+]/.test(pattern) || /[*+]\{/.test(pattern)) {
4457
- throw new Error(`${label} rejects unbounded .* / .+ style quantifiers.`);
4458
- }
4459
- for (const match of pattern.matchAll(/\{(\d+)(?:,(\d*))?\}/g)) {
4460
- const min = Number(match[1]);
4461
- const maxRaw = match[2];
4462
- const max = maxRaw === void 0 || maxRaw === "" ? min : Number(maxRaw);
4463
- if (!Number.isFinite(min) || !Number.isFinite(max) || min > REDACTION_POLICY_LIMITS.maxTypedQuantifier || max > REDACTION_POLICY_LIMITS.maxTypedQuantifier) {
4464
- throw new Error(
4465
- `${label} quantifiers must be <= ${REDACTION_POLICY_LIMITS.maxTypedQuantifier}.`
4466
- );
4435
+ function rejectUnknownFields(record, allowed, label) {
4436
+ for (const key of Object.keys(record)) {
4437
+ if (!allowed.has(key)) {
4438
+ throw new Error(`${label} rejects unknown field "${key}".`);
4467
4439
  }
4468
4440
  }
4469
4441
  }
4470
4442
  function compilePatternDetector(input3, index) {
4471
4443
  const label = `patterns[${index}]`;
4472
4444
  if (!isRecord6(input3)) throw new Error(`${label} must be an object.`);
4445
+ const typeEarly = input3.type;
4446
+ if (typeEarly === "typed") {
4447
+ throw new Error(
4448
+ `${label}.type "typed" is no longer supported (removed in 6.29.1). Use type "literal" or "prefix" with a fixed value; arbitrary regex patterns are not accepted.`
4449
+ );
4450
+ }
4451
+ rejectUnknownFields(input3, ALLOWED_PATTERN_FIELDS, label);
4473
4452
  const idRaw = input3.id;
4474
4453
  if (typeof idRaw !== "string" || idRaw.trim() === "") {
4475
4454
  throw new Error(`${label}.id must be a non-empty string.`);
@@ -4484,48 +4463,35 @@ function compilePatternDetector(input3, index) {
4484
4463
  throw new Error(`${label}.id must be an identifier-like detector id.`);
4485
4464
  }
4486
4465
  const type = input3.type;
4487
- if (type !== "literal" && type !== "prefix" && type !== "typed") {
4488
- throw new Error(`${label}.type must be literal, prefix, or typed.`);
4466
+ if (type !== "literal" && type !== "prefix") {
4467
+ throw new Error(`${label}.type must be literal or prefix.`);
4468
+ }
4469
+ if (input3.pattern !== void 0) {
4470
+ throw new Error(
4471
+ `${label}.pattern is not accepted; use .value with type literal or prefix.`
4472
+ );
4489
4473
  }
4490
4474
  const severity = parseSeverity(input3.severity, label);
4491
- let source;
4492
- if (type === "typed") {
4493
- if (typeof input3.pattern !== "string") {
4494
- throw new Error(`${label}.pattern must be a string for typed patterns.`);
4495
- }
4496
- assertBoundedTypedPattern(input3.pattern, `${label}.pattern`);
4497
- source = input3.pattern;
4498
- } else {
4499
- if (typeof input3.value !== "string") {
4500
- throw new Error(`${label}.value must be a string for ${type} patterns.`);
4501
- }
4502
- if (input3.value.length === 0) {
4503
- throw new Error(`${label}.value must be non-empty.`);
4504
- }
4505
- if (input3.value.length > REDACTION_POLICY_LIMITS.maxPatternLength) {
4506
- throw new Error(
4507
- `${label}.value exceeds max length ${REDACTION_POLICY_LIMITS.maxPatternLength}.`
4508
- );
4509
- }
4510
- const escaped = escapeRegExp(input3.value);
4511
- source = type === "prefix" ? `^${escaped}` : escaped;
4475
+ if (typeof input3.value !== "string") {
4476
+ throw new Error(`${label}.value must be a string for ${type} patterns.`);
4512
4477
  }
4513
- let regex;
4514
- try {
4515
- regex = new RegExp(source);
4516
- } catch (error) {
4478
+ if (input3.value.length === 0) {
4479
+ throw new Error(`${label}.value must be non-empty.`);
4480
+ }
4481
+ if (input3.value.length > REDACTION_POLICY_LIMITS.maxPatternLength) {
4517
4482
  throw new Error(
4518
- `${label} failed to compile: ${error instanceof Error ? error.message : String(error)}`
4483
+ `${label}.value exceeds max length ${REDACTION_POLICY_LIMITS.maxPatternLength}.`
4519
4484
  );
4520
4485
  }
4486
+ const needle = input3.value;
4487
+ const match = type === "prefix" ? (haystack) => haystack.startsWith(needle) : (haystack) => haystack.includes(needle);
4521
4488
  return {
4522
4489
  id: `policy.${id}`,
4523
4490
  severity,
4524
4491
  matchKind: "custom",
4525
4492
  detect({ value }) {
4526
4493
  if (typeof value !== "string") return [];
4527
- regex.lastIndex = 0;
4528
- return regex.test(value) ? [{ action: "replace", severity, matchKind: "custom" }] : [];
4494
+ return match(value) ? [{ action: "replace", severity, matchKind: "custom" }] : [];
4529
4495
  }
4530
4496
  };
4531
4497
  }
@@ -4533,6 +4499,7 @@ function compileRedactionPolicy(raw, policyPath) {
4533
4499
  if (!isRecord6(raw)) {
4534
4500
  throw new Error(`Redaction policy must be a JSON object (${policyPath}).`);
4535
4501
  }
4502
+ rejectUnknownFields(raw, ALLOWED_TOP_LEVEL, "Redaction policy");
4536
4503
  const diagnostics = [];
4537
4504
  if (raw.version !== void 0 && raw.version !== 1) {
4538
4505
  throw new Error(`Unsupported redaction policy version (supported: 1).`);
@@ -4596,14 +4563,38 @@ function compileRedactionPolicy(raw, policyPath) {
4596
4563
  async function loadRedactionPolicy(policyPath) {
4597
4564
  rejectRemotePolicyPath(policyPath);
4598
4565
  const resolved = path19.resolve(policyPath);
4599
- let text;
4566
+ let fileStat;
4600
4567
  try {
4601
- text = await readFile(resolved, "utf-8");
4568
+ fileStat = await stat(resolved);
4602
4569
  } catch (error) {
4603
4570
  throw new Error(
4604
4571
  `Failed to read --policy file "${resolved}": ${error instanceof Error ? error.message : String(error)}`
4605
4572
  );
4606
4573
  }
4574
+ if (!fileStat.isFile()) {
4575
+ throw new Error(
4576
+ `--policy must be a regular local JSON file (not a directory or special file): "${resolved}".`
4577
+ );
4578
+ }
4579
+ if (fileStat.size > REDACTION_POLICY_LIMITS.maxPolicyFileBytes) {
4580
+ throw new Error(
4581
+ `--policy file exceeds max size ${REDACTION_POLICY_LIMITS.maxPolicyFileBytes} bytes.`
4582
+ );
4583
+ }
4584
+ let buffer;
4585
+ try {
4586
+ buffer = await readFile(resolved);
4587
+ } catch (error) {
4588
+ throw new Error(
4589
+ `Failed to read --policy file "${resolved}": ${error instanceof Error ? error.message : String(error)}`
4590
+ );
4591
+ }
4592
+ if (buffer.byteLength > REDACTION_POLICY_LIMITS.maxPolicyFileBytes) {
4593
+ throw new Error(
4594
+ `--policy file exceeds max size ${REDACTION_POLICY_LIMITS.maxPolicyFileBytes} bytes.`
4595
+ );
4596
+ }
4597
+ const text = buffer.toString("utf-8");
4607
4598
  let parsed;
4608
4599
  try {
4609
4600
  parsed = JSON.parse(text);
@@ -12079,7 +12070,7 @@ function isModuleNotFound4(e) {
12079
12070
  }
12080
12071
  async function loadIndexSqlite() {
12081
12072
  try {
12082
- return await import('./src-QONEBW2F.mjs');
12073
+ return await import('./src-CMPIJGUE.mjs');
12083
12074
  } catch (e) {
12084
12075
  if (isModuleNotFound4(e)) {
12085
12076
  console.error(
@@ -12357,18 +12348,56 @@ async function workspacePathCommand(options = {}) {
12357
12348
  console.log(location.workspaceDir);
12358
12349
  }
12359
12350
  var DEFAULT_CONFIG_FILENAME = "agent-inspect.suite.json";
12351
+ var CONFIG_EXISTS_ERROR = `${DEFAULT_CONFIG_FILENAME} already exists; refusing to overwrite it.`;
12360
12352
  function printJson5(value) {
12361
12353
  console.log(JSON.stringify(value, null, 2));
12362
12354
  }
12363
12355
  function resolveCwd(options) {
12364
12356
  return path19.resolve(options.cwd ?? process.cwd());
12365
12357
  }
12358
+ function getErrorCode(error) {
12359
+ if (typeof error !== "object" || error === null || !("code" in error)) {
12360
+ return void 0;
12361
+ }
12362
+ return typeof error.code === "string" ? error.code : void 0;
12363
+ }
12364
+ function getErrorMessage(error) {
12365
+ return error instanceof Error ? error.message : String(error);
12366
+ }
12367
+ function failInit(options, message) {
12368
+ if (options.json) {
12369
+ printJson5({ ok: false, error: message });
12370
+ } else {
12371
+ console.error(`[AgentInspect] suite init failed: ${message}`);
12372
+ }
12373
+ process.exitCode = 1;
12374
+ }
12375
+ async function pathExists2(filePath) {
12376
+ try {
12377
+ await lstat(filePath);
12378
+ return true;
12379
+ } catch (error) {
12380
+ if (getErrorCode(error) === "ENOENT") return false;
12381
+ throw error;
12382
+ }
12383
+ }
12366
12384
  async function suiteInitCommand(options = {}) {
12367
12385
  const cwd = resolveCwd(options);
12368
12386
  const configPath = path19.join(cwd, DEFAULT_CONFIG_FILENAME);
12369
12387
  const template = options.template?.trim();
12370
12388
  const suiteConfig = template !== void 0 && template !== "" ? resolveSuiteTemplate(template) : defaultSuiteConfigTemplate();
12389
+ const content = `${JSON.stringify(suiteConfig, null, 2)}
12390
+ `;
12371
12391
  if (options.dryRun) {
12392
+ try {
12393
+ if (await pathExists2(configPath)) {
12394
+ failInit(options, CONFIG_EXISTS_ERROR);
12395
+ return;
12396
+ }
12397
+ } catch (error) {
12398
+ failInit(options, getErrorMessage(error));
12399
+ return;
12400
+ }
12372
12401
  if (options.json) {
12373
12402
  printJson5({
12374
12403
  ok: true,
@@ -12385,8 +12414,10 @@ async function suiteInitCommand(options = {}) {
12385
12414
  return;
12386
12415
  }
12387
12416
  try {
12388
- await writeFile(configPath, `${JSON.stringify(suiteConfig, null, 2)}
12389
- `, "utf-8");
12417
+ await writeFile(configPath, content, {
12418
+ encoding: "utf-8",
12419
+ flag: "wx"
12420
+ });
12390
12421
  if (options.json) {
12391
12422
  printJson5({
12392
12423
  ok: true,
@@ -12403,13 +12434,8 @@ async function suiteInitCommand(options = {}) {
12403
12434
  console.log(" npx agent-inspect suite validate");
12404
12435
  console.log(" npx agent-inspect suite run --json");
12405
12436
  } catch (error) {
12406
- const message = error instanceof Error ? error.message : String(error);
12407
- if (options.json) {
12408
- printJson5({ ok: false, error: message });
12409
- } else {
12410
- console.error(`[AgentInspect] suite init failed: ${message}`);
12411
- }
12412
- process.exitCode = 1;
12437
+ const message = getErrorCode(error) === "EEXIST" ? CONFIG_EXISTS_ERROR : getErrorMessage(error);
12438
+ failInit(options, message);
12413
12439
  }
12414
12440
  }
12415
12441
  async function suiteValidateCommand(options = {}) {