truthmark 2.2.6 → 2.2.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/dist/main.js CHANGED
@@ -54,6 +54,9 @@ var isPathInsideRoot = (rootDir, targetPath) => {
54
54
  var isNodeErrorWithCode = (error, code) => {
55
55
  return error instanceof Error && "code" in error && error.code === code;
56
56
  };
57
+ var pathSegments = (absolutePath) => {
58
+ return absolutePath.split(path.sep);
59
+ };
57
60
  var joinMissingSegments = (resolvedPath, missingSegments) => {
58
61
  return missingSegments.reduce((currentResolvedPath, segment) => {
59
62
  return path.join(currentResolvedPath, segment);
@@ -98,6 +101,43 @@ var resolveRepoPath = (rootDir, relativePath) => {
98
101
  }
99
102
  return resolvedPath;
100
103
  };
104
+ var isSafeExactFile = async (rootDir, relativePath, allowMissing) => {
105
+ try {
106
+ const absolutePath = resolveRepoPath(rootDir, relativePath);
107
+ await assertRepoContainment(rootDir, absolutePath);
108
+ const relative = path.relative(rootDir, absolutePath);
109
+ const segments = pathSegments(relative);
110
+ if (segments.length === 0 || segments.every((segment) => segment.length === 0)) {
111
+ return false;
112
+ }
113
+ let current = rootDir;
114
+ for (let index = 0; index < segments.length; index += 1) {
115
+ const segment = segments[index];
116
+ current = path.join(current, segment);
117
+ try {
118
+ const stat = await fs.lstat(current);
119
+ if (stat.isSymbolicLink()) {
120
+ return false;
121
+ }
122
+ const isFinal = index === segments.length - 1;
123
+ if (isFinal) {
124
+ return stat.isFile() && stat.nlink === 1;
125
+ }
126
+ if (!stat.isDirectory()) {
127
+ return false;
128
+ }
129
+ } catch (error) {
130
+ if (!isNodeErrorWithCode(error, "ENOENT")) {
131
+ throw error;
132
+ }
133
+ return allowMissing;
134
+ }
135
+ }
136
+ return false;
137
+ } catch {
138
+ return false;
139
+ }
140
+ };
101
141
  var assertRepoContainment = async (rootDir, targetPath) => {
102
142
  const [resolvedRootDir, resolvedTargetPath] = await Promise.all([
103
143
  resolveThroughExistingAncestor(rootDir),
@@ -360,7 +400,6 @@ var DERIVED_TRUTHMARK_PATHS = {
360
400
  portalOutput: "generated/portal",
361
401
  portalTemplate: "templates/portal.html"
362
402
  };
363
- var DEFAULT_INSTRUCTION_TARGETS = ["AGENTS.md"];
364
403
  var createDefaultRawConfig = () => ({
365
404
  version: 2,
366
405
  truthmark: {
@@ -369,7 +408,6 @@ var createDefaultRawConfig = () => ({
369
408
  portal: { ...DEFAULT_TRUTHMARK_WORKSPACE.generated.portal }
370
409
  }
371
410
  },
372
- instruction_targets: [...DEFAULT_INSTRUCTION_TARGETS],
373
411
  frontmatter: {
374
412
  required: [],
375
413
  recommended: ["status", "last_reviewed"]
@@ -412,7 +450,6 @@ var createDefaultConfig = () => ({
412
450
  "docs/truthmark/templates/*.md"
413
451
  ]
414
452
  },
415
- instructionTargets: [...DEFAULT_INSTRUCTION_TARGETS],
416
453
  frontmatter: {
417
454
  required: [],
418
455
  recommended: ["status", "last_reviewed"]
@@ -612,14 +649,14 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
612
649
  const diagnostics = [];
613
650
  const truthDocumentEntries = [];
614
651
  for (const rawEntry of rawEntries) {
615
- const path12 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
652
+ const path13 = rawEntry && typeof rawEntry === "object" && "path" in rawEntry ? rawEntry.path : null;
616
653
  const kind = rawEntry && typeof rawEntry === "object" && "kind" in rawEntry ? rawEntry.kind : null;
617
654
  const lane = rawEntry && typeof rawEntry === "object" && "lane" in rawEntry ? rawEntry.lane : null;
618
- const inferredKind = typeof path12 === "string" ? inferTruthDocumentKindFromPath(path12, options) : null;
619
- const inferredLane = typeof path12 === "string" ? inferTruthDocumentLaneFromPath(path12, options) : null;
655
+ const inferredKind = typeof path13 === "string" ? inferTruthDocumentKindFromPath(path13, options) : null;
656
+ const inferredLane = typeof path13 === "string" ? inferTruthDocumentLaneFromPath(path13, options) : null;
620
657
  const normalizedKind = isTruthDocumentKind(kind) ? kind : inferredKind;
621
658
  const normalizedLane = lane === "product" || lane === "engineering" ? lane : normalizedKind ? laneForTruthDocumentKind(normalizedKind) : inferredLane;
622
- if (typeof path12 !== "string" || path12.trim().length === 0 || !normalizedKind || !normalizedLane) {
659
+ if (typeof path13 !== "string" || path13.trim().length === 0 || !normalizedKind || !normalizedLane) {
623
660
  diagnostics.push(
624
661
  createAreaDiagnostic(
625
662
  `Area ${areaName} truth_documents entries must include non-empty path plus valid lane and kind fields.`,
@@ -629,7 +666,7 @@ var parseTruthDocumentsFromYaml = (sectionLines, areaName, options) => {
629
666
  continue;
630
667
  }
631
668
  truthDocumentEntries.push({
632
- path: path12.trim(),
669
+ path: path13.trim(),
633
670
  kind: normalizedKind,
634
671
  kindSource: isTruthDocumentKind(kind) ? "explicit" : "inferred",
635
672
  lane: normalizedLane,
@@ -1737,7 +1774,9 @@ var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
1737
1774
  var validateWorkspacePaths = (rawConfig, configPath) => {
1738
1775
  const diagnostics = [];
1739
1776
  const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
1740
- if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some((forbidden) => pathsOverlap(workspace, forbidden))) {
1777
+ if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some(
1778
+ (forbidden) => pathsOverlap(workspace, forbidden)
1779
+ )) {
1741
1780
  diagnostics.push(
1742
1781
  toConfigDiagnostic(
1743
1782
  "truthmark.workspace must be a non-empty repo-relative directory that does not overlap repository control, package, source, test, or instruction paths.",
@@ -1745,30 +1784,35 @@ var validateWorkspacePaths = (rawConfig, configPath) => {
1745
1784
  )
1746
1785
  );
1747
1786
  }
1748
- for (const target of rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS) {
1749
- if (isUnsafeRepoRelativePath(target) || pathsOverlap(workspace, target)) {
1750
- diagnostics.push(
1751
- toConfigDiagnostic(
1752
- "instruction_targets must be repo-relative files outside truthmark.workspace.",
1753
- configPath
1754
- )
1755
- );
1756
- }
1757
- }
1758
1787
  return diagnostics;
1759
1788
  };
1760
1789
  var normalizeConfig = (rawConfig) => {
1761
1790
  const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
1762
- const routesIndex = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.routesIndex);
1763
- const routeAreasRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.routeAreasRoot);
1764
- const productTruthRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.productTruthRoot);
1791
+ const routesIndex = joinWorkspacePath(
1792
+ workspace,
1793
+ DERIVED_TRUTHMARK_PATHS.routesIndex
1794
+ );
1795
+ const routeAreasRoot = joinWorkspacePath(
1796
+ workspace,
1797
+ DERIVED_TRUTHMARK_PATHS.routeAreasRoot
1798
+ );
1799
+ const productTruthRoot = joinWorkspacePath(
1800
+ workspace,
1801
+ DERIVED_TRUTHMARK_PATHS.productTruthRoot
1802
+ );
1765
1803
  const engineeringTruthRoot = joinWorkspacePath(
1766
1804
  workspace,
1767
1805
  DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
1768
1806
  );
1769
- const templatesRoot = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.templatesRoot);
1807
+ const templatesRoot = joinWorkspacePath(
1808
+ workspace,
1809
+ DERIVED_TRUTHMARK_PATHS.templatesRoot
1810
+ );
1770
1811
  const portalOutput = portalOutputFor(workspace);
1771
- const portalTemplate = joinWorkspacePath(workspace, DERIVED_TRUTHMARK_PATHS.portalTemplate);
1812
+ const portalTemplate = joinWorkspacePath(
1813
+ workspace,
1814
+ DERIVED_TRUTHMARK_PATHS.portalTemplate
1815
+ );
1772
1816
  return {
1773
1817
  version: rawConfig.version,
1774
1818
  platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
@@ -1809,7 +1853,6 @@ var normalizeConfig = (rawConfig) => {
1809
1853
  `${templatesRoot}/*.md`
1810
1854
  ]
1811
1855
  },
1812
- instructionTargets: rawConfig.instruction_targets ?? [...DEFAULT_INSTRUCTION_TARGETS],
1813
1856
  frontmatter: {
1814
1857
  required: rawConfig.frontmatter?.required ?? [],
1815
1858
  recommended: rawConfig.frontmatter?.recommended ?? []
@@ -1817,6 +1860,19 @@ var normalizeConfig = (rawConfig) => {
1817
1860
  ignore: rawConfig.ignore ?? []
1818
1861
  };
1819
1862
  };
1863
+ var compatibilityDiagnostics = (rawConfig, configPath) => {
1864
+ if (!("instruction_targets" in rawConfig)) {
1865
+ return [];
1866
+ }
1867
+ return [
1868
+ {
1869
+ category: "config",
1870
+ severity: "review",
1871
+ message: "instruction_targets is accepted for compatibility but ignored; select platforms to control managed instruction-file writes.",
1872
+ file: configPath
1873
+ }
1874
+ ];
1875
+ };
1820
1876
  var loadConfig = async (rootDir) => {
1821
1877
  const absolutePath = resolveRepoPath(rootDir, CONFIG_PATH2);
1822
1878
  let source;
@@ -1827,7 +1883,9 @@ var loadConfig = async (rootDir) => {
1827
1883
  return {
1828
1884
  status: "missing",
1829
1885
  config: null,
1830
- diagnostics: [toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH2)],
1886
+ diagnostics: [
1887
+ toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH2)
1888
+ ],
1831
1889
  configPath: CONFIG_PATH2
1832
1890
  };
1833
1891
  }
@@ -1849,7 +1907,10 @@ var loadConfig = async (rootDir) => {
1849
1907
  configPath: CONFIG_PATH2
1850
1908
  };
1851
1909
  }
1852
- const unsupportedDiagnostics = unsupportedShapeDiagnostics(parsedConfig, CONFIG_PATH2);
1910
+ const unsupportedDiagnostics = unsupportedShapeDiagnostics(
1911
+ parsedConfig,
1912
+ CONFIG_PATH2
1913
+ );
1853
1914
  if (unsupportedDiagnostics.length > 0) {
1854
1915
  return {
1855
1916
  status: "invalid",
@@ -1862,16 +1923,21 @@ var loadConfig = async (rootDir) => {
1862
1923
  return {
1863
1924
  status: "invalid",
1864
1925
  config: null,
1865
- diagnostics: (validateTruthmarkConfig.errors ?? []).map((error) => {
1866
- const propertyPath = error.instancePath || "/";
1867
- const additionalProperty = error.keyword === "additionalProperties" && error.params && "additionalProperty" in error.params ? String(error.params.additionalProperty) : null;
1868
- const message = additionalProperty ? `${propertyPath} additional property ${additionalProperty} is not allowed` : `${propertyPath} ${error.message ?? "is invalid"}`.trim();
1869
- return toConfigDiagnostic(message, CONFIG_PATH2);
1870
- }),
1926
+ diagnostics: (validateTruthmarkConfig.errors ?? []).map(
1927
+ (error) => {
1928
+ const propertyPath = error.instancePath || "/";
1929
+ const additionalProperty = error.keyword === "additionalProperties" && error.params && "additionalProperty" in error.params ? String(error.params.additionalProperty) : null;
1930
+ const message = additionalProperty ? `${propertyPath} additional property ${additionalProperty} is not allowed` : `${propertyPath} ${error.message ?? "is invalid"}`.trim();
1931
+ return toConfigDiagnostic(message, CONFIG_PATH2);
1932
+ }
1933
+ ),
1871
1934
  configPath: CONFIG_PATH2
1872
1935
  };
1873
1936
  }
1874
- const pathDiagnostics = validateWorkspacePaths(parsedConfig, CONFIG_PATH2);
1937
+ const pathDiagnostics = validateWorkspacePaths(
1938
+ parsedConfig,
1939
+ CONFIG_PATH2
1940
+ );
1875
1941
  if (pathDiagnostics.length > 0) {
1876
1942
  return {
1877
1943
  status: "invalid",
@@ -1883,161 +1949,14 @@ var loadConfig = async (rootDir) => {
1883
1949
  return {
1884
1950
  status: "loaded",
1885
1951
  config: normalizeConfig(parsedConfig),
1886
- diagnostics: [],
1952
+ diagnostics: compatibilityDiagnostics(
1953
+ parsedConfig,
1954
+ CONFIG_PATH2
1955
+ ),
1887
1956
  configPath: CONFIG_PATH2
1888
1957
  };
1889
1958
  };
1890
1959
 
1891
- // src/init/hierarchy.ts
1892
- import fs5 from "fs/promises";
1893
- var truthRoot2 = resolveTruthDocsRoot;
1894
- var BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-behavior.md";
1895
- var CONTRACT_DOC_TEMPLATE_FILE_NAME = "engineering-contract.md";
1896
- var ARCHITECTURE_DOC_TEMPLATE_FILE_NAME = "engineering-architecture.md";
1897
- var WORKFLOW_DOC_TEMPLATE_FILE_NAME = "engineering-workflow.md";
1898
- var OPERATIONS_DOC_TEMPLATE_FILE_NAME = "engineering-operations.md";
1899
- var TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-test-behavior.md";
1900
- var PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME = "product-capability.md";
1901
- var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
1902
- const rootIndexSource = await fs5.readFile(
1903
- resolveRepoPath(rootDir, rootIndexPath),
1904
- "utf8"
1905
- );
1906
- const parsedRootIndex = parseAreasMarkdown(rootIndexSource);
1907
- return parsedRootIndex.areaFileReferences.some(
1908
- (areaReference) => areaReference.areaFiles.includes(childRoutePath)
1909
- );
1910
- };
1911
- var truthTemplatePath = (config, fileName) => {
1912
- return `${config.truthmark.paths.templatesRoot}/${fileName}`;
1913
- };
1914
- var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
1915
- const seededResult = await ensureRepoFile(
1916
- rootDir,
1917
- templatePath,
1918
- defaultTemplate
1919
- );
1920
- if (seededResult.status !== "unchanged") {
1921
- return seededResult;
1922
- }
1923
- const existingTemplate = await fs5.readFile(
1924
- resolveRepoPath(rootDir, templatePath),
1925
- "utf8"
1926
- );
1927
- const mergedTemplate = mergeTruthDocTemplate(
1928
- existingTemplate,
1929
- defaultTemplate
1930
- );
1931
- return writeRepoFile(rootDir, templatePath, mergedTemplate);
1932
- };
1933
- var scaffoldHierarchy = async (rootDir, config) => {
1934
- const results = [];
1935
- const truthDocsRoot = truthRoot2(config);
1936
- const truthDomainRoot = `${truthDocsRoot}/${config.truthmark.routes.defaultArea}`;
1937
- const childRoutePath = `${config.truthmark.paths.routeAreasRoot}/${config.truthmark.routes.defaultArea}.md`;
1938
- results.push(
1939
- await ensureRepoFile(
1940
- rootDir,
1941
- config.truthmark.paths.routesIndex,
1942
- renderHierarchicalAreasIndexTemplate(config)
1943
- )
1944
- );
1945
- if (await rootIndexReferencesChildRoute(
1946
- rootDir,
1947
- config.truthmark.paths.routesIndex,
1948
- childRoutePath
1949
- )) {
1950
- results.push(
1951
- await ensureRepoFile(
1952
- rootDir,
1953
- childRoutePath,
1954
- renderChildAreaTemplate(config)
1955
- )
1956
- );
1957
- }
1958
- results.push(
1959
- await ensureRepoFile(
1960
- rootDir,
1961
- `${resolveEngineeringTruthRoot(config)}/README.md`,
1962
- renderTruthRootReadmeTemplate(config, "engineering")
1963
- )
1964
- );
1965
- results.push(
1966
- await ensureRepoFile(
1967
- rootDir,
1968
- `${config.truthmark.paths.productTruthRoot}/README.md`,
1969
- renderTruthRootReadmeTemplate(config, "product")
1970
- )
1971
- );
1972
- results.push(
1973
- await ensureRepoFile(
1974
- rootDir,
1975
- `${truthDomainRoot}/README.md`,
1976
- renderTruthDomainReadmeTemplate(config)
1977
- )
1978
- );
1979
- results.push(
1980
- await ensureOrUpdateTruthDocTemplate(
1981
- rootDir,
1982
- truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
1983
- renderBehaviorDocTemplateFile()
1984
- )
1985
- );
1986
- results.push(
1987
- await ensureOrUpdateTruthDocTemplate(
1988
- rootDir,
1989
- truthTemplatePath(config, CONTRACT_DOC_TEMPLATE_FILE_NAME),
1990
- renderContractDocTemplateFile()
1991
- )
1992
- );
1993
- results.push(
1994
- await ensureOrUpdateTruthDocTemplate(
1995
- rootDir,
1996
- truthTemplatePath(config, ARCHITECTURE_DOC_TEMPLATE_FILE_NAME),
1997
- renderArchitectureDocTemplateFile()
1998
- )
1999
- );
2000
- results.push(
2001
- await ensureOrUpdateTruthDocTemplate(
2002
- rootDir,
2003
- truthTemplatePath(config, WORKFLOW_DOC_TEMPLATE_FILE_NAME),
2004
- renderWorkflowDocTemplateFile()
2005
- )
2006
- );
2007
- results.push(
2008
- await ensureOrUpdateTruthDocTemplate(
2009
- rootDir,
2010
- truthTemplatePath(config, OPERATIONS_DOC_TEMPLATE_FILE_NAME),
2011
- renderOperationsDocTemplateFile()
2012
- )
2013
- );
2014
- results.push(
2015
- await ensureOrUpdateTruthDocTemplate(
2016
- rootDir,
2017
- truthTemplatePath(config, TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
2018
- renderTestBehaviorDocTemplateFile()
2019
- )
2020
- );
2021
- results.push(
2022
- await ensureOrUpdateTruthDocTemplate(
2023
- rootDir,
2024
- truthTemplatePath(config, PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME),
2025
- renderProductCapabilityDocTemplateFile()
2026
- )
2027
- );
2028
- results.push(
2029
- await ensureRepoFile(
2030
- rootDir,
2031
- `${truthDomainRoot}/bootstrap-routing.md`,
2032
- renderBootstrapRoutingDocTemplate(config)
2033
- )
2034
- );
2035
- return results;
2036
- };
2037
-
2038
- // src/checks/generated-surfaces.ts
2039
- import fs6 from "fs/promises";
2040
-
2041
1960
  // src/truth/evidence.ts
2042
1961
  var renderClaimEvidenceCheckedSection = (items) => {
2043
1962
  return [
@@ -2267,70 +2186,290 @@ var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = [])
2267
2186
  const writeMentions = writeAgents.map(
2268
2187
  (agent) => `@${agent.replace(/_/gu, "-")}`
2269
2188
  );
2270
- const writeAgentLines = writeMentions.length > 0 ? [
2271
- `- dispatch write-capable project custom agents only with explicit write leases: ${writeMentions.join(", ")}`,
2272
- "- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
2273
- "- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
2274
- "- parent must inspect the actual checkout diff against each lease before accepting a worker report"
2275
- ] : [];
2276
- const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
2277
- const readOnlyCustomAgentLabel = writeAgents.length > 0 ? "read-only custom agents" : "custom agents";
2278
- return [
2279
- "Copilot custom-agent mode:",
2280
- "- use automatically when this workflow runs in Copilot and the parent agent chooses bounded custom-agent fan-out",
2281
- `- dispatch read-only project custom agents ${readOnlyScope}: ${mentions.join(", ")}`,
2282
- `- ${readOnlyCustomAgentLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
2283
- `- parent supplies bounded evidence shards; ${readOnlyCustomAgentLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
2284
- ...writeAgentLines,
2285
- `- ${parentRule}`
2286
- ].join("\n");
2287
- };
2288
- var defaultAgentConfig = () => {
2289
- return createDefaultConfig();
2290
- };
2291
- var renderHierarchySummary = (config) => {
2292
- const productRoot = resolveProductTruthRoot(config);
2293
- const engineeringRoot = resolveEngineeringTruthRoot(config);
2294
- return [
2295
- "Truthmark hierarchy hints:",
2296
- "- Config, when present: .truthmark/config.yml",
2297
- `- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
2298
- `- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
2299
- `- Product truth docs, when present: ${productRoot}/**/*.md`,
2300
- `- Engineering truth docs, when present: ${engineeringRoot}/**/*.md`
2301
- ].join("\n");
2302
- };
2303
-
2304
- // src/templates/agents-block.ts
2305
- var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
2306
- var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
2307
- var renderCompactHierarchySummary = (config) => {
2308
- const productTruthRoot = resolveProductTruthRoot(config);
2309
- const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2310
- const truthDocRoots = Array.from(
2311
- /* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])
2312
- ).map((truthRoot3) => `${truthRoot3}/**/*.md`);
2313
- return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.truthmark.paths.routesIndex} and ${config.truthmark.paths.routeAreasRoot}/**/*.md when present; Truth docs: ${truthDocRoots.join(" and ")} when present.`;
2314
- };
2315
- var renderAgentsBlock = (config = defaultAgentConfig()) => {
2316
- const portalLine = config.truthmark.generated.portal.enabled ? `Truthmark Portal is a separate manual-only presentation workflow. Run it only when explicitly requested; it writes generated non-canonical static files under ${config.truthmark.paths.portalOutput}/. Markdown remains canonical.` : null;
2317
- return [
2318
- TRUTHMARK_BLOCK_START,
2319
- "## Truthmark Workflow",
2320
- "",
2321
- "Truthmark-managed block. Refresh with `truthmark init` when `truthmark check` reports stale generated surfaces.",
2322
- renderCompactHierarchySummary(config),
2323
- "Decisions live in the canonical doc they govern; date active decisions inline.",
2324
- "Agent runtime: host-native skill packages/adapters plus this block; inspect checkout directly. Delegation is host-owned.",
2325
- "### Truth Sync",
2326
- "After functional code changes, run relevant tests, then use the truthmark-sync skill before finishing; later functional changes need a fresh Sync review. Memory: code changed -> tests -> Sync -> report.",
2327
- "Support new or changed behavior-bearing truth claims with checkout evidence. Code leads; truth docs follow. Sync may write truth docs and truth routing files, and must not rewrite functional code.",
2328
- "If routing cannot map changed code to a bounded truth owner, run Truth Structure before syncing when safe; otherwise stop and recommend Truth Structure. Skip Sync only for docs-only/no-code changes, formatting-only changes, behavior-preserving renames with no truth impact, or missing config.",
2329
- "Explicit workflows: Truth Structure, Truth Document, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
2330
- ...portalLine === null ? [] : [portalLine],
2331
- "Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
2332
- TRUTHMARK_BLOCK_END
2333
- ].join("\n");
2189
+ const writeAgentLines = writeMentions.length > 0 ? [
2190
+ `- dispatch write-capable project custom agents only with explicit write leases: ${writeMentions.join(", ")}`,
2191
+ "- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
2192
+ "- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
2193
+ "- parent must inspect the actual checkout diff against each lease before accepting a worker report"
2194
+ ] : [];
2195
+ const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
2196
+ const readOnlyCustomAgentLabel = writeAgents.length > 0 ? "read-only custom agents" : "custom agents";
2197
+ return [
2198
+ "Copilot custom-agent mode:",
2199
+ "- use automatically when this workflow runs in Copilot and the parent agent chooses bounded custom-agent fan-out",
2200
+ `- dispatch read-only project custom agents ${readOnlyScope}: ${mentions.join(", ")}`,
2201
+ `- ${readOnlyCustomAgentLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
2202
+ `- parent supplies bounded evidence shards; ${readOnlyCustomAgentLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
2203
+ ...writeAgentLines,
2204
+ `- ${parentRule}`
2205
+ ].join("\n");
2206
+ };
2207
+ var defaultAgentConfig = () => {
2208
+ return createDefaultConfig();
2209
+ };
2210
+ var renderHierarchySummary = (config) => {
2211
+ const productRoot = resolveProductTruthRoot(config);
2212
+ const engineeringRoot = resolveEngineeringTruthRoot(config);
2213
+ return [
2214
+ "Truthmark hierarchy hints:",
2215
+ "- Config, when present: .truthmark/config.yml",
2216
+ `- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
2217
+ `- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
2218
+ `- Product truth docs, when present: ${productRoot}/**/*.md`,
2219
+ `- Engineering truth docs, when present: ${engineeringRoot}/**/*.md`
2220
+ ].join("\n");
2221
+ };
2222
+
2223
+ // src/templates/agents-block.ts
2224
+ var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
2225
+ var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
2226
+ var renderCompactHierarchySummary = (config) => {
2227
+ const productTruthRoot = resolveProductTruthRoot(config);
2228
+ const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
2229
+ const truthDocRoots = Array.from(
2230
+ /* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])
2231
+ ).map((truthRoot3) => `${truthRoot3}/**/*.md`);
2232
+ return `Hierarchy hints: config .truthmark/config.yml when present; routes ${config.truthmark.paths.routesIndex} and ${config.truthmark.paths.routeAreasRoot}/**/*.md when present; Truth docs: ${truthDocRoots.join(" and ")} when present.`;
2233
+ };
2234
+ var renderAgentsBlock = (config = defaultAgentConfig()) => {
2235
+ const portalLine = config.truthmark.generated.portal.enabled ? `Truthmark Portal is a separate manual-only presentation workflow. Run it only when explicitly requested; it writes generated non-canonical static files under ${config.truthmark.paths.portalOutput}/. Markdown remains canonical.` : null;
2236
+ return [
2237
+ TRUTHMARK_BLOCK_START,
2238
+ "## Truthmark Workflow",
2239
+ "",
2240
+ "Truthmark-managed block. Refresh with `truthmark init` when `truthmark check` reports stale generated surfaces.",
2241
+ renderCompactHierarchySummary(config),
2242
+ "Decisions live in the canonical doc they govern; date active decisions inline.",
2243
+ "Agent runtime: host-native skill packages/adapters plus this block; inspect checkout directly. Delegation is host-owned.",
2244
+ "### Truth Sync",
2245
+ "After functional code changes, run relevant tests, then use the truthmark-sync skill before finishing; later functional changes need a fresh Sync review. Memory: code changed -> tests -> Sync -> report.",
2246
+ "Support new or changed behavior-bearing truth claims with checkout evidence. Code leads; truth docs follow. Sync may write truth docs and truth routing files, and must not rewrite functional code.",
2247
+ "If routing cannot map changed code to a bounded truth owner, run Truth Structure before syncing when safe; otherwise stop and recommend Truth Structure. Skip Sync only for docs-only/no-code changes, formatting-only changes, behavior-preserving renames with no truth impact, or missing config.",
2248
+ "Explicit workflows: Truth Structure, Truth Document, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
2249
+ ...portalLine === null ? [] : [portalLine],
2250
+ "Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
2251
+ TRUTHMARK_BLOCK_END
2252
+ ].join("\n");
2253
+ };
2254
+
2255
+ // src/managed-block.ts
2256
+ var findMarkerIndexes = (content, marker) => {
2257
+ const indexes = [];
2258
+ let cursor = 0;
2259
+ while (true) {
2260
+ const index = content.indexOf(marker, cursor);
2261
+ if (index === -1) {
2262
+ return indexes;
2263
+ }
2264
+ indexes.push(index);
2265
+ cursor = index + marker.length;
2266
+ }
2267
+ };
2268
+ var parseManagedBlock = (content) => {
2269
+ const starts = findMarkerIndexes(content, TRUTHMARK_BLOCK_START);
2270
+ const ends = findMarkerIndexes(content, TRUTHMARK_BLOCK_END);
2271
+ if (starts.length === 0 && ends.length === 0) {
2272
+ return { status: "absent" };
2273
+ }
2274
+ if (starts.length !== 1 || ends.length !== 1) {
2275
+ return { status: "malformed" };
2276
+ }
2277
+ const start = starts[0];
2278
+ const endStart = ends[0];
2279
+ if (start === -1 || endStart === -1 || endStart < start) {
2280
+ return { status: "malformed" };
2281
+ }
2282
+ return {
2283
+ status: "valid",
2284
+ start,
2285
+ end: endStart + TRUTHMARK_BLOCK_END.length
2286
+ };
2287
+ };
2288
+ var extractManagedBlock = (content) => {
2289
+ const block = parseManagedBlock(content);
2290
+ if (block.status !== "valid") {
2291
+ return null;
2292
+ }
2293
+ return content.slice(block.start, block.end);
2294
+ };
2295
+ var trimmedBoundary = (value) => value.replace(/\n+$/u, "");
2296
+ var upsertManagedBlock = (existingContent, block) => {
2297
+ if (existingContent === null || existingContent.trim().length === 0) {
2298
+ return block;
2299
+ }
2300
+ const marker = parseManagedBlock(existingContent);
2301
+ if (marker.status !== "valid") {
2302
+ return `${trimmedBoundary(existingContent)}
2303
+
2304
+ ${block}`;
2305
+ }
2306
+ const before = trimmedBoundary(existingContent.slice(0, marker.start));
2307
+ const after = existingContent.slice(marker.end).replace(/^\n+/u, "");
2308
+ if (before.length === 0 && after.length === 0) {
2309
+ return block;
2310
+ }
2311
+ if (before.length === 0) {
2312
+ return `${block}
2313
+
2314
+ ${after}`;
2315
+ }
2316
+ if (after.length === 0) {
2317
+ return `${before}
2318
+
2319
+ ${block}`;
2320
+ }
2321
+ return `${before}
2322
+
2323
+ ${block}
2324
+
2325
+ ${after}`;
2326
+ };
2327
+
2328
+ // src/init/hierarchy.ts
2329
+ import fs5 from "fs/promises";
2330
+ var truthRoot2 = resolveTruthDocsRoot;
2331
+ var BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-behavior.md";
2332
+ var CONTRACT_DOC_TEMPLATE_FILE_NAME = "engineering-contract.md";
2333
+ var ARCHITECTURE_DOC_TEMPLATE_FILE_NAME = "engineering-architecture.md";
2334
+ var WORKFLOW_DOC_TEMPLATE_FILE_NAME = "engineering-workflow.md";
2335
+ var OPERATIONS_DOC_TEMPLATE_FILE_NAME = "engineering-operations.md";
2336
+ var TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-test-behavior.md";
2337
+ var PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME = "product-capability.md";
2338
+ var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
2339
+ const rootIndexSource = await fs5.readFile(
2340
+ resolveRepoPath(rootDir, rootIndexPath),
2341
+ "utf8"
2342
+ );
2343
+ const parsedRootIndex = parseAreasMarkdown(rootIndexSource);
2344
+ return parsedRootIndex.areaFileReferences.some(
2345
+ (areaReference) => areaReference.areaFiles.includes(childRoutePath)
2346
+ );
2347
+ };
2348
+ var truthTemplatePath = (config, fileName) => {
2349
+ return `${config.truthmark.paths.templatesRoot}/${fileName}`;
2350
+ };
2351
+ var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
2352
+ const seededResult = await ensureRepoFile(
2353
+ rootDir,
2354
+ templatePath,
2355
+ defaultTemplate
2356
+ );
2357
+ if (seededResult.status !== "unchanged") {
2358
+ return seededResult;
2359
+ }
2360
+ const existingTemplate = await fs5.readFile(
2361
+ resolveRepoPath(rootDir, templatePath),
2362
+ "utf8"
2363
+ );
2364
+ const mergedTemplate = mergeTruthDocTemplate(
2365
+ existingTemplate,
2366
+ defaultTemplate
2367
+ );
2368
+ return writeRepoFile(rootDir, templatePath, mergedTemplate);
2369
+ };
2370
+ var scaffoldHierarchy = async (rootDir, config) => {
2371
+ const results = [];
2372
+ const truthDocsRoot = truthRoot2(config);
2373
+ const truthDomainRoot = `${truthDocsRoot}/${config.truthmark.routes.defaultArea}`;
2374
+ const childRoutePath = `${config.truthmark.paths.routeAreasRoot}/${config.truthmark.routes.defaultArea}.md`;
2375
+ results.push(
2376
+ await ensureRepoFile(
2377
+ rootDir,
2378
+ config.truthmark.paths.routesIndex,
2379
+ renderHierarchicalAreasIndexTemplate(config)
2380
+ )
2381
+ );
2382
+ if (await rootIndexReferencesChildRoute(
2383
+ rootDir,
2384
+ config.truthmark.paths.routesIndex,
2385
+ childRoutePath
2386
+ )) {
2387
+ results.push(
2388
+ await ensureRepoFile(
2389
+ rootDir,
2390
+ childRoutePath,
2391
+ renderChildAreaTemplate(config)
2392
+ )
2393
+ );
2394
+ }
2395
+ results.push(
2396
+ await ensureRepoFile(
2397
+ rootDir,
2398
+ `${resolveEngineeringTruthRoot(config)}/README.md`,
2399
+ renderTruthRootReadmeTemplate(config, "engineering")
2400
+ )
2401
+ );
2402
+ results.push(
2403
+ await ensureRepoFile(
2404
+ rootDir,
2405
+ `${config.truthmark.paths.productTruthRoot}/README.md`,
2406
+ renderTruthRootReadmeTemplate(config, "product")
2407
+ )
2408
+ );
2409
+ results.push(
2410
+ await ensureRepoFile(
2411
+ rootDir,
2412
+ `${truthDomainRoot}/README.md`,
2413
+ renderTruthDomainReadmeTemplate(config)
2414
+ )
2415
+ );
2416
+ results.push(
2417
+ await ensureOrUpdateTruthDocTemplate(
2418
+ rootDir,
2419
+ truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
2420
+ renderBehaviorDocTemplateFile()
2421
+ )
2422
+ );
2423
+ results.push(
2424
+ await ensureOrUpdateTruthDocTemplate(
2425
+ rootDir,
2426
+ truthTemplatePath(config, CONTRACT_DOC_TEMPLATE_FILE_NAME),
2427
+ renderContractDocTemplateFile()
2428
+ )
2429
+ );
2430
+ results.push(
2431
+ await ensureOrUpdateTruthDocTemplate(
2432
+ rootDir,
2433
+ truthTemplatePath(config, ARCHITECTURE_DOC_TEMPLATE_FILE_NAME),
2434
+ renderArchitectureDocTemplateFile()
2435
+ )
2436
+ );
2437
+ results.push(
2438
+ await ensureOrUpdateTruthDocTemplate(
2439
+ rootDir,
2440
+ truthTemplatePath(config, WORKFLOW_DOC_TEMPLATE_FILE_NAME),
2441
+ renderWorkflowDocTemplateFile()
2442
+ )
2443
+ );
2444
+ results.push(
2445
+ await ensureOrUpdateTruthDocTemplate(
2446
+ rootDir,
2447
+ truthTemplatePath(config, OPERATIONS_DOC_TEMPLATE_FILE_NAME),
2448
+ renderOperationsDocTemplateFile()
2449
+ )
2450
+ );
2451
+ results.push(
2452
+ await ensureOrUpdateTruthDocTemplate(
2453
+ rootDir,
2454
+ truthTemplatePath(config, TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
2455
+ renderTestBehaviorDocTemplateFile()
2456
+ )
2457
+ );
2458
+ results.push(
2459
+ await ensureOrUpdateTruthDocTemplate(
2460
+ rootDir,
2461
+ truthTemplatePath(config, PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME),
2462
+ renderProductCapabilityDocTemplateFile()
2463
+ )
2464
+ );
2465
+ results.push(
2466
+ await ensureRepoFile(
2467
+ rootDir,
2468
+ `${truthDomainRoot}/bootstrap-routing.md`,
2469
+ renderBootstrapRoutingDocTemplate(config)
2470
+ )
2471
+ );
2472
+ return results;
2334
2473
  };
2335
2474
 
2336
2475
  // src/agents/workflow-manifest.ts
@@ -3720,8 +3859,8 @@ var renderTruthmarkSkillPackage = ({
3720
3859
  }
3721
3860
  return files;
3722
3861
  };
3723
- var normalizeOpenCodePermissionPath = (path12) => {
3724
- const normalized = path12.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3862
+ var normalizeOpenCodePermissionPath = (path13) => {
3863
+ const normalized = path13.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
3725
3864
  return normalized === "" ? "." : normalized;
3726
3865
  };
3727
3866
  var appendOpenCodePermissionGlob = (root, glob) => {
@@ -4348,8 +4487,31 @@ var renderTruthmarkCopilotPortalPrompt = (config = defaultAgentConfig()) => {
4348
4487
  };
4349
4488
 
4350
4489
  // src/templates/generated-surfaces.ts
4351
- var codexFiles = (config) => {
4490
+ var RETIRED_GENERATED_SURFACES = {
4491
+ exactPaths: [
4492
+ "GEMINI.md",
4493
+ ".github/prompts/truthmark-preview.prompt.md",
4494
+ ".cursor/rules/truthmark-structure.mdc",
4495
+ ".cursor/rules/truthmark-document.mdc",
4496
+ ".cursor/rules/truthmark-sync.mdc",
4497
+ ".cursor/rules/truthmark-realize.mdc",
4498
+ ".cursor/rules/truthmark-check.mdc",
4499
+ ".cursor/rules/truthmark-portal.mdc"
4500
+ ],
4501
+ recursiveRoots: [".gemini"],
4502
+ skillRoots: [
4503
+ ".agents/skills",
4504
+ ".opencode/skills",
4505
+ ".claude/skills",
4506
+ ".github/skills",
4507
+ ".cursor/skills"
4508
+ ],
4509
+ retiredPackages: ["truthmark-preview"],
4510
+ retiredPackageFiles: ["helper-manifest.yml", "support/helper-policy.md"]
4511
+ };
4512
+ var codexFiles = (config, block) => {
4352
4513
  const files = [
4514
+ ...instructionBlockFiles(["AGENTS.md"], block),
4353
4515
  ...renderTruthmarkSkillPackage({
4354
4516
  skillPath: TRUTHMARK_STRUCTURE_SKILL_PATH,
4355
4517
  workflowId: "truthmark-structure",
@@ -4433,8 +4595,9 @@ var codexFiles = (config) => {
4433
4595
  }
4434
4596
  return files;
4435
4597
  };
4436
- var opencodeFiles = (config) => {
4598
+ var opencodeFiles = (config, block) => {
4437
4599
  const files = [
4600
+ ...instructionBlockFiles(["AGENTS.md"], block),
4438
4601
  ...renderTruthmarkSkillPackage({
4439
4602
  skillPath: ".opencode/skills/truthmark-structure/SKILL.md",
4440
4603
  workflowId: "truthmark-structure",
@@ -4719,8 +4882,8 @@ var cursorFiles = (config) => {
4719
4882
  return files;
4720
4883
  };
4721
4884
  var instructionBlockFiles = (paths, block) => {
4722
- return paths.map((path12) => ({
4723
- path: path12,
4885
+ return paths.map((path13) => ({
4886
+ path: path13,
4724
4887
  content: block,
4725
4888
  managedBlock: true
4726
4889
  }));
@@ -4728,9 +4891,9 @@ var instructionBlockFiles = (paths, block) => {
4728
4891
  var filesForPlatform = (platform, config, block) => {
4729
4892
  switch (platform) {
4730
4893
  case "codex":
4731
- return codexFiles(config);
4894
+ return codexFiles(config, block);
4732
4895
  case "opencode":
4733
- return opencodeFiles(config);
4896
+ return opencodeFiles(config, block);
4734
4897
  case "claude-code":
4735
4898
  return claudeFiles(config, block);
4736
4899
  case "github-copilot":
@@ -4742,322 +4905,347 @@ var filesForPlatform = (platform, config, block) => {
4742
4905
  }
4743
4906
  };
4744
4907
  var renderGeneratedSurfaces = (config, block = renderAgentsBlock(config)) => {
4745
- const files = [
4746
- ...instructionBlockFiles(config.instructionTargets, block),
4747
- ...config.platforms.flatMap(
4748
- (platform) => filesForPlatform(platform, config, block)
4749
- )
4750
- ];
4908
+ const files = config.platforms.flatMap(
4909
+ (platform) => filesForPlatform(platform, config, block)
4910
+ );
4751
4911
  return Array.from(
4752
4912
  new Map(files.map((file) => [file.path, file])).values()
4753
4913
  ).sort((left, right) => left.path.localeCompare(right.path));
4754
4914
  };
4755
-
4756
- // src/checks/generated-surfaces.ts
4757
- var readOptionalFile = async (rootDir, filePath) => {
4758
- try {
4759
- return await fs6.readFile(resolveRepoPath(rootDir, filePath), "utf8");
4760
- } catch (error) {
4761
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
4762
- return null;
4915
+ var renderGeneratedSurfaceCatalog = (config) => {
4916
+ const catalog = /* @__PURE__ */ new Map();
4917
+ for (const platform of SUPPORTED_PLATFORMS) {
4918
+ const baseConfig = {
4919
+ ...config,
4920
+ platforms: [platform],
4921
+ truthmark: {
4922
+ ...config.truthmark,
4923
+ generated: { portal: { enabled: false } }
4924
+ }
4925
+ };
4926
+ const basePaths = new Set(
4927
+ renderGeneratedSurfaces(baseConfig).map(({ path: path13 }) => path13)
4928
+ );
4929
+ for (const surface of renderGeneratedSurfaces({
4930
+ ...baseConfig,
4931
+ truthmark: {
4932
+ ...baseConfig.truthmark,
4933
+ generated: { portal: { enabled: true } }
4934
+ }
4935
+ })) {
4936
+ const owner = {
4937
+ kind: basePaths.has(surface.path) ? "platform" : "portal",
4938
+ platform
4939
+ };
4940
+ const existing = catalog.get(surface.path);
4941
+ const recognizedContent = surface.content.endsWith("\n") ? surface.content : `${surface.content}
4942
+ `;
4943
+ if (existing) {
4944
+ existing.owners.push(owner);
4945
+ if (!existing.recognizedContents.includes(recognizedContent)) {
4946
+ existing.recognizedContents.push(recognizedContent);
4947
+ }
4948
+ } else {
4949
+ catalog.set(surface.path, {
4950
+ ...surface,
4951
+ owners: [owner],
4952
+ recognizedContents: [recognizedContent]
4953
+ });
4954
+ }
4763
4955
  }
4764
- throw error;
4765
- }
4766
- };
4767
- var extractManagedBlock = (content) => {
4768
- const startIndex = content.indexOf(TRUTHMARK_BLOCK_START);
4769
- const endIndex = content.indexOf(TRUTHMARK_BLOCK_END);
4770
- if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
4771
- return null;
4772
- }
4773
- return content.slice(startIndex, endIndex + TRUTHMARK_BLOCK_END.length);
4774
- };
4775
- var normalizeGeneratedSurfaceContent = (content) => {
4776
- if (content === null) {
4777
- return null;
4778
4956
  }
4779
- return content.replace(/\r\n/g, "\n").replace(/\n$/u, "");
4780
- };
4781
- var GENERATED_HOST_SKILL_ROOTS = [
4782
- ".agents/skills",
4783
- ".opencode/skills",
4784
- ".claude/skills",
4785
- ".github/skills",
4786
- ".cursor/skills"
4787
- ];
4788
- var RETIRED_SKILL_HELPER_PATHS = [
4789
- "helper-manifest.yml",
4790
- "support/helper-policy.md"
4791
- ];
4792
- var RETIRED_PACKAGE_DIRECTORIES = ["truthmark-preview"];
4793
- var RETIRED_GENERATED_SURFACE_PATHS = [
4794
- "GEMINI.md",
4795
- ".github/prompts/truthmark-preview.prompt.md",
4796
- ".cursor/rules/truthmark-structure.mdc",
4797
- ".cursor/rules/truthmark-document.mdc",
4798
- ".cursor/rules/truthmark-sync.mdc",
4799
- ".cursor/rules/truthmark-realize.mdc",
4800
- ".cursor/rules/truthmark-check.mdc",
4801
- ".cursor/rules/truthmark-portal.mdc"
4802
- ];
4803
- var RETIRED_GENERATED_SURFACE_ROOTS = [".gemini"];
4804
- var isRetiredGeminiSurfacePath = (filePath) => filePath === "GEMINI.md" || filePath.startsWith(".gemini/");
4805
- var obsoleteGeneratedSurfaceMessage = (surfacePath) => {
4806
- if (isRetiredGeminiSurfacePath(surfacePath)) {
4807
- return `Generated surface ${surfacePath} is obsolete; remove stale Gemini instructions manually if they are no longer wanted.`;
4957
+ for (const retiredPath of RETIRED_GENERATED_SURFACES.exactPaths) {
4958
+ if (!catalog.has(retiredPath))
4959
+ catalog.set(retiredPath, {
4960
+ path: retiredPath,
4961
+ content: "",
4962
+ owners: [
4963
+ {
4964
+ kind: "retired",
4965
+ manualCleanupOnly: retiredPath === "GEMINI.md" || retiredPath.startsWith(".gemini/")
4966
+ }
4967
+ ],
4968
+ recognizedContents: []
4969
+ });
4808
4970
  }
4809
- return `Generated surface ${surfacePath} is obsolete; rerun truthmark init.`;
4971
+ return [...catalog.values()].sort(
4972
+ (left, right) => left.path.localeCompare(right.path)
4973
+ );
4810
4974
  };
4811
- var pathExists = async (absolutePath) => {
4975
+
4976
+ // src/init/lifecycle.ts
4977
+ import fs6 from "fs/promises";
4978
+ var plannedContents = /* @__PURE__ */ new WeakMap();
4979
+ var readFile = async (rootDir, filePath) => {
4812
4980
  try {
4813
- await fs6.access(absolutePath);
4814
- return true;
4981
+ return await fs6.readFile(resolveRepoPath(rootDir, filePath), "utf8");
4815
4982
  } catch (error) {
4816
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
4817
- return false;
4818
- }
4983
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
4984
+ return null;
4819
4985
  throw error;
4820
4986
  }
4821
4987
  };
4822
- var listDirectoryFiles = async (rootDir, packageRoot) => {
4823
- const stack = [packageRoot];
4988
+ var listFiles = async (rootDir, directory) => {
4824
4989
  const files = [];
4990
+ const stack = [directory];
4825
4991
  while (stack.length > 0) {
4826
4992
  const current = stack.pop();
4827
- if (current === void 0) {
4828
- continue;
4829
- }
4830
- const absoluteCurrent = resolveRepoPath(rootDir, current);
4831
- const entries = await fs6.readdir(absoluteCurrent, {
4832
- withFileTypes: true
4833
- });
4834
- for (const entry of entries) {
4835
- const next = `${current}/${entry.name}`;
4836
- if (entry.isDirectory()) {
4837
- stack.push(next);
4838
- } else {
4839
- files.push(next);
4993
+ try {
4994
+ for (const entry of await fs6.readdir(resolveRepoPath(rootDir, current), {
4995
+ withFileTypes: true
4996
+ })) {
4997
+ const next = `${current}/${entry.name}`;
4998
+ if (entry.isDirectory()) stack.push(next);
4999
+ else if (entry.isFile() || entry.isSymbolicLink()) files.push(next);
4840
5000
  }
5001
+ } catch (error) {
5002
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
5003
+ throw error;
4841
5004
  }
4842
5005
  }
4843
5006
  return files;
4844
5007
  };
4845
- var collectRetiredGeneratedSurfaces = async (rootDir, expectedSurfacePaths, options = {}) => {
4846
- const legacyCandidates = /* @__PURE__ */ new Set();
4847
- const includeGeminiSurfaces = options.includeGeminiSurfaces ?? true;
4848
- for (const retiredPath of RETIRED_GENERATED_SURFACE_PATHS) {
4849
- if (!includeGeminiSurfaces && isRetiredGeminiSurfacePath(retiredPath)) {
4850
- continue;
4851
- }
4852
- const absoluteRetiredPath = resolveRepoPath(rootDir, retiredPath);
4853
- if (await pathExists(absoluteRetiredPath) && !expectedSurfacePaths.has(retiredPath)) {
4854
- legacyCandidates.add(retiredPath);
4855
- }
4856
- }
4857
- for (const retiredRoot of RETIRED_GENERATED_SURFACE_ROOTS) {
4858
- if (!includeGeminiSurfaces && isRetiredGeminiSurfacePath(`${retiredRoot}/`)) {
4859
- continue;
4860
- }
4861
- const absoluteRetiredRoot = resolveRepoPath(rootDir, retiredRoot);
4862
- if (!await pathExists(absoluteRetiredRoot)) {
4863
- continue;
4864
- }
4865
- const retiredFiles = await listDirectoryFiles(rootDir, retiredRoot);
4866
- for (const filePath of retiredFiles) {
4867
- if (!expectedSurfacePaths.has(filePath)) {
4868
- legacyCandidates.add(filePath);
4869
- }
5008
+ var findRetiredSurfaces = async (rootDir) => {
5009
+ const paths = /* @__PURE__ */ new Set();
5010
+ for (const filePath of RETIRED_GENERATED_SURFACES.exactPaths) {
5011
+ try {
5012
+ await fs6.lstat(resolveRepoPath(rootDir, filePath));
5013
+ paths.add(filePath);
5014
+ } catch (error) {
5015
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
5016
+ throw error;
4870
5017
  }
4871
5018
  }
4872
- for (const skillRoot of GENERATED_HOST_SKILL_ROOTS) {
4873
- const absoluteSkillRoot = resolveRepoPath(rootDir, skillRoot);
4874
- let skillPackages = [];
5019
+ for (const root of RETIRED_GENERATED_SURFACES.recursiveRoots)
5020
+ for (const filePath of await listFiles(rootDir, root)) paths.add(filePath);
5021
+ for (const skillRoot of RETIRED_GENERATED_SURFACES.skillRoots) {
5022
+ for (const packageName of RETIRED_GENERATED_SURFACES.retiredPackages)
5023
+ for (const filePath of await listFiles(
5024
+ rootDir,
5025
+ `${skillRoot}/${packageName}`
5026
+ ))
5027
+ paths.add(filePath);
5028
+ let packages = [];
4875
5029
  try {
4876
- skillPackages = await fs6.readdir(absoluteSkillRoot, {
5030
+ packages = (await fs6.readdir(resolveRepoPath(rootDir, skillRoot), {
4877
5031
  withFileTypes: true
4878
- });
5032
+ })).filter(
5033
+ (entry) => entry.isDirectory() && entry.name.startsWith("truthmark-")
5034
+ ).map((entry) => entry.name);
4879
5035
  } catch (error) {
4880
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
4881
- continue;
4882
- }
4883
- throw error;
4884
- }
4885
- const packageNames = skillPackages.filter((entry) => entry.isDirectory() && entry.name.startsWith("truthmark-")).map((entry) => entry.name);
4886
- for (const packageName of RETIRED_PACKAGE_DIRECTORIES) {
4887
- if (!packageNames.includes(packageName)) {
4888
- continue;
4889
- }
4890
- const packageRoot = `${skillRoot}/${packageName}`;
4891
- const packageFiles = await listDirectoryFiles(rootDir, packageRoot);
4892
- for (const filePath of packageFiles) {
4893
- if (!expectedSurfacePaths.has(filePath)) {
4894
- legacyCandidates.add(filePath);
4895
- }
4896
- }
5036
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
5037
+ throw error;
4897
5038
  }
4898
- for (const packageName of packageNames) {
4899
- for (const retiredName of RETIRED_SKILL_HELPER_PATHS) {
4900
- const retiredPath = `${packageName}/${retiredName}`;
4901
- const relativeRetiredPath = `${skillRoot}/${retiredPath}`;
4902
- const absoluteRetiredPath = resolveRepoPath(rootDir, relativeRetiredPath);
4903
- if (await pathExists(absoluteRetiredPath) && !expectedSurfacePaths.has(relativeRetiredPath)) {
4904
- legacyCandidates.add(relativeRetiredPath);
5039
+ for (const packageName of packages)
5040
+ for (const retiredFile of RETIRED_GENERATED_SURFACES.retiredPackageFiles) {
5041
+ const filePath = `${skillRoot}/${packageName}/${retiredFile}`;
5042
+ try {
5043
+ await fs6.lstat(resolveRepoPath(rootDir, filePath));
5044
+ paths.add(filePath);
5045
+ } catch (error) {
5046
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
5047
+ throw error;
4905
5048
  }
4906
5049
  }
4907
- }
4908
5050
  }
4909
- return Array.from(legacyCandidates);
5051
+ return [...paths];
4910
5052
  };
4911
- var checkGeneratedSurfaces = async (rootDir, config) => {
5053
+ var buildLifecyclePlan = async (rootDir, config, mode, desired = renderGeneratedSurfaces(config)) => {
5054
+ const desiredPaths = new Set(desired.map(({ path: path13 }) => path13));
5055
+ const entries = [];
4912
5056
  const diagnostics = [];
4913
- const renderedSurfaces = renderGeneratedSurfaces(config);
4914
- for (const surface of renderedSurfaces) {
4915
- const content = await readOptionalFile(rootDir, surface.path);
4916
- if (content === null) {
4917
- diagnostics.push({
4918
- category: "generated-surface",
4919
- severity: "review",
4920
- message: `Generated surface ${surface.path} is missing; rerun truthmark init.`,
4921
- file: surface.path
5057
+ let applicable = true;
5058
+ for (const surface of desired.filter(({ managedBlock }) => managedBlock)) {
5059
+ if (!await isSafeExactFile(rootDir, surface.path, true)) {
5060
+ applicable = false;
5061
+ entries.push({
5062
+ path: surface.path,
5063
+ action: "manual-review",
5064
+ reason: "Managed instruction destination is aliased or not a regular single-link file."
5065
+ });
5066
+ }
5067
+ }
5068
+ for (const surface of desired.filter(({ managedBlock }) => managedBlock)) {
5069
+ const content = await readFile(rootDir, surface.path);
5070
+ if (content !== null && parseManagedBlock(content).status === "malformed") {
5071
+ applicable = false;
5072
+ entries.push({
5073
+ path: surface.path,
5074
+ action: "manual-review",
5075
+ reason: "Truthmark managed-block markers are malformed."
5076
+ });
5077
+ }
5078
+ }
5079
+ const catalog = renderGeneratedSurfaceCatalog(config);
5080
+ for (const retiredPath of await findRetiredSurfaces(rootDir)) {
5081
+ if (!catalog.some(({ path: catalogPath }) => catalogPath === retiredPath))
5082
+ catalog.push({
5083
+ path: retiredPath,
5084
+ content: "",
5085
+ owners: [
5086
+ {
5087
+ kind: "retired",
5088
+ manualCleanupOnly: retiredPath === "GEMINI.md" || retiredPath.startsWith(".gemini/")
5089
+ }
5090
+ ],
5091
+ recognizedContents: []
5092
+ });
5093
+ }
5094
+ for (const surface of catalog) {
5095
+ if (desiredPaths.has(surface.path)) continue;
5096
+ const content = await readFile(rootDir, surface.path);
5097
+ if (content === null) continue;
5098
+ if (!await isSafeExactFile(rootDir, surface.path, false)) {
5099
+ applicable = false;
5100
+ entries.push({
5101
+ path: surface.path,
5102
+ action: "manual-review",
5103
+ reason: "Path is not safely contained in the worktree."
4922
5104
  });
4923
5105
  continue;
4924
5106
  }
4925
- const comparableContent = normalizeGeneratedSurfaceContent(
4926
- surface.managedBlock ? extractManagedBlock(content) : content
4927
- );
4928
- const expectedContent = normalizeGeneratedSurfaceContent(surface.content);
4929
- if (comparableContent !== expectedContent) {
5107
+ if (surface.path === "GEMINI.md" || surface.path.startsWith(".gemini/")) {
5108
+ entries.push({
5109
+ path: surface.path,
5110
+ action: "preserve",
5111
+ reason: "Gemini surfaces require manual cleanup."
5112
+ });
5113
+ } else if (surface.managedBlock) {
5114
+ const block = parseManagedBlock(content);
5115
+ if (block.status === "malformed") {
5116
+ applicable = false;
5117
+ entries.push({
5118
+ path: surface.path,
5119
+ action: "manual-review",
5120
+ reason: "Truthmark managed-block markers are malformed."
5121
+ });
5122
+ } else if (block.status === "valid") {
5123
+ entries.push({
5124
+ path: surface.path,
5125
+ action: "remove-managed-block",
5126
+ reason: "No configured platform owns this managed block."
5127
+ });
5128
+ }
5129
+ } else if (surface.recognizedContents.includes(content)) {
5130
+ entries.push({
5131
+ path: surface.path,
5132
+ action: "remove-file",
5133
+ reason: "Generated file has no active platform owner."
5134
+ });
5135
+ } else {
5136
+ entries.push({
5137
+ path: surface.path,
5138
+ action: "preserve",
5139
+ reason: "Generated path has diverged content and requires review."
5140
+ });
4930
5141
  diagnostics.push({
4931
5142
  category: "generated-surface",
4932
5143
  severity: "review",
4933
- message: `Generated surface ${surface.path} is stale; rerun truthmark init.`,
5144
+ message: `Generated surface ${surface.path} has diverged content; preserved for manual review.`,
4934
5145
  file: surface.path
4935
5146
  });
4936
5147
  }
4937
5148
  }
4938
- const staleSurfaces = await collectRetiredGeneratedSurfaces(
4939
- rootDir,
4940
- new Set(renderedSurfaces.map((surface) => surface.path))
4941
- );
4942
- for (const surfacePath of staleSurfaces) {
4943
- diagnostics.push({
4944
- category: "generated-surface",
4945
- severity: "review",
4946
- message: obsoleteGeneratedSurfaceMessage(surfacePath),
4947
- file: surfacePath
4948
- });
4949
- }
4950
- return diagnostics;
4951
- };
4952
- var findAutoRemovableRetiredGeneratedSurfaces = async (rootDir, expectedSurfacePaths) => {
4953
- return collectRetiredGeneratedSurfaces(rootDir, expectedSurfacePaths, {
4954
- includeGeminiSurfaces: false
4955
- });
4956
- };
4957
-
4958
- // src/init/init.ts
4959
- var escapeRegExp = (value) => {
4960
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4961
- };
4962
- var MANAGED_WORKFLOW_HEADING = "## Truthmark Workflow";
4963
- var CANONICAL_MANAGED_LINES = new Set(
4964
- renderAgentsBlock().split("\n").map((line) => line.trim()).filter(
4965
- (line) => line.length > 0 && line !== TRUTHMARK_BLOCK_START && line !== TRUTHMARK_BLOCK_END
4966
- )
4967
- );
4968
- var countCanonicalManagedLineMatches = (lines) => {
4969
- return lines.reduce((matchCount, line) => {
4970
- return CANONICAL_MANAGED_LINES.has(line.trim()) ? matchCount + 1 : matchCount;
4971
- }, 0);
4972
- };
4973
- var isManagedChunk = (lines, minimumMatches) => {
4974
- return countCanonicalManagedLineMatches(lines) >= minimumMatches;
4975
- };
4976
- var removeTrailingManagedChunk = (preservedLines) => {
4977
- let startIndex = -1;
4978
- for (let index = preservedLines.length - 1; index >= 0; index -= 1) {
4979
- if (preservedLines[index].trim() === MANAGED_WORKFLOW_HEADING) {
4980
- startIndex = index;
4981
- break;
4982
- }
4983
- }
4984
- if (startIndex === -1) {
4985
- return;
4986
- }
4987
- const candidateChunk = preservedLines.slice(startIndex);
4988
- const looksManaged = isManagedChunk(candidateChunk, 4);
4989
- if (looksManaged) {
4990
- preservedLines.splice(startIndex);
4991
- }
4992
- };
4993
- var upsertManagedBlock = (existingContent, block) => {
4994
- if (!existingContent || existingContent.trim().length === 0) {
4995
- return block;
4996
- }
4997
- const normalizedExistingContent = existingContent;
4998
- const startMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_START), "g");
4999
- const endMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_END), "g");
5000
- const managedBlockPattern = new RegExp(
5001
- `${escapeRegExp(TRUTHMARK_BLOCK_START)}[\\s\\S]*?${escapeRegExp(TRUTHMARK_BLOCK_END)}`,
5002
- "g"
5149
+ entries.sort(
5150
+ (a, b) => a.path.localeCompare(b.path) || a.action.localeCompare(b.action)
5003
5151
  );
5004
- const completeBlocks = normalizedExistingContent.match(managedBlockPattern) ?? [];
5005
- const startCount = normalizedExistingContent.match(startMarkerPattern)?.length ?? 0;
5006
- const endCount = normalizedExistingContent.match(endMarkerPattern)?.length ?? 0;
5007
- if (startCount === 1 && endCount === 1 && completeBlocks.length === 1) {
5008
- return normalizedExistingContent.replace(managedBlockPattern, block);
5009
- }
5010
- const preservedLines = [];
5011
- let insideManagedBlock = false;
5012
- let managedLines = [];
5013
- for (const line of normalizedExistingContent.split("\n")) {
5014
- const trimmedLine = line.trim();
5015
- if (trimmedLine === TRUTHMARK_BLOCK_START) {
5016
- if (insideManagedBlock && !isManagedChunk(managedLines, 2)) {
5017
- preservedLines.push(...managedLines);
5018
- }
5019
- insideManagedBlock = true;
5020
- managedLines = [];
5021
- continue;
5022
- }
5023
- if (trimmedLine === TRUTHMARK_BLOCK_END) {
5024
- if (insideManagedBlock) {
5025
- insideManagedBlock = false;
5026
- managedLines = [];
5027
- continue;
5028
- }
5029
- if (!insideManagedBlock) {
5030
- removeTrailingManagedChunk(preservedLines);
5031
- }
5032
- continue;
5033
- }
5034
- if (insideManagedBlock) {
5035
- managedLines.push(line);
5036
- continue;
5152
+ if (!applicable)
5153
+ diagnostics.push({
5154
+ category: "generated-surface",
5155
+ severity: "error",
5156
+ message: "Lifecycle changes were not applied because preflight found unsafe or malformed generated surfaces."
5157
+ });
5158
+ const plan = {
5159
+ schemaVersion: "truthmark-lifecycle/v0",
5160
+ mode,
5161
+ entries,
5162
+ diagnostics,
5163
+ applicable,
5164
+ applied: false
5165
+ };
5166
+ const contents = /* @__PURE__ */ new Map();
5167
+ for (const entry of entries) {
5168
+ if (entry.action === "remove-file" || entry.action === "remove-managed-block") {
5169
+ const content = await readFile(rootDir, entry.path);
5170
+ if (content !== null) contents.set(entry.path, content);
5037
5171
  }
5038
- preservedLines.push(line);
5039
5172
  }
5040
- if (insideManagedBlock && !isManagedChunk(managedLines, 2)) {
5041
- preservedLines.push(...managedLines);
5173
+ plannedContents.set(plan, contents);
5174
+ return plan;
5175
+ };
5176
+ var applyLifecyclePlan = async (rootDir, plan) => {
5177
+ if (!plan.applicable || plan.mode !== "apply") return plan;
5178
+ const expected = plannedContents.get(plan);
5179
+ const failure = async (entry) => {
5180
+ if (entry.action !== "remove-file" && entry.action !== "remove-managed-block")
5181
+ return null;
5182
+ if (!await isSafeExactFile(rootDir, entry.path, false))
5183
+ return `Unsafe lifecycle target: ${entry.path}`;
5184
+ const content = await readFile(rootDir, entry.path);
5185
+ if (content === null || expected?.get(entry.path) !== content)
5186
+ return `Lifecycle target changed after planning: ${entry.path}`;
5187
+ if (entry.action === "remove-managed-block" && parseManagedBlock(content).status !== "valid")
5188
+ return `Managed block changed during removal: ${entry.path}`;
5189
+ return null;
5190
+ };
5191
+ for (const entry of plan.entries) {
5192
+ const message = await failure(entry);
5193
+ if (message)
5194
+ return {
5195
+ ...plan,
5196
+ applicable: false,
5197
+ applied: false,
5198
+ diagnostics: [
5199
+ ...plan.diagnostics,
5200
+ {
5201
+ category: "generated-surface",
5202
+ severity: "error",
5203
+ message,
5204
+ file: entry.path
5205
+ }
5206
+ ]
5207
+ };
5042
5208
  }
5043
- const preservedContent = preservedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
5044
- if (preservedContent.length === 0) {
5045
- return block;
5209
+ for (const entry of plan.entries) {
5210
+ const absolutePath = resolveRepoPath(rootDir, entry.path);
5211
+ if (entry.action === "remove-file") {
5212
+ const stat = await fs6.lstat(absolutePath);
5213
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
5214
+ throw new Error(
5215
+ `Refusing unsafe generated file removal: ${entry.path}`
5216
+ );
5217
+ await fs6.rm(absolutePath);
5218
+ } else if (entry.action === "remove-managed-block") {
5219
+ const stat = await fs6.lstat(absolutePath);
5220
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
5221
+ throw new Error(`Refusing unsafe managed-block removal: ${entry.path}`);
5222
+ const content = await fs6.readFile(absolutePath, "utf8");
5223
+ const block = parseManagedBlock(content);
5224
+ if (block.status !== "valid")
5225
+ throw new Error(`Managed block changed during removal: ${entry.path}`);
5226
+ const remaining = `${content.slice(0, block.start)}${content.slice(block.end)}`;
5227
+ if (remaining.trim().length === 0) await fs6.rm(absolutePath);
5228
+ else await fs6.writeFile(absolutePath, remaining, "utf8");
5229
+ }
5046
5230
  }
5047
- return `${preservedContent}
5048
-
5049
- ${block}`;
5231
+ return { ...plan, applied: true };
5050
5232
  };
5051
- var writeManagedAgentsFile = async (rootDir, path12 = "AGENTS.md", block) => {
5233
+
5234
+ // src/init/init.ts
5235
+ var writeManagedAgentsFile = async (rootDir, path13 = "AGENTS.md", block) => {
5052
5236
  let existingContent = null;
5053
5237
  try {
5054
- existingContent = await fs7.readFile(resolveRepoPath(rootDir, path12), "utf8");
5238
+ existingContent = await fs7.readFile(resolveRepoPath(rootDir, path13), "utf8");
5055
5239
  } catch (error) {
5056
5240
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
5057
5241
  throw error;
5058
5242
  }
5059
5243
  }
5060
- return writeRepoFile(rootDir, path12, upsertManagedBlock(existingContent, block));
5244
+ return writeRepoFile(
5245
+ rootDir,
5246
+ path13,
5247
+ upsertManagedBlock(existingContent, block)
5248
+ );
5061
5249
  };
5062
5250
  var diagnosticCategoryForPath = (filePath, config) => {
5063
5251
  if (filePath === "AGENTS.md") {
@@ -5121,35 +5309,104 @@ var runInit = async (cwd) => {
5121
5309
  }
5122
5310
  const results = [];
5123
5311
  const config = loadedConfig.config;
5124
- results.push(...await scaffoldHierarchy(rootDir, config));
5125
5312
  const block = renderAgentsBlock(config);
5126
5313
  const platformFiles = renderGeneratedSurfaces(config, block);
5127
- const expectedSurfacePaths = new Set(platformFiles.map((file) => file.path));
5314
+ const lifecyclePlan = await buildLifecyclePlan(
5315
+ rootDir,
5316
+ config,
5317
+ "apply",
5318
+ platformFiles
5319
+ );
5320
+ if (!lifecyclePlan.applicable) {
5321
+ return {
5322
+ command: "init",
5323
+ summary: "Truthmark init made no changes because generated-surface preflight failed.",
5324
+ diagnostics: [...loadedConfig.diagnostics, ...lifecyclePlan.diagnostics],
5325
+ data: { lifecyclePlan }
5326
+ };
5327
+ }
5328
+ const appliedLifecyclePlan = await applyLifecyclePlan(rootDir, lifecyclePlan);
5329
+ if (!appliedLifecyclePlan.applicable) {
5330
+ return {
5331
+ command: "init",
5332
+ summary: "Truthmark init made no changes because generated-surface preflight failed.",
5333
+ diagnostics: [
5334
+ ...loadedConfig.diagnostics,
5335
+ ...appliedLifecyclePlan.diagnostics
5336
+ ],
5337
+ data: { lifecyclePlan: appliedLifecyclePlan }
5338
+ };
5339
+ }
5340
+ results.push(...await scaffoldHierarchy(rootDir, config));
5128
5341
  for (const file of platformFiles) {
5129
5342
  results.push(await writePlatformFile(rootDir, file));
5130
5343
  }
5131
- const obsoleteSurfacePaths = await findAutoRemovableRetiredGeneratedSurfaces(
5132
- rootDir,
5133
- expectedSurfacePaths
5344
+ const changedResults = results.filter(
5345
+ (result) => result.status !== "unchanged"
5346
+ );
5347
+ const lifecycleChanged = appliedLifecyclePlan.entries.some(
5348
+ ({ action }) => action === "remove-file" || action === "remove-managed-block"
5134
5349
  );
5135
- for (const obsoletePath of obsoleteSurfacePaths) {
5136
- await fs7.rm(resolveRepoPath(rootDir, obsoletePath), { force: true });
5137
- }
5138
- const changedResults = results.filter((result) => result.status !== "unchanged");
5139
5350
  return {
5140
5351
  command: "init",
5141
- summary: changedResults.length > 0 ? "Initialized or updated the Truthmark repository scaffold." : "Truthmark repository scaffold is already up to date.",
5142
- diagnostics: writeDiagnostics(results, config),
5352
+ summary: changedResults.length > 0 || lifecycleChanged ? "Initialized or updated the Truthmark repository scaffold." : "Truthmark repository scaffold is already up to date.",
5353
+ diagnostics: [
5354
+ ...loadedConfig.diagnostics,
5355
+ ...appliedLifecyclePlan.diagnostics,
5356
+ ...appliedLifecyclePlan.entries.map((entry) => ({
5357
+ category: "generated-surface",
5358
+ severity: entry.action === "remove-file" || entry.action === "remove-managed-block" ? "action" : "review",
5359
+ message: `${entry.action}: ${entry.reason}`,
5360
+ file: entry.path
5361
+ })),
5362
+ ...writeDiagnostics(results, config)
5363
+ ],
5143
5364
  data: {
5144
5365
  repositoryRoot: repository.repositoryRoot,
5145
5366
  worktreePath: repository.worktreePath,
5146
5367
  branchName: repository.branchName,
5147
5368
  isDetached: repository.isDetached,
5148
- isUnborn: repository.isUnborn
5369
+ isUnborn: repository.isUnborn,
5370
+ lifecyclePlan: appliedLifecyclePlan
5149
5371
  }
5150
5372
  };
5151
5373
  };
5152
5374
 
5375
+ // src/init/uninstall.ts
5376
+ var runUninstall = async (cwd, mode) => {
5377
+ const repository = await getGitRepository(cwd);
5378
+ const loaded = await loadConfig(repository.worktreePath);
5379
+ if (!loaded.config) {
5380
+ const lifecyclePlan = {
5381
+ schemaVersion: "truthmark-lifecycle/v0",
5382
+ mode,
5383
+ entries: [],
5384
+ diagnostics: loaded.diagnostics,
5385
+ applicable: false,
5386
+ applied: false
5387
+ };
5388
+ return {
5389
+ command: "uninstall",
5390
+ summary: "Truthmark uninstall requires a valid .truthmark/config.yml; no files were changed.",
5391
+ diagnostics: loaded.diagnostics,
5392
+ data: { lifecyclePlan }
5393
+ };
5394
+ }
5395
+ const planned = await buildLifecyclePlan(
5396
+ repository.worktreePath,
5397
+ loaded.config,
5398
+ mode,
5399
+ []
5400
+ );
5401
+ const plan = await applyLifecyclePlan(repository.worktreePath, planned);
5402
+ return {
5403
+ command: "uninstall",
5404
+ summary: mode === "dry-run" ? "Truthmark uninstall dry run completed; no files were changed." : plan.applied ? "Truthmark generated host surfaces were uninstalled; authored truth and config were preserved." : "Truthmark uninstall was not applied.",
5405
+ diagnostics: [...loaded.diagnostics, ...plan.diagnostics],
5406
+ data: { lifecyclePlan: plan }
5407
+ };
5408
+ };
5409
+
5153
5410
  // src/checks/branch-scope.ts
5154
5411
  import fs8 from "fs/promises";
5155
5412
  import fg from "fast-glob";
@@ -5229,7 +5486,7 @@ import fg2 from "fast-glob";
5229
5486
  var looksLikeGlob = (pattern) => {
5230
5487
  return /[*?[\]{}()!+@]/u.test(pattern);
5231
5488
  };
5232
- var pathExists2 = async (absolutePath) => {
5489
+ var pathExists = async (absolutePath) => {
5233
5490
  try {
5234
5491
  await fs9.stat(absolutePath);
5235
5492
  return true;
@@ -5299,7 +5556,7 @@ var checkControlledPaths = async (rootDir, controlledPaths) => {
5299
5556
  });
5300
5557
  continue;
5301
5558
  }
5302
- if (!await pathExists2(absoluteEntryPath)) {
5559
+ if (!await pathExists(absoluteEntryPath)) {
5303
5560
  diagnostics.push({
5304
5561
  category: "authority",
5305
5562
  severity: "error",
@@ -5517,7 +5774,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
5517
5774
  // src/checks/links.ts
5518
5775
  import fs11 from "fs/promises";
5519
5776
  import path5 from "path";
5520
- var pathExists3 = async (absolutePath) => {
5777
+ var pathExists2 = async (absolutePath) => {
5521
5778
  try {
5522
5779
  await fs11.stat(absolutePath);
5523
5780
  return true;
@@ -5563,7 +5820,7 @@ var checkLinks = async (rootDir, markdownPaths) => {
5563
5820
  });
5564
5821
  continue;
5565
5822
  }
5566
- if (!await pathExists3(absoluteTarget)) {
5823
+ if (!await pathExists2(absoluteTarget)) {
5567
5824
  diagnostics.push({
5568
5825
  category: "links",
5569
5826
  severity: "error",
@@ -5577,14 +5834,76 @@ var checkLinks = async (rootDir, markdownPaths) => {
5577
5834
  };
5578
5835
 
5579
5836
  // src/checks/areas.ts
5580
- import fs13 from "fs/promises";
5581
- import fg4 from "fast-glob";
5582
- import micromatch4 from "micromatch";
5837
+ import fs14 from "fs/promises";
5838
+ import fg5 from "fast-glob";
5839
+ import micromatch5 from "micromatch";
5583
5840
 
5584
- // src/routing/area-resolver.ts
5841
+ // src/git/files.ts
5585
5842
  import fs12 from "fs/promises";
5843
+ import { execa as execa2 } from "execa";
5586
5844
  import fg3 from "fast-glob";
5587
5845
  import micromatch2 from "micromatch";
5846
+ var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
5847
+ var normalizePath = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
5848
+ var isIgnoredPath = (filePath, ignorePatterns) => micromatch2.isMatch(filePath, [...defaultIgnore, ...ignorePatterns]);
5849
+ var gitDiscoverableFiles = async (rootDir) => {
5850
+ try {
5851
+ const result = await execa2(
5852
+ "git",
5853
+ [
5854
+ "ls-files",
5855
+ "-z",
5856
+ "--cached",
5857
+ "--others",
5858
+ "--exclude-standard",
5859
+ "--deduplicate"
5860
+ ],
5861
+ {
5862
+ cwd: rootDir,
5863
+ reject: false,
5864
+ stripFinalNewline: false
5865
+ }
5866
+ );
5867
+ if ((result.exitCode ?? 1) !== 0) {
5868
+ return null;
5869
+ }
5870
+ return result.stdout.split("\0").filter((filePath) => filePath.length > 0).map(normalizePath);
5871
+ } catch {
5872
+ return null;
5873
+ }
5874
+ };
5875
+ var isCurrentContainedFile = async (rootDir, relativePath) => {
5876
+ try {
5877
+ const absolutePath = resolveRepoPath(rootDir, relativePath);
5878
+ await assertRepoContainment(rootDir, absolutePath);
5879
+ return (await fs12.stat(absolutePath)).isFile();
5880
+ } catch {
5881
+ return false;
5882
+ }
5883
+ };
5884
+ var discoverRepositoryFilePaths = async (rootDir, ignorePatterns) => {
5885
+ const discoveredPaths = await gitDiscoverableFiles(rootDir) ?? await fg3(["**/*"], {
5886
+ cwd: rootDir,
5887
+ onlyFiles: true,
5888
+ dot: true,
5889
+ ignore: [...defaultIgnore, ...ignorePatterns],
5890
+ followSymbolicLinks: false
5891
+ });
5892
+ const paths = [...new Set(discoveredPaths.map(normalizePath))].filter((filePath) => !isIgnoredPath(filePath, ignorePatterns)).sort();
5893
+ const currentPaths = await Promise.all(
5894
+ paths.map(async (filePath) => {
5895
+ return await isCurrentContainedFile(rootDir, filePath) ? filePath : null;
5896
+ })
5897
+ );
5898
+ return currentPaths.filter(
5899
+ (filePath) => filePath !== null
5900
+ );
5901
+ };
5902
+
5903
+ // src/routing/area-resolver.ts
5904
+ import fs13 from "fs/promises";
5905
+ import fg4 from "fast-glob";
5906
+ import micromatch3 from "micromatch";
5588
5907
  var unique = (values) => {
5589
5908
  return [...new Set(values)];
5590
5909
  };
@@ -5603,7 +5922,7 @@ var isCodeSurfaceWithinParent = (childPattern, parentPatterns) => {
5603
5922
  return false;
5604
5923
  }
5605
5924
  return parentPatterns.some((parentPattern) => {
5606
- return micromatch2.isMatch(childPrefix, parentPattern) || micromatch2.isMatch(childPattern, parentPattern);
5925
+ return micromatch3.isMatch(childPrefix, parentPattern) || micromatch3.isMatch(childPattern, parentPattern);
5607
5926
  });
5608
5927
  };
5609
5928
  var ensureChildPath = async (rootDir, areaFilesRoot, filePath) => {
@@ -5633,7 +5952,7 @@ var ensureChildPath = async (rootDir, areaFilesRoot, filePath) => {
5633
5952
  var readRouteFile = async (rootDir, filePath) => {
5634
5953
  try {
5635
5954
  return {
5636
- source: await fs12.readFile(resolveRepoPath(rootDir, filePath), "utf8"),
5955
+ source: await fs13.readFile(resolveRepoPath(rootDir, filePath), "utf8"),
5637
5956
  diagnostic: null
5638
5957
  };
5639
5958
  } catch (error) {
@@ -5752,7 +6071,7 @@ var resolveAreaRouting = async (rootDir, config) => {
5752
6071
  );
5753
6072
  }
5754
6073
  }
5755
- const routeFilesUnderRoot = await fg3([`${config.areaFilesRoot}/**/*.md`], {
6074
+ const routeFilesUnderRoot = await fg4([`${config.areaFilesRoot}/**/*.md`], {
5756
6075
  cwd: rootDir,
5757
6076
  onlyFiles: true,
5758
6077
  followSymbolicLinks: false
@@ -5794,7 +6113,8 @@ var resolveAreaRouting = async (rootDir, config) => {
5794
6113
  };
5795
6114
 
5796
6115
  // src/sync/classify.ts
5797
- import micromatch3 from "micromatch";
6116
+ import path6 from "path";
6117
+ import micromatch4 from "micromatch";
5798
6118
  var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
5799
6119
  ".c",
5800
6120
  ".cc",
@@ -5871,11 +6191,11 @@ var FUNCTIONAL_CONFIG_BASENAMES = /* @__PURE__ */ new Set([
5871
6191
  "swagger.yaml",
5872
6192
  "swagger.yml"
5873
6193
  ]);
5874
- var normalizePath = (filePath) => {
6194
+ var normalizePath2 = (filePath) => {
5875
6195
  return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
5876
6196
  };
5877
6197
  var getBaseName = (filePath) => {
5878
- const segments = normalizePath(filePath).split("/");
6198
+ const segments = normalizePath2(filePath).split("/");
5879
6199
  return segments.at(-1) ?? filePath;
5880
6200
  };
5881
6201
  var getExtension = (filePath) => {
@@ -5886,20 +6206,23 @@ var getExtension = (filePath) => {
5886
6206
  }
5887
6207
  return baseName.slice(extensionIndex).toLowerCase();
5888
6208
  };
6209
+ var isTestPath = (filePath) => {
6210
+ return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path6.posix.basename(filePath));
6211
+ };
5889
6212
  var isConfigPath = (filePath) => {
5890
- const normalizedPath = normalizePath(filePath);
6213
+ const normalizedPath = normalizePath2(filePath);
5891
6214
  const baseName = getBaseName(normalizedPath);
5892
6215
  const extension = getExtension(normalizedPath);
5893
6216
  return CONFIG_BASENAMES.has(baseName) || CONFIG_EXTENSIONS.has(extension) || CONFIG_SUFFIXES.some((suffix) => baseName.endsWith(suffix));
5894
6217
  };
5895
6218
  var isFunctionalConfigPath = (filePath) => {
5896
- const normalizedPath = normalizePath(filePath);
6219
+ const normalizedPath = normalizePath2(filePath);
5897
6220
  const baseName = getBaseName(normalizedPath).toLowerCase();
5898
6221
  const extension = getExtension(normalizedPath);
5899
6222
  return normalizedPath.startsWith(".github/workflows/") || FUNCTIONAL_CONFIG_BASENAMES.has(baseName) || (extension === ".yaml" || extension === ".yml" || extension === ".json") && FUNCTIONAL_CONFIG_DIRECTORIES.test(normalizedPath);
5900
6223
  };
5901
6224
  var isCodeLikePath = (filePath) => {
5902
- const normalizedPath = normalizePath(filePath);
6225
+ const normalizedPath = normalizePath2(filePath);
5903
6226
  const extension = getExtension(normalizedPath);
5904
6227
  if (CODE_EXTENSIONS.has(extension)) {
5905
6228
  return true;
@@ -5907,7 +6230,7 @@ var isCodeLikePath = (filePath) => {
5907
6230
  return extension.length === 0 && COMMON_CODE_DIRECTORIES.test(normalizedPath);
5908
6231
  };
5909
6232
  var classifyPath = (filePath, ignorePatterns) => {
5910
- const normalizedPath = normalizePath(filePath);
6233
+ const normalizedPath = normalizePath2(filePath);
5911
6234
  if (normalizedPath === ".truthmark/config.yml") {
5912
6235
  return "config";
5913
6236
  }
@@ -5917,7 +6240,7 @@ var classifyPath = (filePath, ignorePatterns) => {
5917
6240
  if (normalizedPath.startsWith(".agents/skills/truthmark-") || normalizedPath.startsWith(".claude/") || normalizedPath.startsWith(".codex/") || normalizedPath.startsWith(".opencode/") || normalizedPath.startsWith(".antigravity/rules/truthmark-") || normalizedPath.startsWith(".cursor/rules/truthmark-") || normalizedPath.startsWith(".cursor/skills/truthmark-") || normalizedPath === ".github/copilot-instructions.md" || normalizedPath.startsWith(".github/agents/truth-") || normalizedPath.startsWith(".github/prompts/truthmark-") || normalizedPath.startsWith(".github/skills/truthmark-") || normalizedPath === "AGENTS.md" || normalizedPath === "CLAUDE.md") {
5918
6241
  return "derived";
5919
6242
  }
5920
- if (ignorePatterns.length > 0 && micromatch3.isMatch(normalizedPath, ignorePatterns)) {
6243
+ if (ignorePatterns.length > 0 && micromatch4.isMatch(normalizedPath, ignorePatterns)) {
5921
6244
  return "ignored";
5922
6245
  }
5923
6246
  if (normalizedPath.toLowerCase().endsWith(".md")) {
@@ -5939,9 +6262,9 @@ var classifyPath = (filePath, ignorePatterns) => {
5939
6262
  var looksLikeGlob2 = (pattern) => {
5940
6263
  return /[*?[\]{}()!+@]/u.test(pattern);
5941
6264
  };
5942
- var pathExists4 = async (absolutePath) => {
6265
+ var pathExists3 = async (absolutePath) => {
5943
6266
  try {
5944
- await fs13.stat(absolutePath);
6267
+ await fs14.stat(absolutePath);
5945
6268
  return true;
5946
6269
  } catch (error) {
5947
6270
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -5950,33 +6273,6 @@ var pathExists4 = async (absolutePath) => {
5950
6273
  throw error;
5951
6274
  }
5952
6275
  };
5953
- var COVERAGE_SCAN_PATTERNS = [
5954
- "app/**/*",
5955
- "api/**/*",
5956
- "apps/**/*",
5957
- "bin/**/*",
5958
- "client/**/*",
5959
- "cmd/**/*",
5960
- "frontend/**/*",
5961
- "infra/**/*",
5962
- "infrastructure/**/*",
5963
- "internal/**/*",
5964
- "k8s/**/*",
5965
- "kubernetes/**/*",
5966
- "lib/**/*",
5967
- "packages/**/*",
5968
- "pkg/**/*",
5969
- "proto/**/*",
5970
- "schema/**/*",
5971
- "schemas/**/*",
5972
- "scripts/**/*",
5973
- "server/**/*",
5974
- "services/**/*",
5975
- "src/**/*",
5976
- "terraform/**/*",
5977
- "web/**/*",
5978
- ".github/workflows/**/*"
5979
- ];
5980
6276
  var BROAD_CODE_SURFACES = /* @__PURE__ */ new Set([
5981
6277
  "app/**",
5982
6278
  "apps/**",
@@ -6098,15 +6394,12 @@ var checkAreas = async (rootDir, config) => {
6098
6394
  productTruthRoot: resolveProductTruthRoot(config),
6099
6395
  engineeringTruthRoot: resolveEngineeringTruthRoot(config)
6100
6396
  });
6101
- const discoveredCodeFiles = await fg4([...COVERAGE_SCAN_PATTERNS], {
6102
- cwd: rootDir,
6103
- onlyFiles: true,
6104
- ignore: config.ignore,
6105
- followSymbolicLinks: false,
6106
- dot: true
6107
- });
6397
+ const discoveredCodeFiles = await discoverRepositoryFilePaths(
6398
+ rootDir,
6399
+ config.ignore
6400
+ );
6108
6401
  const rawCodeFiles = discoveredCodeFiles.filter(
6109
- (filePath) => classifyPath(filePath, config.ignore) === "functional-code"
6402
+ (filePath) => classifyPath(filePath, config.ignore) === "functional-code" && !isTestPath(filePath)
6110
6403
  );
6111
6404
  const diagnostics = [...routing.diagnostics];
6112
6405
  const truthDocumentPaths = [];
@@ -6118,15 +6411,7 @@ var checkAreas = async (rootDir, config) => {
6118
6411
  valid: true,
6119
6412
  patterns: []
6120
6413
  }));
6121
- const codeFiles = [];
6122
- for (const codeFile of rawCodeFiles.sort()) {
6123
- try {
6124
- await assertRepoContainment(rootDir, resolveRepoPath(rootDir, codeFile));
6125
- codeFiles.push(codeFile);
6126
- } catch {
6127
- continue;
6128
- }
6129
- }
6414
+ const codeFiles = rawCodeFiles;
6130
6415
  const truthReferences = routing.truthDocumentReferences;
6131
6416
  for (const area of truthReferences) {
6132
6417
  let areaHasTruthDocumentErrors = false;
@@ -6176,7 +6461,7 @@ var checkAreas = async (rootDir, config) => {
6176
6461
  ] of area.truthDocuments.entries()) {
6177
6462
  const routedEntry = area.truthDocumentEntries[truthDocumentIndex];
6178
6463
  if (looksLikeGlob2(truthDocument)) {
6179
- const matches = (await fg4([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
6464
+ const matches = (await fg5([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
6180
6465
  if (matches.length === 0) {
6181
6466
  diagnostics.push({
6182
6467
  category: "area-index",
@@ -6231,7 +6516,7 @@ var checkAreas = async (rootDir, config) => {
6231
6516
  areaHasTruthDocumentErrors = true;
6232
6517
  continue;
6233
6518
  }
6234
- if (!await pathExists4(absoluteTruthDocumentPath)) {
6519
+ if (!await pathExists3(absoluteTruthDocumentPath)) {
6235
6520
  diagnostics.push({
6236
6521
  category: "area-index",
6237
6522
  severity: "error",
@@ -6284,7 +6569,7 @@ var checkAreas = async (rootDir, config) => {
6284
6569
  entry.valid = false;
6285
6570
  continue;
6286
6571
  }
6287
- const matches = await fg4([codeSurfaceEntry], {
6572
+ const matches = await fg5([codeSurfaceEntry], {
6288
6573
  cwd: rootDir,
6289
6574
  onlyFiles: true,
6290
6575
  followSymbolicLinks: false
@@ -6335,7 +6620,7 @@ var checkAreas = async (rootDir, config) => {
6335
6620
  entry.valid = false;
6336
6621
  continue;
6337
6622
  }
6338
- if (!await pathExists4(absoluteCodeSurfacePath)) {
6623
+ if (!await pathExists3(absoluteCodeSurfacePath)) {
6339
6624
  diagnostics.push({
6340
6625
  category: "area-index",
6341
6626
  severity: "error",
@@ -6350,7 +6635,7 @@ var checkAreas = async (rootDir, config) => {
6350
6635
  }
6351
6636
  for (const codeFile of codeFiles.sort()) {
6352
6637
  const matched = areaCoverage.some(
6353
- (entry) => entry.valid && entry.patterns.some((pattern) => micromatch4.isMatch(codeFile, pattern))
6638
+ (entry) => entry.valid && entry.patterns.some((pattern) => micromatch5.isMatch(codeFile, pattern))
6354
6639
  );
6355
6640
  if (!matched) {
6356
6641
  diagnostics.push({
@@ -6382,8 +6667,8 @@ var checkAreas = async (rootDir, config) => {
6382
6667
  };
6383
6668
 
6384
6669
  // src/checks/decisions.ts
6385
- import fs14 from "fs/promises";
6386
- import micromatch5 from "micromatch";
6670
+ import fs15 from "fs/promises";
6671
+ import micromatch6 from "micromatch";
6387
6672
  var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
6388
6673
  "Capability Promise",
6389
6674
  "Users And Value",
@@ -6419,11 +6704,11 @@ var FORBIDDEN_ENGINEERING_HEADINGS = [
6419
6704
  var isTruthDocumentKind3 = (value) => {
6420
6705
  return TRUTH_DOCUMENT_KINDS.includes(value);
6421
6706
  };
6422
- var escapeRegExp2 = (value) => {
6707
+ var escapeRegExp = (value) => {
6423
6708
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6424
6709
  };
6425
6710
  var hasHeading = (source, heading) => {
6426
- return new RegExp(`^#{2,3}\\s+${escapeRegExp2(heading)}\\s*$`, "mu").test(
6711
+ return new RegExp(`^#{2,3}\\s+${escapeRegExp(heading)}\\s*$`, "mu").test(
6427
6712
  source
6428
6713
  );
6429
6714
  };
@@ -6487,7 +6772,7 @@ var decisionTruthGlobs = (config) => {
6487
6772
  ];
6488
6773
  };
6489
6774
  var isDecisionTruthCandidate = (config, filePath) => {
6490
- return !filePath.endsWith("/README.md") && micromatch5.isMatch(filePath, decisionTruthGlobs(config));
6775
+ return !filePath.endsWith("/README.md") && micromatch6.isMatch(filePath, decisionTruthGlobs(config));
6491
6776
  };
6492
6777
  var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocumentEntries = []) => {
6493
6778
  const diagnostics = [];
@@ -6498,7 +6783,7 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
6498
6783
  (filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
6499
6784
  ).sort();
6500
6785
  for (const filePath of candidatePaths) {
6501
- const source = await fs14.readFile(
6786
+ const source = await fs15.readFile(
6502
6787
  resolveRepoPath(rootDir, filePath),
6503
6788
  "utf8"
6504
6789
  );
@@ -6539,19 +6824,85 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
6539
6824
  return diagnostics;
6540
6825
  };
6541
6826
 
6827
+ // src/checks/generated-surfaces.ts
6828
+ import fs16 from "fs/promises";
6829
+ var readOptionalFile = async (rootDir, filePath) => {
6830
+ try {
6831
+ return await fs16.readFile(resolveRepoPath(rootDir, filePath), "utf8");
6832
+ } catch (error) {
6833
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6834
+ return null;
6835
+ }
6836
+ throw error;
6837
+ }
6838
+ };
6839
+ var normalizeGeneratedSurfaceContent = (content) => {
6840
+ if (content === null) {
6841
+ return null;
6842
+ }
6843
+ return content.replace(/\r\n/g, "\n").replace(/\n$/u, "");
6844
+ };
6845
+ var isRetiredGeminiSurfacePath = (filePath) => filePath === "GEMINI.md" || filePath.startsWith(".gemini/");
6846
+ var obsoleteGeneratedSurfaceMessage = (surfacePath) => {
6847
+ if (isRetiredGeminiSurfacePath(surfacePath)) {
6848
+ return `Generated surface ${surfacePath} is obsolete; remove stale Gemini instructions manually if they are no longer wanted.`;
6849
+ }
6850
+ return `Generated surface ${surfacePath} is obsolete; rerun truthmark init.`;
6851
+ };
6852
+ var checkGeneratedSurfaces = async (rootDir, config) => {
6853
+ const diagnostics = [];
6854
+ const renderedSurfaces = renderGeneratedSurfaces(config);
6855
+ for (const surface of renderedSurfaces) {
6856
+ const content = await readOptionalFile(rootDir, surface.path);
6857
+ if (content === null) {
6858
+ diagnostics.push({
6859
+ category: "generated-surface",
6860
+ severity: "review",
6861
+ message: `Generated surface ${surface.path} is missing; rerun truthmark init.`,
6862
+ file: surface.path
6863
+ });
6864
+ continue;
6865
+ }
6866
+ const comparableContent = normalizeGeneratedSurfaceContent(
6867
+ surface.managedBlock ? extractManagedBlock(content) : content
6868
+ );
6869
+ const expectedContent = normalizeGeneratedSurfaceContent(surface.content);
6870
+ if (comparableContent !== expectedContent) {
6871
+ diagnostics.push({
6872
+ category: "generated-surface",
6873
+ severity: "review",
6874
+ message: `Generated surface ${surface.path} is stale; rerun truthmark init.`,
6875
+ file: surface.path
6876
+ });
6877
+ }
6878
+ }
6879
+ const lifecyclePlan = await buildLifecyclePlan(
6880
+ rootDir,
6881
+ config,
6882
+ "dry-run",
6883
+ renderedSurfaces
6884
+ );
6885
+ for (const entry of lifecyclePlan.entries) {
6886
+ diagnostics.push({
6887
+ category: "generated-surface",
6888
+ severity: entry.action === "manual-review" ? "error" : "review",
6889
+ message: entry.action === "preserve" && (entry.path.includes("truthmark-preview") || entry.path.endsWith("helper-manifest.yml") || entry.path.endsWith("support/helper-policy.md") || isRetiredGeminiSurfacePath(entry.path)) ? obsoleteGeneratedSurfaceMessage(entry.path) : entry.action === "preserve" ? `Generated surface ${entry.path} is inactive but was preserved: ${entry.reason}` : `Generated surface ${entry.path} is inactive; rerun truthmark init to reconcile it.`,
6890
+ file: entry.path
6891
+ });
6892
+ }
6893
+ return diagnostics;
6894
+ };
6895
+
6542
6896
  // src/impact/build.ts
6543
- import path10 from "path";
6897
+ import path11 from "path";
6544
6898
  import micromatch7 from "micromatch";
6545
6899
 
6546
6900
  // src/repo-index/file-tree.ts
6547
- import fs15 from "fs/promises";
6548
- import path7 from "path";
6549
- import { execa as execa2 } from "execa";
6550
- import fg5 from "fast-glob";
6551
- import micromatch6 from "micromatch";
6901
+ import fs17 from "fs/promises";
6902
+ import path8 from "path";
6552
6903
 
6553
6904
  // src/truth/source-references.ts
6554
- import path6 from "path";
6905
+ import path7 from "path";
6555
6906
  var repoRootPrefixes = [
6556
6907
  ".codex/",
6557
6908
  ".github/",
@@ -6571,11 +6922,11 @@ var normalizeSourceReferencePath = (truthDocPath, referencePath) => {
6571
6922
  (prefix) => strippedPath.startsWith(prefix)
6572
6923
  );
6573
6924
  if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
6574
- return path6.posix.normalize(
6575
- path6.posix.join(path6.posix.dirname(truthDocPath), strippedPath)
6925
+ return path7.posix.normalize(
6926
+ path7.posix.join(path7.posix.dirname(truthDocPath), strippedPath)
6576
6927
  );
6577
6928
  }
6578
- return path6.posix.normalize(strippedPath);
6929
+ return path7.posix.normalize(strippedPath);
6579
6930
  };
6580
6931
  var normalizeReferenceText = (value) => {
6581
6932
  const trimmed = value.trim();
@@ -6640,9 +6991,6 @@ var languageByExtension = /* @__PURE__ */ new Map([
6640
6991
  [".yaml", "yaml"],
6641
6992
  [".toml", "toml"]
6642
6993
  ]);
6643
- var isTestPath = (filePath) => {
6644
- return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
6645
- };
6646
6994
  var fileKind = (filePath, ignore) => {
6647
6995
  const classification = classifyPath(filePath, ignore);
6648
6996
  if (classification === "derived") {
@@ -6667,7 +7015,7 @@ var fileKind = (filePath, ignore) => {
6667
7015
  };
6668
7016
  var targetHintsForTest = (filePath) => {
6669
7017
  const hints = /* @__PURE__ */ new Set();
6670
- const basename = path7.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
7018
+ const basename = path8.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
6671
7019
  if (basename.length > 0) {
6672
7020
  hints.add(basename);
6673
7021
  }
@@ -6684,43 +7032,18 @@ var targetHintsForTest = (filePath) => {
6684
7032
  }
6685
7033
  return [...hints].sort();
6686
7034
  };
6687
- var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
6688
- var normalizePath2 = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
6689
7035
  var isTruthDocumentKind4 = (value) => {
6690
7036
  return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
6691
7037
  };
6692
- var gitDiscoverableFiles = async (rootDir) => {
6693
- const result = await execa2(
6694
- "git",
6695
- ["ls-files", "--cached", "--others", "--exclude-standard", "--deduplicate"],
6696
- {
6697
- cwd: rootDir,
6698
- reject: false
6699
- }
6700
- );
6701
- if ((result.exitCode ?? 1) !== 0) {
6702
- return null;
6703
- }
6704
- return result.stdout.split("\n").map((line) => normalizePath2(line.trim())).filter((line) => line.length > 0);
6705
- };
6706
- var isIgnoredPath = (filePath, ignore) => {
6707
- return micromatch6.isMatch(filePath, [...defaultIgnore, ...ignore]);
6708
- };
6709
7038
  var discoverRepoFiles = async (rootDir, ignore) => {
6710
- const discoveredFiles = await gitDiscoverableFiles(rootDir) ?? await fg5(["**/*"], {
6711
- cwd: rootDir,
6712
- onlyFiles: true,
6713
- dot: true,
6714
- ignore: [...defaultIgnore, ...ignore],
6715
- followSymbolicLinks: false
6716
- });
7039
+ const discoveredFiles = await discoverRepositoryFilePaths(rootDir, ignore);
6717
7040
  const files = [];
6718
7041
  const docs = [];
6719
7042
  const tests = [];
6720
- for (const filePath of discoveredFiles.filter((entry) => !isIgnoredPath(entry, ignore)).sort()) {
7043
+ for (const filePath of discoveredFiles) {
6721
7044
  let stat;
6722
7045
  try {
6723
- stat = await fs15.stat(path7.join(rootDir, filePath));
7046
+ stat = await fs17.stat(path8.join(rootDir, filePath));
6724
7047
  } catch (error) {
6725
7048
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6726
7049
  continue;
@@ -6730,7 +7053,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6730
7053
  if (!stat.isFile()) {
6731
7054
  continue;
6732
7055
  }
6733
- const extension = path7.posix.extname(filePath);
7056
+ const extension = path8.posix.extname(filePath);
6734
7057
  const kind = fileKind(filePath, ignore);
6735
7058
  files.push({
6736
7059
  path: filePath,
@@ -6744,7 +7067,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6744
7067
  });
6745
7068
  }
6746
7069
  if (kind === "doc") {
6747
- const source = await fs15.readFile(path7.join(rootDir, filePath), "utf8");
7070
+ const source = await fs17.readFile(path8.join(rootDir, filePath), "utf8");
6748
7071
  const parsed = parseFrontmatter(source);
6749
7072
  const markdown = parseMarkdownDocument(parsed.content);
6750
7073
  const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
@@ -6772,8 +7095,8 @@ var discoverRepoFiles = async (rootDir, ignore) => {
6772
7095
  };
6773
7096
 
6774
7097
  // src/repo-index/package-metadata.ts
6775
- import fs16 from "fs/promises";
6776
- import path8 from "path";
7098
+ import fs18 from "fs/promises";
7099
+ import path9 from "path";
6777
7100
  import fg6 from "fast-glob";
6778
7101
  var packageManagerFor = async (rootDir, packageDir) => {
6779
7102
  const lockfiles = [
@@ -6785,7 +7108,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
6785
7108
  ];
6786
7109
  for (const [lockfile, manager] of lockfiles) {
6787
7110
  try {
6788
- await fs16.access(path8.join(rootDir, packageDir, lockfile));
7111
+ await fs18.access(path9.join(rootDir, packageDir, lockfile));
6789
7112
  return manager;
6790
7113
  } catch {
6791
7114
  continue;
@@ -6802,8 +7125,8 @@ var discoverPackageMetadata = async (rootDir) => {
6802
7125
  });
6803
7126
  const packages = [];
6804
7127
  for (const packageFile of packageFiles.sort()) {
6805
- const packageDir = path8.posix.dirname(packageFile) === "." ? "" : path8.posix.dirname(packageFile);
6806
- const raw = JSON.parse(await fs16.readFile(path8.join(rootDir, packageFile), "utf8"));
7128
+ const packageDir = path9.posix.dirname(packageFile) === "." ? "" : path9.posix.dirname(packageFile);
7129
+ const raw = JSON.parse(await fs18.readFile(path9.join(rootDir, packageFile), "utf8"));
6807
7130
  const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
6808
7131
  packages.push({
6809
7132
  path: packageFile,
@@ -6908,8 +7231,8 @@ var buildRepoIndex = async (cwd) => {
6908
7231
  import { execa as execa4 } from "execa";
6909
7232
 
6910
7233
  // src/git/changes.ts
6911
- import fs17 from "fs/promises";
6912
- import path9 from "path";
7234
+ import fs19 from "fs/promises";
7235
+ import path10 from "path";
6913
7236
  import { execa as execa3 } from "execa";
6914
7237
  var normalizePath3 = (filePath) => {
6915
7238
  return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
@@ -6918,9 +7241,9 @@ var listChangedPaths = async (cwd, args) => {
6918
7241
  const result = await execa3("git", args, { cwd });
6919
7242
  return result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => normalizePath3(line));
6920
7243
  };
6921
- var pathExists5 = async (filePath) => {
7244
+ var pathExists4 = async (filePath) => {
6922
7245
  try {
6923
- await fs17.access(filePath);
7246
+ await fs19.access(filePath);
6924
7247
  return true;
6925
7248
  } catch {
6926
7249
  return false;
@@ -6964,7 +7287,7 @@ var getUncommittedChanges = async (cwd) => {
6964
7287
  const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
6965
7288
  for (const deletedPath of deletedPathCandidates) {
6966
7289
  const change = getOrCreateChange(changesByPath, deletedPath);
6967
- change.deleted = !await pathExists5(path9.join(rootDir, deletedPath));
7290
+ change.deleted = !await pathExists4(path10.join(rootDir, deletedPath));
6968
7291
  }
6969
7292
  return Array.from(changesByPath.values()).sort((left, right) => {
6970
7293
  return left.path.localeCompare(right.path);
@@ -7068,10 +7391,10 @@ var toImpactRoute = (route) => ({
7068
7391
  var changedFilePaths = (changedFile) => {
7069
7392
  return [changedFile.path, ...changedFile.previousPath ? [changedFile.previousPath] : []];
7070
7393
  };
7071
- var pathSegments = (filePath) => filePath.split("/").filter(Boolean);
7394
+ var pathSegments2 = (filePath) => filePath.split("/").filter(Boolean);
7072
7395
  var testHintMatchesChangedFile = (hints, changedPath) => {
7073
- const changedBaseName = path10.posix.basename(changedPath);
7074
- const changedSegments = pathSegments(changedPath);
7396
+ const changedBaseName = path11.posix.basename(changedPath);
7397
+ const changedSegments = pathSegments2(changedPath);
7075
7398
  return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
7076
7399
  };
7077
7400
  var buildImpactSet = async (cwd, options) => {
@@ -7167,12 +7490,12 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
7167
7490
  };
7168
7491
 
7169
7492
  // src/evidence/validate.ts
7170
- import fs19 from "fs/promises";
7493
+ import fs21 from "fs/promises";
7171
7494
  import fg7 from "fast-glob";
7172
7495
 
7173
7496
  // src/evidence/parse.ts
7174
- import fs18 from "fs/promises";
7175
- import path11 from "path";
7497
+ import fs20 from "fs/promises";
7498
+ import path12 from "path";
7176
7499
  import { parse as parse4 } from "yaml";
7177
7500
  var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
7178
7501
  var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
@@ -7191,7 +7514,7 @@ var toEvidenceReference = (truthDocPath, raw) => {
7191
7514
  };
7192
7515
  };
7193
7516
  var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7194
- const source = await fs18.readFile(path11.join(rootDir, truthDocPath), "utf8");
7517
+ const source = await fs20.readFile(path12.join(rootDir, truthDocPath), "utf8");
7195
7518
  const parsed = parseFrontmatter(source);
7196
7519
  const references = [];
7197
7520
  for (const entry of parseSourceReferences(source, truthDocPath)) {
@@ -7222,9 +7545,9 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
7222
7545
  };
7223
7546
 
7224
7547
  // src/evidence/validate.ts
7225
- var pathExists6 = async (filePath) => {
7548
+ var pathExists5 = async (filePath) => {
7226
7549
  try {
7227
- await fs19.access(filePath);
7550
+ await fs21.access(filePath);
7228
7551
  return true;
7229
7552
  } catch {
7230
7553
  return false;
@@ -7269,7 +7592,7 @@ var validateHash = async (rootDir, reference) => {
7269
7592
  if (!reference.contentHash.startsWith("sha256:")) {
7270
7593
  return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
7271
7594
  }
7272
- const source = await fs19.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7595
+ const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7273
7596
  const lines = source.split("\n");
7274
7597
  const startLine = reference.startLine ?? 1;
7275
7598
  const endLine = reference.endLine ?? lines.length;
@@ -7283,7 +7606,7 @@ var validateLineSpan = async (rootDir, reference) => {
7283
7606
  if (reference.startLine === void 0 && reference.endLine === void 0) {
7284
7607
  return null;
7285
7608
  }
7286
- const source = await fs19.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7609
+ const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
7287
7610
  const lines = source.split("\n");
7288
7611
  const startLine = reference.startLine ?? 1;
7289
7612
  const endLine = reference.endLine ?? lines.length;
@@ -7298,7 +7621,7 @@ var validateReference = async (rootDir, reference) => {
7298
7621
  }
7299
7622
  const absolutePath = resolveRepoPath(rootDir, reference.path);
7300
7623
  await assertRepoContainment(rootDir, absolutePath);
7301
- if (!await pathExists6(absolutePath)) {
7624
+ if (!await pathExists5(absolutePath)) {
7302
7625
  diagnostics.push(diagnosticFor(reference, `Referenced file ${reference.path} does not exist.`));
7303
7626
  return diagnostics;
7304
7627
  }
@@ -7730,7 +8053,7 @@ var candidateStaleTruthDocsFor = (repoIndex, primaryTruthDocs, impactSet, diagno
7730
8053
  ]);
7731
8054
  };
7732
8055
  for (const truthDoc of [...primary, ...changedPaths].filter(
7733
- (path12) => indexedTruthDocs.has(path12)
8056
+ (path13) => indexedTruthDocs.has(path13)
7734
8057
  )) {
7735
8058
  for (const linkedTruthDoc of linkedTruthDocs(truthDoc)) {
7736
8059
  addCandidate(linkedTruthDoc);
@@ -7928,7 +8251,7 @@ var buildWorkflowState = async (cwd, options) => {
7928
8251
  };
7929
8252
 
7930
8253
  // src/cli/handlers.ts
7931
- import fs20 from "fs/promises";
8254
+ import fs22 from "fs/promises";
7932
8255
 
7933
8256
  // src/agents/workflow-helper-validation.ts
7934
8257
  import { parse as parseYaml2 } from "yaml";
@@ -8251,13 +8574,20 @@ var runConfig2 = async (options) => {
8251
8574
  var runInit2 = async () => {
8252
8575
  return runInit(process.cwd());
8253
8576
  };
8577
+ var runUninstall2 = async (mode) => {
8578
+ return runUninstall(process.cwd(), mode);
8579
+ };
8254
8580
  var runCheck2 = async (options = {}) => {
8255
8581
  return runCheck(process.cwd(), options);
8256
8582
  };
8257
8583
  var runIndex = async () => {
8258
8584
  const repoIndex = await buildRepoIndex(process.cwd());
8259
- const errorCount = repoIndex.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
8260
- const reviewCount = repoIndex.diagnostics.filter((diagnostic) => diagnostic.severity === "review").length;
8585
+ const errorCount = repoIndex.diagnostics.filter(
8586
+ (diagnostic) => diagnostic.severity === "error"
8587
+ ).length;
8588
+ const reviewCount = repoIndex.diagnostics.filter(
8589
+ (diagnostic) => diagnostic.severity === "review"
8590
+ ).length;
8261
8591
  return {
8262
8592
  command: "index",
8263
8593
  summary: `Truthmark index completed with ${errorCount} error diagnostics and ${reviewCount} review diagnostics.`,
@@ -8283,8 +8613,12 @@ var runImpact = async (options) => {
8283
8613
  };
8284
8614
  }
8285
8615
  const impactSet = await buildImpactSet(process.cwd(), { base: options.base });
8286
- const errorCount = impactSet.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
8287
- const reviewCount = impactSet.diagnostics.filter((diagnostic) => diagnostic.severity === "review").length;
8616
+ const errorCount = impactSet.diagnostics.filter(
8617
+ (diagnostic) => diagnostic.severity === "error"
8618
+ ).length;
8619
+ const reviewCount = impactSet.diagnostics.filter(
8620
+ (diagnostic) => diagnostic.severity === "review"
8621
+ ).length;
8288
8622
  return {
8289
8623
  command: "impact",
8290
8624
  summary: `Truthmark impact completed with ${errorCount} error diagnostics and ${reviewCount} review diagnostics.`,
@@ -8313,7 +8647,7 @@ var invalidWorkflowResult = (command, workflow) => ({
8313
8647
  });
8314
8648
  var readHelperFile = async (filePath, helper) => {
8315
8649
  try {
8316
- return await fs20.readFile(filePath, "utf8");
8650
+ return await fs22.readFile(filePath, "utf8");
8317
8651
  } catch (error) {
8318
8652
  const message = error instanceof Error ? error.message : String(error);
8319
8653
  return { ok: false, helper, errors: [`could not read file: ${message}`] };
@@ -8332,7 +8666,10 @@ var runValidateWriteLease = async (leaseFile, changedFilesFile) => {
8332
8666
  if (typeof leaseText !== "string") {
8333
8667
  return leaseText;
8334
8668
  }
8335
- const changedText = await readHelperFile(changedFilesFile, "validate-write-lease");
8669
+ const changedText = await readHelperFile(
8670
+ changedFilesFile,
8671
+ "validate-write-lease"
8672
+ );
8336
8673
  if (typeof changedText !== "string") {
8337
8674
  return changedText;
8338
8675
  }
@@ -8374,9 +8711,15 @@ var writeResult = (result, options) => {
8374
8711
  };
8375
8712
  var renderValidationHuman = (result) => {
8376
8713
  if (result.ok === true) {
8377
- return [`${result.helper}: ok`, ...result.checks.map((check) => `- ${check}`)].join("\n");
8714
+ return [
8715
+ `${result.helper}: ok`,
8716
+ ...result.checks.map((check) => `- ${check}`)
8717
+ ].join("\n");
8378
8718
  }
8379
- return [`${result.helper}: failed`, ...result.errors.map((error) => `- ${error}`)].join("\n");
8719
+ return [
8720
+ `${result.helper}: failed`,
8721
+ ...result.errors.map((error) => `- ${error}`)
8722
+ ].join("\n");
8380
8723
  };
8381
8724
  var toValidationCommandResult = (command, result) => ({
8382
8725
  command,
@@ -8399,35 +8742,69 @@ var addJsonOption = (command) => {
8399
8742
  };
8400
8743
  var buildProgram = () => {
8401
8744
  const program = new Command();
8402
- program.name("truthmark").description("Git-native, branch-scoped truth workflow installer for local AI coding agents.").showHelpAfterError();
8745
+ program.name("truthmark").description(
8746
+ "Git-native, branch-scoped truth workflow installer for local AI coding agents."
8747
+ ).showHelpAfterError();
8403
8748
  addJsonOption(
8404
- program.command("config").description("Create or render the Truthmark repository config before initialization.").option("--stdout", "Render default config in the JSON data payload without writing").option("--force", "Overwrite an existing .truthmark/config.yml")
8749
+ program.command("config").description(
8750
+ "Create or render the Truthmark repository config before initialization."
8751
+ ).option(
8752
+ "--stdout",
8753
+ "Render default config in the JSON data payload without writing"
8754
+ ).option("--force", "Overwrite an existing .truthmark/config.yml")
8405
8755
  ).action(async (options) => {
8406
8756
  writeResult(await runConfig2(options), options);
8407
8757
  });
8408
8758
  addJsonOption(
8409
- program.command("init").description("Initialize Truthmark workflow files in the current repository.")
8759
+ program.command("init").description(
8760
+ "Initialize Truthmark workflow files in the current repository."
8761
+ )
8410
8762
  ).action(async (options) => {
8411
8763
  writeResult(await runInit2(), options);
8412
8764
  });
8765
+ addJsonOption(
8766
+ program.command("uninstall").description(
8767
+ "Remove recognized generated host surfaces while preserving truth, config, Portal output, Gemini, and user files."
8768
+ ).option("--dry-run", "Plan removals without changing files").option("--apply", "Apply the planned safe removals")
8769
+ ).action(async (options) => {
8770
+ if (Boolean(options.dryRun) === Boolean(options.apply)) {
8771
+ program.error(
8772
+ "truthmark uninstall requires exactly one of --dry-run or --apply"
8773
+ );
8774
+ return;
8775
+ }
8776
+ writeResult(
8777
+ await runUninstall2(options.apply ? "apply" : "dry-run"),
8778
+ options
8779
+ );
8780
+ });
8413
8781
  addJsonOption(
8414
8782
  program.command("check").description("Run local Truthmark diagnostics.").option("--base <ref>", "Base Git ref for freshness diagnostics")
8415
8783
  ).action(async (options) => {
8416
8784
  writeResult(await runCheck2({ base: options.base }), options);
8417
8785
  });
8418
8786
  addJsonOption(
8419
- program.command("index").description("Inspect derived Truthmark workflow routing metadata for the current checkout.")
8787
+ program.command("index").description(
8788
+ "Inspect derived Truthmark workflow routing metadata for the current checkout."
8789
+ )
8420
8790
  ).action(async (options) => {
8421
8791
  writeResult(await runIndex(), options);
8422
8792
  });
8423
8793
  addJsonOption(
8424
- program.command("impact").description("Map changed files to truth routes, docs, owners, and tests.").requiredOption("--base <ref>", "Base Git ref to compare against")
8794
+ program.command("impact").description(
8795
+ "Map changed files to truth routes, docs, owners, and tests."
8796
+ ).requiredOption("--base <ref>", "Base Git ref to compare against")
8425
8797
  ).action(async (options) => {
8426
8798
  writeResult(await runImpact({ base: options.base }), options);
8427
8799
  });
8428
8800
  const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
8429
8801
  addJsonOption(
8430
- workflow.command("status").description("Return schema-versioned workflow state for a canonical workflow ID.").option("--workflow <workflow>", "Canonical workflow ID, such as truthmark-sync").option("--base <ref>", "Base Git ref for impact-backed workflow state")
8802
+ workflow.command("status").description(
8803
+ "Return schema-versioned workflow state for a canonical workflow ID."
8804
+ ).option(
8805
+ "--workflow <workflow>",
8806
+ "Canonical workflow ID, such as truthmark-sync"
8807
+ ).option("--base <ref>", "Base Git ref for impact-backed workflow state")
8431
8808
  ).action(async (options) => {
8432
8809
  writeResult(
8433
8810
  await runWorkflowStatus({
@@ -8437,11 +8814,17 @@ var buildProgram = () => {
8437
8814
  options
8438
8815
  );
8439
8816
  });
8440
- const validate = program.command("validate").description("Run optional Truthmark workflow helper validators from the installed CLI.");
8817
+ const validate = program.command("validate").description(
8818
+ "Run optional Truthmark workflow helper validators from the installed CLI."
8819
+ );
8441
8820
  addJsonOption(
8442
8821
  validate.command("sync-report").description("Validate a Truth Sync report file.").argument("<report-file>", "Truth Sync report file")
8443
8822
  ).action(async (reportFile, options) => {
8444
- writeValidationResult("validate sync-report", await runValidateSyncReport(reportFile), options);
8823
+ writeValidationResult(
8824
+ "validate sync-report",
8825
+ await runValidateSyncReport(reportFile),
8826
+ options
8827
+ );
8445
8828
  });
8446
8829
  addJsonOption(
8447
8830
  validate.command("document-report").description("Validate a Truth Document report file.").argument("<report-file>", "Truth Document report file")
@@ -8453,7 +8836,9 @@ var buildProgram = () => {
8453
8836
  );
8454
8837
  });
8455
8838
  addJsonOption(
8456
- validate.command("write-lease").description("Validate a workflow write lease or worker report against changed files.").argument("<lease-or-report-file>", "Lease or worker report file").argument("<changed-files-file>", "Newline-separated changed file list")
8839
+ validate.command("write-lease").description(
8840
+ "Validate a workflow write lease or worker report against changed files."
8841
+ ).argument("<lease-or-report-file>", "Lease or worker report file").argument("<changed-files-file>", "Newline-separated changed file list")
8457
8842
  ).action(
8458
8843
  async (leaseOrReportFile, changedFilesFile, options) => {
8459
8844
  writeValidationResult(