truthmark 2.2.5 → 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/README.md +19 -3
- package/dist/main.js +1095 -692
- package/dist/main.js.map +1 -1
- package/docs/README.md +13 -11
- package/docs/readmes/README.ar.md +4 -0
- package/docs/readmes/README.de.md +4 -0
- package/docs/readmes/README.el.md +4 -0
- package/docs/readmes/README.es.md +4 -0
- package/docs/readmes/README.fr.md +4 -0
- package/docs/readmes/README.id.md +4 -0
- package/docs/readmes/README.it.md +4 -0
- package/docs/readmes/README.ja.md +4 -0
- package/docs/readmes/README.ko.md +4 -0
- package/docs/readmes/README.pl.md +4 -0
- package/docs/readmes/README.pt.md +4 -0
- package/docs/readmes/README.ru.md +4 -0
- package/docs/readmes/README.tr.md +4 -0
- package/docs/readmes/README.vi.md +4 -0
- package/docs/readmes/README.zh.md +4 -0
- package/package.json +7 -7
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
|
|
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
|
|
619
|
-
const inferredLane = typeof
|
|
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
|
|
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:
|
|
669
|
+
path: path13.trim(),
|
|
633
670
|
kind: normalizedKind,
|
|
634
671
|
kindSource: isTruthDocumentKind(kind) ? "explicit" : "inferred",
|
|
635
672
|
lane: normalizedLane,
|
|
@@ -1235,6 +1272,16 @@ var renderBehaviorDocTemplateFile = () => {
|
|
|
1235
1272
|
"",
|
|
1236
1273
|
"{{core_rules}}",
|
|
1237
1274
|
"",
|
|
1275
|
+
"## Behavior Scenarios",
|
|
1276
|
+
"",
|
|
1277
|
+
"<!--",
|
|
1278
|
+
"Use compact scenario blocks only where they clarify normal, fallback, or compatibility-critical behavior.",
|
|
1279
|
+
"Write scenarios as current truth, not desired requirements: `#### Scenario: <implemented case>` followed by `- **GIVEN** ...`, `- **WHEN** ...`, `- **THEN** ...`, and optional `- **AND** ...` bullets.",
|
|
1280
|
+
"Keep each bullet evidence-backed and observable; do not force a scenario for every rule.",
|
|
1281
|
+
"-->",
|
|
1282
|
+
"",
|
|
1283
|
+
"{{behavior_scenarios}}",
|
|
1284
|
+
"",
|
|
1238
1285
|
"## Flows And States",
|
|
1239
1286
|
"",
|
|
1240
1287
|
"<!--",
|
|
@@ -1727,7 +1774,9 @@ var unsupportedShapeDiagnostics = (parsedConfig, configPath) => {
|
|
|
1727
1774
|
var validateWorkspacePaths = (rawConfig, configPath) => {
|
|
1728
1775
|
const diagnostics = [];
|
|
1729
1776
|
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
1730
|
-
if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some(
|
|
1777
|
+
if (isUnsafeRepoRelativePath(rawConfig.truthmark.workspace) || FORBIDDEN_WORKSPACE_OVERLAPS.some(
|
|
1778
|
+
(forbidden) => pathsOverlap(workspace, forbidden)
|
|
1779
|
+
)) {
|
|
1731
1780
|
diagnostics.push(
|
|
1732
1781
|
toConfigDiagnostic(
|
|
1733
1782
|
"truthmark.workspace must be a non-empty repo-relative directory that does not overlap repository control, package, source, test, or instruction paths.",
|
|
@@ -1735,30 +1784,35 @@ var validateWorkspacePaths = (rawConfig, configPath) => {
|
|
|
1735
1784
|
)
|
|
1736
1785
|
);
|
|
1737
1786
|
}
|
|
1738
|
-
for (const target of rawConfig.instruction_targets ?? DEFAULT_INSTRUCTION_TARGETS) {
|
|
1739
|
-
if (isUnsafeRepoRelativePath(target) || pathsOverlap(workspace, target)) {
|
|
1740
|
-
diagnostics.push(
|
|
1741
|
-
toConfigDiagnostic(
|
|
1742
|
-
"instruction_targets must be repo-relative files outside truthmark.workspace.",
|
|
1743
|
-
configPath
|
|
1744
|
-
)
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
1787
|
return diagnostics;
|
|
1749
1788
|
};
|
|
1750
1789
|
var normalizeConfig = (rawConfig) => {
|
|
1751
1790
|
const workspace = normalizeRepoRelativePath(rawConfig.truthmark.workspace);
|
|
1752
|
-
const routesIndex = joinWorkspacePath(
|
|
1753
|
-
|
|
1754
|
-
|
|
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
|
+
);
|
|
1755
1803
|
const engineeringTruthRoot = joinWorkspacePath(
|
|
1756
1804
|
workspace,
|
|
1757
1805
|
DERIVED_TRUTHMARK_PATHS.engineeringTruthRoot
|
|
1758
1806
|
);
|
|
1759
|
-
const templatesRoot = joinWorkspacePath(
|
|
1807
|
+
const templatesRoot = joinWorkspacePath(
|
|
1808
|
+
workspace,
|
|
1809
|
+
DERIVED_TRUTHMARK_PATHS.templatesRoot
|
|
1810
|
+
);
|
|
1760
1811
|
const portalOutput = portalOutputFor(workspace);
|
|
1761
|
-
const portalTemplate = joinWorkspacePath(
|
|
1812
|
+
const portalTemplate = joinWorkspacePath(
|
|
1813
|
+
workspace,
|
|
1814
|
+
DERIVED_TRUTHMARK_PATHS.portalTemplate
|
|
1815
|
+
);
|
|
1762
1816
|
return {
|
|
1763
1817
|
version: rawConfig.version,
|
|
1764
1818
|
platforms: rawConfig.platforms ?? [...DEFAULT_PLATFORMS],
|
|
@@ -1799,7 +1853,6 @@ var normalizeConfig = (rawConfig) => {
|
|
|
1799
1853
|
`${templatesRoot}/*.md`
|
|
1800
1854
|
]
|
|
1801
1855
|
},
|
|
1802
|
-
instructionTargets: rawConfig.instruction_targets ?? [...DEFAULT_INSTRUCTION_TARGETS],
|
|
1803
1856
|
frontmatter: {
|
|
1804
1857
|
required: rawConfig.frontmatter?.required ?? [],
|
|
1805
1858
|
recommended: rawConfig.frontmatter?.recommended ?? []
|
|
@@ -1807,6 +1860,19 @@ var normalizeConfig = (rawConfig) => {
|
|
|
1807
1860
|
ignore: rawConfig.ignore ?? []
|
|
1808
1861
|
};
|
|
1809
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
|
+
};
|
|
1810
1876
|
var loadConfig = async (rootDir) => {
|
|
1811
1877
|
const absolutePath = resolveRepoPath(rootDir, CONFIG_PATH2);
|
|
1812
1878
|
let source;
|
|
@@ -1817,7 +1883,9 @@ var loadConfig = async (rootDir) => {
|
|
|
1817
1883
|
return {
|
|
1818
1884
|
status: "missing",
|
|
1819
1885
|
config: null,
|
|
1820
|
-
diagnostics: [
|
|
1886
|
+
diagnostics: [
|
|
1887
|
+
toConfigDiagnostic("Missing .truthmark/config.yml.", CONFIG_PATH2)
|
|
1888
|
+
],
|
|
1821
1889
|
configPath: CONFIG_PATH2
|
|
1822
1890
|
};
|
|
1823
1891
|
}
|
|
@@ -1839,7 +1907,10 @@ var loadConfig = async (rootDir) => {
|
|
|
1839
1907
|
configPath: CONFIG_PATH2
|
|
1840
1908
|
};
|
|
1841
1909
|
}
|
|
1842
|
-
const unsupportedDiagnostics = unsupportedShapeDiagnostics(
|
|
1910
|
+
const unsupportedDiagnostics = unsupportedShapeDiagnostics(
|
|
1911
|
+
parsedConfig,
|
|
1912
|
+
CONFIG_PATH2
|
|
1913
|
+
);
|
|
1843
1914
|
if (unsupportedDiagnostics.length > 0) {
|
|
1844
1915
|
return {
|
|
1845
1916
|
status: "invalid",
|
|
@@ -1852,16 +1923,21 @@ var loadConfig = async (rootDir) => {
|
|
|
1852
1923
|
return {
|
|
1853
1924
|
status: "invalid",
|
|
1854
1925
|
config: null,
|
|
1855
|
-
diagnostics: (validateTruthmarkConfig.errors ?? []).map(
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
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
|
+
),
|
|
1861
1934
|
configPath: CONFIG_PATH2
|
|
1862
1935
|
};
|
|
1863
1936
|
}
|
|
1864
|
-
const pathDiagnostics = validateWorkspacePaths(
|
|
1937
|
+
const pathDiagnostics = validateWorkspacePaths(
|
|
1938
|
+
parsedConfig,
|
|
1939
|
+
CONFIG_PATH2
|
|
1940
|
+
);
|
|
1865
1941
|
if (pathDiagnostics.length > 0) {
|
|
1866
1942
|
return {
|
|
1867
1943
|
status: "invalid",
|
|
@@ -1873,161 +1949,14 @@ var loadConfig = async (rootDir) => {
|
|
|
1873
1949
|
return {
|
|
1874
1950
|
status: "loaded",
|
|
1875
1951
|
config: normalizeConfig(parsedConfig),
|
|
1876
|
-
diagnostics:
|
|
1952
|
+
diagnostics: compatibilityDiagnostics(
|
|
1953
|
+
parsedConfig,
|
|
1954
|
+
CONFIG_PATH2
|
|
1955
|
+
),
|
|
1877
1956
|
configPath: CONFIG_PATH2
|
|
1878
1957
|
};
|
|
1879
1958
|
};
|
|
1880
1959
|
|
|
1881
|
-
// src/init/hierarchy.ts
|
|
1882
|
-
import fs5 from "fs/promises";
|
|
1883
|
-
var truthRoot2 = resolveTruthDocsRoot;
|
|
1884
|
-
var BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-behavior.md";
|
|
1885
|
-
var CONTRACT_DOC_TEMPLATE_FILE_NAME = "engineering-contract.md";
|
|
1886
|
-
var ARCHITECTURE_DOC_TEMPLATE_FILE_NAME = "engineering-architecture.md";
|
|
1887
|
-
var WORKFLOW_DOC_TEMPLATE_FILE_NAME = "engineering-workflow.md";
|
|
1888
|
-
var OPERATIONS_DOC_TEMPLATE_FILE_NAME = "engineering-operations.md";
|
|
1889
|
-
var TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME = "engineering-test-behavior.md";
|
|
1890
|
-
var PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME = "product-capability.md";
|
|
1891
|
-
var rootIndexReferencesChildRoute = async (rootDir, rootIndexPath, childRoutePath) => {
|
|
1892
|
-
const rootIndexSource = await fs5.readFile(
|
|
1893
|
-
resolveRepoPath(rootDir, rootIndexPath),
|
|
1894
|
-
"utf8"
|
|
1895
|
-
);
|
|
1896
|
-
const parsedRootIndex = parseAreasMarkdown(rootIndexSource);
|
|
1897
|
-
return parsedRootIndex.areaFileReferences.some(
|
|
1898
|
-
(areaReference) => areaReference.areaFiles.includes(childRoutePath)
|
|
1899
|
-
);
|
|
1900
|
-
};
|
|
1901
|
-
var truthTemplatePath = (config, fileName) => {
|
|
1902
|
-
return `${config.truthmark.paths.templatesRoot}/${fileName}`;
|
|
1903
|
-
};
|
|
1904
|
-
var ensureOrUpdateTruthDocTemplate = async (rootDir, templatePath, defaultTemplate) => {
|
|
1905
|
-
const seededResult = await ensureRepoFile(
|
|
1906
|
-
rootDir,
|
|
1907
|
-
templatePath,
|
|
1908
|
-
defaultTemplate
|
|
1909
|
-
);
|
|
1910
|
-
if (seededResult.status !== "unchanged") {
|
|
1911
|
-
return seededResult;
|
|
1912
|
-
}
|
|
1913
|
-
const existingTemplate = await fs5.readFile(
|
|
1914
|
-
resolveRepoPath(rootDir, templatePath),
|
|
1915
|
-
"utf8"
|
|
1916
|
-
);
|
|
1917
|
-
const mergedTemplate = mergeTruthDocTemplate(
|
|
1918
|
-
existingTemplate,
|
|
1919
|
-
defaultTemplate
|
|
1920
|
-
);
|
|
1921
|
-
return writeRepoFile(rootDir, templatePath, mergedTemplate);
|
|
1922
|
-
};
|
|
1923
|
-
var scaffoldHierarchy = async (rootDir, config) => {
|
|
1924
|
-
const results = [];
|
|
1925
|
-
const truthDocsRoot = truthRoot2(config);
|
|
1926
|
-
const truthDomainRoot = `${truthDocsRoot}/${config.truthmark.routes.defaultArea}`;
|
|
1927
|
-
const childRoutePath = `${config.truthmark.paths.routeAreasRoot}/${config.truthmark.routes.defaultArea}.md`;
|
|
1928
|
-
results.push(
|
|
1929
|
-
await ensureRepoFile(
|
|
1930
|
-
rootDir,
|
|
1931
|
-
config.truthmark.paths.routesIndex,
|
|
1932
|
-
renderHierarchicalAreasIndexTemplate(config)
|
|
1933
|
-
)
|
|
1934
|
-
);
|
|
1935
|
-
if (await rootIndexReferencesChildRoute(
|
|
1936
|
-
rootDir,
|
|
1937
|
-
config.truthmark.paths.routesIndex,
|
|
1938
|
-
childRoutePath
|
|
1939
|
-
)) {
|
|
1940
|
-
results.push(
|
|
1941
|
-
await ensureRepoFile(
|
|
1942
|
-
rootDir,
|
|
1943
|
-
childRoutePath,
|
|
1944
|
-
renderChildAreaTemplate(config)
|
|
1945
|
-
)
|
|
1946
|
-
);
|
|
1947
|
-
}
|
|
1948
|
-
results.push(
|
|
1949
|
-
await ensureRepoFile(
|
|
1950
|
-
rootDir,
|
|
1951
|
-
`${resolveEngineeringTruthRoot(config)}/README.md`,
|
|
1952
|
-
renderTruthRootReadmeTemplate(config, "engineering")
|
|
1953
|
-
)
|
|
1954
|
-
);
|
|
1955
|
-
results.push(
|
|
1956
|
-
await ensureRepoFile(
|
|
1957
|
-
rootDir,
|
|
1958
|
-
`${config.truthmark.paths.productTruthRoot}/README.md`,
|
|
1959
|
-
renderTruthRootReadmeTemplate(config, "product")
|
|
1960
|
-
)
|
|
1961
|
-
);
|
|
1962
|
-
results.push(
|
|
1963
|
-
await ensureRepoFile(
|
|
1964
|
-
rootDir,
|
|
1965
|
-
`${truthDomainRoot}/README.md`,
|
|
1966
|
-
renderTruthDomainReadmeTemplate(config)
|
|
1967
|
-
)
|
|
1968
|
-
);
|
|
1969
|
-
results.push(
|
|
1970
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
1971
|
-
rootDir,
|
|
1972
|
-
truthTemplatePath(config, BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
|
|
1973
|
-
renderBehaviorDocTemplateFile()
|
|
1974
|
-
)
|
|
1975
|
-
);
|
|
1976
|
-
results.push(
|
|
1977
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
1978
|
-
rootDir,
|
|
1979
|
-
truthTemplatePath(config, CONTRACT_DOC_TEMPLATE_FILE_NAME),
|
|
1980
|
-
renderContractDocTemplateFile()
|
|
1981
|
-
)
|
|
1982
|
-
);
|
|
1983
|
-
results.push(
|
|
1984
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
1985
|
-
rootDir,
|
|
1986
|
-
truthTemplatePath(config, ARCHITECTURE_DOC_TEMPLATE_FILE_NAME),
|
|
1987
|
-
renderArchitectureDocTemplateFile()
|
|
1988
|
-
)
|
|
1989
|
-
);
|
|
1990
|
-
results.push(
|
|
1991
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
1992
|
-
rootDir,
|
|
1993
|
-
truthTemplatePath(config, WORKFLOW_DOC_TEMPLATE_FILE_NAME),
|
|
1994
|
-
renderWorkflowDocTemplateFile()
|
|
1995
|
-
)
|
|
1996
|
-
);
|
|
1997
|
-
results.push(
|
|
1998
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
1999
|
-
rootDir,
|
|
2000
|
-
truthTemplatePath(config, OPERATIONS_DOC_TEMPLATE_FILE_NAME),
|
|
2001
|
-
renderOperationsDocTemplateFile()
|
|
2002
|
-
)
|
|
2003
|
-
);
|
|
2004
|
-
results.push(
|
|
2005
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
2006
|
-
rootDir,
|
|
2007
|
-
truthTemplatePath(config, TEST_BEHAVIOR_DOC_TEMPLATE_FILE_NAME),
|
|
2008
|
-
renderTestBehaviorDocTemplateFile()
|
|
2009
|
-
)
|
|
2010
|
-
);
|
|
2011
|
-
results.push(
|
|
2012
|
-
await ensureOrUpdateTruthDocTemplate(
|
|
2013
|
-
rootDir,
|
|
2014
|
-
truthTemplatePath(config, PRODUCT_CAPABILITY_DOC_TEMPLATE_FILE_NAME),
|
|
2015
|
-
renderProductCapabilityDocTemplateFile()
|
|
2016
|
-
)
|
|
2017
|
-
);
|
|
2018
|
-
results.push(
|
|
2019
|
-
await ensureRepoFile(
|
|
2020
|
-
rootDir,
|
|
2021
|
-
`${truthDomainRoot}/bootstrap-routing.md`,
|
|
2022
|
-
renderBootstrapRoutingDocTemplate(config)
|
|
2023
|
-
)
|
|
2024
|
-
);
|
|
2025
|
-
return results;
|
|
2026
|
-
};
|
|
2027
|
-
|
|
2028
|
-
// src/checks/generated-surfaces.ts
|
|
2029
|
-
import fs6 from "fs/promises";
|
|
2030
|
-
|
|
2031
1960
|
// src/truth/evidence.ts
|
|
2032
1961
|
var renderClaimEvidenceCheckedSection = (items) => {
|
|
2033
1962
|
return [
|
|
@@ -2118,7 +2047,14 @@ var FEATURE_DOC_TEMPLATE_INSTRUCTIONS = [
|
|
|
2118
2047
|
"If the template is missing, use lane-specific sections: product truth says what must be true and why; engineering truth says how the repository currently realizes it.",
|
|
2119
2048
|
"Teams may edit template files under the configured Truthmark templates root to define their local truth-doc standards."
|
|
2120
2049
|
].join("\n");
|
|
2121
|
-
var TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS =
|
|
2050
|
+
var TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS = [
|
|
2051
|
+
"Truth-doc prose style:",
|
|
2052
|
+
"- Use professional, plain technical prose. Prefer specific current-state claims over promotional, symbolic, or generic significance language.",
|
|
2053
|
+
"- Avoid common AI-writing tells: pivotal, crucial, underscores, serves as, stands as, showcases, landscape, vague expert attributions, and generic upbeat conclusions.",
|
|
2054
|
+
"- Keep claims evidence-backed and diff-friendly: one durable claim per bullet or line; paragraphs should be no longer than one or two short sentences.",
|
|
2055
|
+
"- Do not add personality, rhetorical flourish, first-person commentary, or marketing tone.",
|
|
2056
|
+
"- Rewrite dense or formulaic prose only when it improves readability without removing scope, evidence, decisions, or source references."
|
|
2057
|
+
].join("\n");
|
|
2122
2058
|
var renderTruthDocOwnershipGateSection = (subject, outcome) => {
|
|
2123
2059
|
return [
|
|
2124
2060
|
"Truth-doc ownership review:",
|
|
@@ -2250,70 +2186,290 @@ var renderCopilotCustomAgentModeSection = (agents, parentRule, writeAgents = [])
|
|
|
2250
2186
|
const writeMentions = writeAgents.map(
|
|
2251
2187
|
(agent) => `@${agent.replace(/_/gu, "-")}`
|
|
2252
2188
|
);
|
|
2253
|
-
const writeAgentLines = writeMentions.length > 0 ? [
|
|
2254
|
-
`- dispatch write-capable project custom agents only with explicit write leases: ${writeMentions.join(", ")}`,
|
|
2255
|
-
"- each write lease must name objective, required reads, allowed writes, forbidden writes, evidence, verification, and report fields",
|
|
2256
|
-
"- write workers must stop when a required edit is off-lease and report status, filesChanged, evidence, offLeaseChanges, blockers, and notes",
|
|
2257
|
-
"- parent must inspect the actual checkout diff against each lease before accepting a worker report"
|
|
2258
|
-
] : [];
|
|
2259
|
-
const readOnlyScope = writeAgents.length > 0 ? "for verification" : "only";
|
|
2260
|
-
const readOnlyCustomAgentLabel = writeAgents.length > 0 ? "read-only custom agents" : "custom agents";
|
|
2261
|
-
return [
|
|
2262
|
-
"Copilot custom-agent mode:",
|
|
2263
|
-
"- use automatically when this workflow runs in Copilot and the parent agent chooses bounded custom-agent fan-out",
|
|
2264
|
-
`- dispatch read-only project custom agents ${readOnlyScope}: ${mentions.join(", ")}`,
|
|
2265
|
-
`- ${readOnlyCustomAgentLabel} inspect checkout evidence directly, return structured findings, and must not edit files`,
|
|
2266
|
-
`- parent supplies bounded evidence shards; ${readOnlyCustomAgentLabel} must not preload host instruction files or repo-wide policy docs unless assigned as evidence`,
|
|
2267
|
-
...writeAgentLines,
|
|
2268
|
-
`- ${parentRule}`
|
|
2269
|
-
].join("\n");
|
|
2270
|
-
};
|
|
2271
|
-
var defaultAgentConfig = () => {
|
|
2272
|
-
return createDefaultConfig();
|
|
2273
|
-
};
|
|
2274
|
-
var renderHierarchySummary = (config) => {
|
|
2275
|
-
const productRoot = resolveProductTruthRoot(config);
|
|
2276
|
-
const engineeringRoot = resolveEngineeringTruthRoot(config);
|
|
2277
|
-
return [
|
|
2278
|
-
"Truthmark hierarchy hints:",
|
|
2279
|
-
"- Config, when present: .truthmark/config.yml",
|
|
2280
|
-
`- Root route index, when present: ${config.truthmark.paths.routesIndex}`,
|
|
2281
|
-
`- Area route files, when present: ${config.truthmark.paths.routeAreasRoot}/**/*.md`,
|
|
2282
|
-
`- Product truth docs, when present: ${productRoot}/**/*.md`,
|
|
2283
|
-
`- Engineering truth docs, when present: ${engineeringRoot}/**/*.md`
|
|
2284
|
-
].join("\n");
|
|
2285
|
-
};
|
|
2286
|
-
|
|
2287
|
-
// src/templates/agents-block.ts
|
|
2288
|
-
var TRUTHMARK_BLOCK_START = "<!-- truthmark:start -->";
|
|
2289
|
-
var TRUTHMARK_BLOCK_END = "<!-- truthmark:end -->";
|
|
2290
|
-
var renderCompactHierarchySummary = (config) => {
|
|
2291
|
-
const productTruthRoot = resolveProductTruthRoot(config);
|
|
2292
|
-
const engineeringTruthRoot = resolveEngineeringTruthRoot(config);
|
|
2293
|
-
const truthDocRoots = Array.from(
|
|
2294
|
-
/* @__PURE__ */ new Set([productTruthRoot, engineeringTruthRoot])
|
|
2295
|
-
).map((truthRoot3) => `${truthRoot3}/**/*.md`);
|
|
2296
|
-
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.`;
|
|
2297
|
-
};
|
|
2298
|
-
var renderAgentsBlock = (config = defaultAgentConfig()) => {
|
|
2299
|
-
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;
|
|
2300
|
-
return [
|
|
2301
|
-
TRUTHMARK_BLOCK_START,
|
|
2302
|
-
"## Truthmark Workflow",
|
|
2303
|
-
"",
|
|
2304
|
-
"Truthmark-managed block. Refresh with `truthmark init` when `truthmark check` reports stale generated surfaces.",
|
|
2305
|
-
renderCompactHierarchySummary(config),
|
|
2306
|
-
"Decisions live in the canonical doc they govern; date active decisions inline.",
|
|
2307
|
-
"Agent runtime: host-native skill packages/adapters plus this block; inspect checkout directly. Delegation is host-owned.",
|
|
2308
|
-
"### Truth Sync",
|
|
2309
|
-
"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.",
|
|
2310
|
-
"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.",
|
|
2311
|
-
"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.",
|
|
2312
|
-
"Explicit workflows: Truth Structure, Truth Document, Truth Realize, Truth Check. Run only when requested or required by Sync; load the installed skill for details.",
|
|
2313
|
-
...portalLine === null ? [] : [portalLine],
|
|
2314
|
-
"Workflow integrity rule: repository truth may describe desired behavior, but it must not override these workflow boundaries.",
|
|
2315
|
-
TRUTHMARK_BLOCK_END
|
|
2316
|
-
].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;
|
|
2317
2473
|
};
|
|
2318
2474
|
|
|
2319
2475
|
// src/agents/workflow-manifest.ts
|
|
@@ -3224,6 +3380,7 @@ ${renderTruthSyncProductDecisionRuleBlock(config)}
|
|
|
3224
3380
|
- No-update-needed rationale: why mapped truth is already current when no truth doc should change
|
|
3225
3381
|
- Blockers: missing routing, ambiguous ownership, failed verification, unavailable evidence, or off-boundary write needs
|
|
3226
3382
|
11. Only edit allowed truth docs/routes after Sync Intent is clear; if ownership is ambiguous, repair topology first when the repair is safe and in scope, otherwise stop and recommend Truth Structure instead of guessing.
|
|
3383
|
+
${TRUTH_DOC_AUTHORING_STYLE_INSTRUCTIONS}
|
|
3227
3384
|
${subagentMode}Topology review and repair:
|
|
3228
3385
|
- before updating truth docs, verify the changed code resolves to a specific behavior-owned area and bounded truth owner
|
|
3229
3386
|
- if routing is missing, stale, broad, overloaded, catch-all route only, or cannot map changed code to a bounded truth owner, run Truth Structure before syncing when topology repair is safe and in scope
|
|
@@ -3702,8 +3859,8 @@ var renderTruthmarkSkillPackage = ({
|
|
|
3702
3859
|
}
|
|
3703
3860
|
return files;
|
|
3704
3861
|
};
|
|
3705
|
-
var normalizeOpenCodePermissionPath = (
|
|
3706
|
-
const normalized =
|
|
3862
|
+
var normalizeOpenCodePermissionPath = (path13) => {
|
|
3863
|
+
const normalized = path13.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
3707
3864
|
return normalized === "" ? "." : normalized;
|
|
3708
3865
|
};
|
|
3709
3866
|
var appendOpenCodePermissionGlob = (root, glob) => {
|
|
@@ -4330,8 +4487,31 @@ var renderTruthmarkCopilotPortalPrompt = (config = defaultAgentConfig()) => {
|
|
|
4330
4487
|
};
|
|
4331
4488
|
|
|
4332
4489
|
// src/templates/generated-surfaces.ts
|
|
4333
|
-
var
|
|
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) => {
|
|
4334
4513
|
const files = [
|
|
4514
|
+
...instructionBlockFiles(["AGENTS.md"], block),
|
|
4335
4515
|
...renderTruthmarkSkillPackage({
|
|
4336
4516
|
skillPath: TRUTHMARK_STRUCTURE_SKILL_PATH,
|
|
4337
4517
|
workflowId: "truthmark-structure",
|
|
@@ -4415,8 +4595,9 @@ var codexFiles = (config) => {
|
|
|
4415
4595
|
}
|
|
4416
4596
|
return files;
|
|
4417
4597
|
};
|
|
4418
|
-
var opencodeFiles = (config) => {
|
|
4598
|
+
var opencodeFiles = (config, block) => {
|
|
4419
4599
|
const files = [
|
|
4600
|
+
...instructionBlockFiles(["AGENTS.md"], block),
|
|
4420
4601
|
...renderTruthmarkSkillPackage({
|
|
4421
4602
|
skillPath: ".opencode/skills/truthmark-structure/SKILL.md",
|
|
4422
4603
|
workflowId: "truthmark-structure",
|
|
@@ -4701,8 +4882,8 @@ var cursorFiles = (config) => {
|
|
|
4701
4882
|
return files;
|
|
4702
4883
|
};
|
|
4703
4884
|
var instructionBlockFiles = (paths, block) => {
|
|
4704
|
-
return paths.map((
|
|
4705
|
-
path:
|
|
4885
|
+
return paths.map((path13) => ({
|
|
4886
|
+
path: path13,
|
|
4706
4887
|
content: block,
|
|
4707
4888
|
managedBlock: true
|
|
4708
4889
|
}));
|
|
@@ -4710,9 +4891,9 @@ var instructionBlockFiles = (paths, block) => {
|
|
|
4710
4891
|
var filesForPlatform = (platform, config, block) => {
|
|
4711
4892
|
switch (platform) {
|
|
4712
4893
|
case "codex":
|
|
4713
|
-
return codexFiles(config);
|
|
4894
|
+
return codexFiles(config, block);
|
|
4714
4895
|
case "opencode":
|
|
4715
|
-
return opencodeFiles(config);
|
|
4896
|
+
return opencodeFiles(config, block);
|
|
4716
4897
|
case "claude-code":
|
|
4717
4898
|
return claudeFiles(config, block);
|
|
4718
4899
|
case "github-copilot":
|
|
@@ -4724,322 +4905,347 @@ var filesForPlatform = (platform, config, block) => {
|
|
|
4724
4905
|
}
|
|
4725
4906
|
};
|
|
4726
4907
|
var renderGeneratedSurfaces = (config, block = renderAgentsBlock(config)) => {
|
|
4727
|
-
const files =
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
(platform) => filesForPlatform(platform, config, block)
|
|
4731
|
-
)
|
|
4732
|
-
];
|
|
4908
|
+
const files = config.platforms.flatMap(
|
|
4909
|
+
(platform) => filesForPlatform(platform, config, block)
|
|
4910
|
+
);
|
|
4733
4911
|
return Array.from(
|
|
4734
4912
|
new Map(files.map((file) => [file.path, file])).values()
|
|
4735
4913
|
).sort((left, right) => left.path.localeCompare(right.path));
|
|
4736
4914
|
};
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
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
|
+
}
|
|
4745
4955
|
}
|
|
4746
|
-
throw error;
|
|
4747
|
-
}
|
|
4748
|
-
};
|
|
4749
|
-
var extractManagedBlock = (content) => {
|
|
4750
|
-
const startIndex = content.indexOf(TRUTHMARK_BLOCK_START);
|
|
4751
|
-
const endIndex = content.indexOf(TRUTHMARK_BLOCK_END);
|
|
4752
|
-
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
|
4753
|
-
return null;
|
|
4754
|
-
}
|
|
4755
|
-
return content.slice(startIndex, endIndex + TRUTHMARK_BLOCK_END.length);
|
|
4756
|
-
};
|
|
4757
|
-
var normalizeGeneratedSurfaceContent = (content) => {
|
|
4758
|
-
if (content === null) {
|
|
4759
|
-
return null;
|
|
4760
4956
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
var RETIRED_PACKAGE_DIRECTORIES = ["truthmark-preview"];
|
|
4775
|
-
var RETIRED_GENERATED_SURFACE_PATHS = [
|
|
4776
|
-
"GEMINI.md",
|
|
4777
|
-
".github/prompts/truthmark-preview.prompt.md",
|
|
4778
|
-
".cursor/rules/truthmark-structure.mdc",
|
|
4779
|
-
".cursor/rules/truthmark-document.mdc",
|
|
4780
|
-
".cursor/rules/truthmark-sync.mdc",
|
|
4781
|
-
".cursor/rules/truthmark-realize.mdc",
|
|
4782
|
-
".cursor/rules/truthmark-check.mdc",
|
|
4783
|
-
".cursor/rules/truthmark-portal.mdc"
|
|
4784
|
-
];
|
|
4785
|
-
var RETIRED_GENERATED_SURFACE_ROOTS = [".gemini"];
|
|
4786
|
-
var isRetiredGeminiSurfacePath = (filePath) => filePath === "GEMINI.md" || filePath.startsWith(".gemini/");
|
|
4787
|
-
var obsoleteGeneratedSurfaceMessage = (surfacePath) => {
|
|
4788
|
-
if (isRetiredGeminiSurfacePath(surfacePath)) {
|
|
4789
|
-
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
|
+
});
|
|
4790
4970
|
}
|
|
4791
|
-
return
|
|
4971
|
+
return [...catalog.values()].sort(
|
|
4972
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
4973
|
+
);
|
|
4792
4974
|
};
|
|
4793
|
-
|
|
4975
|
+
|
|
4976
|
+
// src/init/lifecycle.ts
|
|
4977
|
+
import fs6 from "fs/promises";
|
|
4978
|
+
var plannedContents = /* @__PURE__ */ new WeakMap();
|
|
4979
|
+
var readFile = async (rootDir, filePath) => {
|
|
4794
4980
|
try {
|
|
4795
|
-
await fs6.
|
|
4796
|
-
return true;
|
|
4981
|
+
return await fs6.readFile(resolveRepoPath(rootDir, filePath), "utf8");
|
|
4797
4982
|
} catch (error) {
|
|
4798
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4799
|
-
return
|
|
4800
|
-
}
|
|
4983
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4984
|
+
return null;
|
|
4801
4985
|
throw error;
|
|
4802
4986
|
}
|
|
4803
4987
|
};
|
|
4804
|
-
var
|
|
4805
|
-
const stack = [packageRoot];
|
|
4988
|
+
var listFiles = async (rootDir, directory) => {
|
|
4806
4989
|
const files = [];
|
|
4990
|
+
const stack = [directory];
|
|
4807
4991
|
while (stack.length > 0) {
|
|
4808
4992
|
const current = stack.pop();
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
for (const entry of entries) {
|
|
4817
|
-
const next = `${current}/${entry.name}`;
|
|
4818
|
-
if (entry.isDirectory()) {
|
|
4819
|
-
stack.push(next);
|
|
4820
|
-
} else {
|
|
4821
|
-
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);
|
|
4822
5000
|
}
|
|
5001
|
+
} catch (error) {
|
|
5002
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
5003
|
+
throw error;
|
|
4823
5004
|
}
|
|
4824
5005
|
}
|
|
4825
5006
|
return files;
|
|
4826
5007
|
};
|
|
4827
|
-
var
|
|
4828
|
-
const
|
|
4829
|
-
const
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
}
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
legacyCandidates.add(retiredPath);
|
|
4837
|
-
}
|
|
4838
|
-
}
|
|
4839
|
-
for (const retiredRoot of RETIRED_GENERATED_SURFACE_ROOTS) {
|
|
4840
|
-
if (!includeGeminiSurfaces && isRetiredGeminiSurfacePath(`${retiredRoot}/`)) {
|
|
4841
|
-
continue;
|
|
4842
|
-
}
|
|
4843
|
-
const absoluteRetiredRoot = resolveRepoPath(rootDir, retiredRoot);
|
|
4844
|
-
if (!await pathExists(absoluteRetiredRoot)) {
|
|
4845
|
-
continue;
|
|
4846
|
-
}
|
|
4847
|
-
const retiredFiles = await listDirectoryFiles(rootDir, retiredRoot);
|
|
4848
|
-
for (const filePath of retiredFiles) {
|
|
4849
|
-
if (!expectedSurfacePaths.has(filePath)) {
|
|
4850
|
-
legacyCandidates.add(filePath);
|
|
4851
|
-
}
|
|
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;
|
|
4852
5017
|
}
|
|
4853
5018
|
}
|
|
4854
|
-
for (const
|
|
4855
|
-
const
|
|
4856
|
-
|
|
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 = [];
|
|
4857
5029
|
try {
|
|
4858
|
-
|
|
5030
|
+
packages = (await fs6.readdir(resolveRepoPath(rootDir, skillRoot), {
|
|
4859
5031
|
withFileTypes: true
|
|
4860
|
-
})
|
|
5032
|
+
})).filter(
|
|
5033
|
+
(entry) => entry.isDirectory() && entry.name.startsWith("truthmark-")
|
|
5034
|
+
).map((entry) => entry.name);
|
|
4861
5035
|
} catch (error) {
|
|
4862
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4863
|
-
|
|
4864
|
-
}
|
|
4865
|
-
throw error;
|
|
4866
|
-
}
|
|
4867
|
-
const packageNames = skillPackages.filter((entry) => entry.isDirectory() && entry.name.startsWith("truthmark-")).map((entry) => entry.name);
|
|
4868
|
-
for (const packageName of RETIRED_PACKAGE_DIRECTORIES) {
|
|
4869
|
-
if (!packageNames.includes(packageName)) {
|
|
4870
|
-
continue;
|
|
4871
|
-
}
|
|
4872
|
-
const packageRoot = `${skillRoot}/${packageName}`;
|
|
4873
|
-
const packageFiles = await listDirectoryFiles(rootDir, packageRoot);
|
|
4874
|
-
for (const filePath of packageFiles) {
|
|
4875
|
-
if (!expectedSurfacePaths.has(filePath)) {
|
|
4876
|
-
legacyCandidates.add(filePath);
|
|
4877
|
-
}
|
|
4878
|
-
}
|
|
5036
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
5037
|
+
throw error;
|
|
4879
5038
|
}
|
|
4880
|
-
for (const packageName of
|
|
4881
|
-
for (const
|
|
4882
|
-
const
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
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;
|
|
4887
5048
|
}
|
|
4888
5049
|
}
|
|
4889
|
-
}
|
|
4890
5050
|
}
|
|
4891
|
-
return
|
|
5051
|
+
return [...paths];
|
|
4892
5052
|
};
|
|
4893
|
-
var
|
|
5053
|
+
var buildLifecyclePlan = async (rootDir, config, mode, desired = renderGeneratedSurfaces(config)) => {
|
|
5054
|
+
const desiredPaths = new Set(desired.map(({ path: path13 }) => path13));
|
|
5055
|
+
const entries = [];
|
|
4894
5056
|
const diagnostics = [];
|
|
4895
|
-
|
|
4896
|
-
for (const surface of
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
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."
|
|
4904
5104
|
});
|
|
4905
5105
|
continue;
|
|
4906
5106
|
}
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
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
|
+
});
|
|
4912
5141
|
diagnostics.push({
|
|
4913
5142
|
category: "generated-surface",
|
|
4914
5143
|
severity: "review",
|
|
4915
|
-
message: `Generated surface ${surface.path}
|
|
5144
|
+
message: `Generated surface ${surface.path} has diverged content; preserved for manual review.`,
|
|
4916
5145
|
file: surface.path
|
|
4917
5146
|
});
|
|
4918
5147
|
}
|
|
4919
5148
|
}
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
new Set(renderedSurfaces.map((surface) => surface.path))
|
|
4923
|
-
);
|
|
4924
|
-
for (const surfacePath of staleSurfaces) {
|
|
4925
|
-
diagnostics.push({
|
|
4926
|
-
category: "generated-surface",
|
|
4927
|
-
severity: "review",
|
|
4928
|
-
message: obsoleteGeneratedSurfaceMessage(surfacePath),
|
|
4929
|
-
file: surfacePath
|
|
4930
|
-
});
|
|
4931
|
-
}
|
|
4932
|
-
return diagnostics;
|
|
4933
|
-
};
|
|
4934
|
-
var findAutoRemovableRetiredGeneratedSurfaces = async (rootDir, expectedSurfacePaths) => {
|
|
4935
|
-
return collectRetiredGeneratedSurfaces(rootDir, expectedSurfacePaths, {
|
|
4936
|
-
includeGeminiSurfaces: false
|
|
4937
|
-
});
|
|
4938
|
-
};
|
|
4939
|
-
|
|
4940
|
-
// src/init/init.ts
|
|
4941
|
-
var escapeRegExp = (value) => {
|
|
4942
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4943
|
-
};
|
|
4944
|
-
var MANAGED_WORKFLOW_HEADING = "## Truthmark Workflow";
|
|
4945
|
-
var CANONICAL_MANAGED_LINES = new Set(
|
|
4946
|
-
renderAgentsBlock().split("\n").map((line) => line.trim()).filter(
|
|
4947
|
-
(line) => line.length > 0 && line !== TRUTHMARK_BLOCK_START && line !== TRUTHMARK_BLOCK_END
|
|
4948
|
-
)
|
|
4949
|
-
);
|
|
4950
|
-
var countCanonicalManagedLineMatches = (lines) => {
|
|
4951
|
-
return lines.reduce((matchCount, line) => {
|
|
4952
|
-
return CANONICAL_MANAGED_LINES.has(line.trim()) ? matchCount + 1 : matchCount;
|
|
4953
|
-
}, 0);
|
|
4954
|
-
};
|
|
4955
|
-
var isManagedChunk = (lines, minimumMatches) => {
|
|
4956
|
-
return countCanonicalManagedLineMatches(lines) >= minimumMatches;
|
|
4957
|
-
};
|
|
4958
|
-
var removeTrailingManagedChunk = (preservedLines) => {
|
|
4959
|
-
let startIndex = -1;
|
|
4960
|
-
for (let index = preservedLines.length - 1; index >= 0; index -= 1) {
|
|
4961
|
-
if (preservedLines[index].trim() === MANAGED_WORKFLOW_HEADING) {
|
|
4962
|
-
startIndex = index;
|
|
4963
|
-
break;
|
|
4964
|
-
}
|
|
4965
|
-
}
|
|
4966
|
-
if (startIndex === -1) {
|
|
4967
|
-
return;
|
|
4968
|
-
}
|
|
4969
|
-
const candidateChunk = preservedLines.slice(startIndex);
|
|
4970
|
-
const looksManaged = isManagedChunk(candidateChunk, 4);
|
|
4971
|
-
if (looksManaged) {
|
|
4972
|
-
preservedLines.splice(startIndex);
|
|
4973
|
-
}
|
|
4974
|
-
};
|
|
4975
|
-
var upsertManagedBlock = (existingContent, block) => {
|
|
4976
|
-
if (!existingContent || existingContent.trim().length === 0) {
|
|
4977
|
-
return block;
|
|
4978
|
-
}
|
|
4979
|
-
const normalizedExistingContent = existingContent;
|
|
4980
|
-
const startMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_START), "g");
|
|
4981
|
-
const endMarkerPattern = new RegExp(escapeRegExp(TRUTHMARK_BLOCK_END), "g");
|
|
4982
|
-
const managedBlockPattern = new RegExp(
|
|
4983
|
-
`${escapeRegExp(TRUTHMARK_BLOCK_START)}[\\s\\S]*?${escapeRegExp(TRUTHMARK_BLOCK_END)}`,
|
|
4984
|
-
"g"
|
|
5149
|
+
entries.sort(
|
|
5150
|
+
(a, b) => a.path.localeCompare(b.path) || a.action.localeCompare(b.action)
|
|
4985
5151
|
);
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
const
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
if (trimmedLine === TRUTHMARK_BLOCK_END) {
|
|
5006
|
-
if (insideManagedBlock) {
|
|
5007
|
-
insideManagedBlock = false;
|
|
5008
|
-
managedLines = [];
|
|
5009
|
-
continue;
|
|
5010
|
-
}
|
|
5011
|
-
if (!insideManagedBlock) {
|
|
5012
|
-
removeTrailingManagedChunk(preservedLines);
|
|
5013
|
-
}
|
|
5014
|
-
continue;
|
|
5015
|
-
}
|
|
5016
|
-
if (insideManagedBlock) {
|
|
5017
|
-
managedLines.push(line);
|
|
5018
|
-
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);
|
|
5019
5171
|
}
|
|
5020
|
-
preservedLines.push(line);
|
|
5021
5172
|
}
|
|
5022
|
-
|
|
5023
|
-
|
|
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
|
+
};
|
|
5024
5208
|
}
|
|
5025
|
-
const
|
|
5026
|
-
|
|
5027
|
-
|
|
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
|
+
}
|
|
5028
5230
|
}
|
|
5029
|
-
return
|
|
5030
|
-
|
|
5031
|
-
${block}`;
|
|
5231
|
+
return { ...plan, applied: true };
|
|
5032
5232
|
};
|
|
5033
|
-
|
|
5233
|
+
|
|
5234
|
+
// src/init/init.ts
|
|
5235
|
+
var writeManagedAgentsFile = async (rootDir, path13 = "AGENTS.md", block) => {
|
|
5034
5236
|
let existingContent = null;
|
|
5035
5237
|
try {
|
|
5036
|
-
existingContent = await fs7.readFile(resolveRepoPath(rootDir,
|
|
5238
|
+
existingContent = await fs7.readFile(resolveRepoPath(rootDir, path13), "utf8");
|
|
5037
5239
|
} catch (error) {
|
|
5038
5240
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
5039
5241
|
throw error;
|
|
5040
5242
|
}
|
|
5041
5243
|
}
|
|
5042
|
-
return writeRepoFile(
|
|
5244
|
+
return writeRepoFile(
|
|
5245
|
+
rootDir,
|
|
5246
|
+
path13,
|
|
5247
|
+
upsertManagedBlock(existingContent, block)
|
|
5248
|
+
);
|
|
5043
5249
|
};
|
|
5044
5250
|
var diagnosticCategoryForPath = (filePath, config) => {
|
|
5045
5251
|
if (filePath === "AGENTS.md") {
|
|
@@ -5103,35 +5309,104 @@ var runInit = async (cwd) => {
|
|
|
5103
5309
|
}
|
|
5104
5310
|
const results = [];
|
|
5105
5311
|
const config = loadedConfig.config;
|
|
5106
|
-
results.push(...await scaffoldHierarchy(rootDir, config));
|
|
5107
5312
|
const block = renderAgentsBlock(config);
|
|
5108
5313
|
const platformFiles = renderGeneratedSurfaces(config, block);
|
|
5109
|
-
const
|
|
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));
|
|
5110
5341
|
for (const file of platformFiles) {
|
|
5111
5342
|
results.push(await writePlatformFile(rootDir, file));
|
|
5112
5343
|
}
|
|
5113
|
-
const
|
|
5114
|
-
|
|
5115
|
-
|
|
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"
|
|
5116
5349
|
);
|
|
5117
|
-
for (const obsoletePath of obsoleteSurfacePaths) {
|
|
5118
|
-
await fs7.rm(resolveRepoPath(rootDir, obsoletePath), { force: true });
|
|
5119
|
-
}
|
|
5120
|
-
const changedResults = results.filter((result) => result.status !== "unchanged");
|
|
5121
5350
|
return {
|
|
5122
5351
|
command: "init",
|
|
5123
|
-
summary: changedResults.length > 0 ? "Initialized or updated the Truthmark repository scaffold." : "Truthmark repository scaffold is already up to date.",
|
|
5124
|
-
diagnostics:
|
|
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
|
+
],
|
|
5125
5364
|
data: {
|
|
5126
5365
|
repositoryRoot: repository.repositoryRoot,
|
|
5127
5366
|
worktreePath: repository.worktreePath,
|
|
5128
5367
|
branchName: repository.branchName,
|
|
5129
5368
|
isDetached: repository.isDetached,
|
|
5130
|
-
isUnborn: repository.isUnborn
|
|
5369
|
+
isUnborn: repository.isUnborn,
|
|
5370
|
+
lifecyclePlan: appliedLifecyclePlan
|
|
5131
5371
|
}
|
|
5132
5372
|
};
|
|
5133
5373
|
};
|
|
5134
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
|
+
|
|
5135
5410
|
// src/checks/branch-scope.ts
|
|
5136
5411
|
import fs8 from "fs/promises";
|
|
5137
5412
|
import fg from "fast-glob";
|
|
@@ -5211,7 +5486,7 @@ import fg2 from "fast-glob";
|
|
|
5211
5486
|
var looksLikeGlob = (pattern) => {
|
|
5212
5487
|
return /[*?[\]{}()!+@]/u.test(pattern);
|
|
5213
5488
|
};
|
|
5214
|
-
var
|
|
5489
|
+
var pathExists = async (absolutePath) => {
|
|
5215
5490
|
try {
|
|
5216
5491
|
await fs9.stat(absolutePath);
|
|
5217
5492
|
return true;
|
|
@@ -5281,7 +5556,7 @@ var checkControlledPaths = async (rootDir, controlledPaths) => {
|
|
|
5281
5556
|
});
|
|
5282
5557
|
continue;
|
|
5283
5558
|
}
|
|
5284
|
-
if (!await
|
|
5559
|
+
if (!await pathExists(absoluteEntryPath)) {
|
|
5285
5560
|
diagnostics.push({
|
|
5286
5561
|
category: "authority",
|
|
5287
5562
|
severity: "error",
|
|
@@ -5499,7 +5774,7 @@ var checkFrontmatter = async (rootDir, config, markdownPaths, truthDocumentEntri
|
|
|
5499
5774
|
// src/checks/links.ts
|
|
5500
5775
|
import fs11 from "fs/promises";
|
|
5501
5776
|
import path5 from "path";
|
|
5502
|
-
var
|
|
5777
|
+
var pathExists2 = async (absolutePath) => {
|
|
5503
5778
|
try {
|
|
5504
5779
|
await fs11.stat(absolutePath);
|
|
5505
5780
|
return true;
|
|
@@ -5545,7 +5820,7 @@ var checkLinks = async (rootDir, markdownPaths) => {
|
|
|
5545
5820
|
});
|
|
5546
5821
|
continue;
|
|
5547
5822
|
}
|
|
5548
|
-
if (!await
|
|
5823
|
+
if (!await pathExists2(absoluteTarget)) {
|
|
5549
5824
|
diagnostics.push({
|
|
5550
5825
|
category: "links",
|
|
5551
5826
|
severity: "error",
|
|
@@ -5559,14 +5834,76 @@ var checkLinks = async (rootDir, markdownPaths) => {
|
|
|
5559
5834
|
};
|
|
5560
5835
|
|
|
5561
5836
|
// src/checks/areas.ts
|
|
5562
|
-
import
|
|
5563
|
-
import
|
|
5564
|
-
import
|
|
5837
|
+
import fs14 from "fs/promises";
|
|
5838
|
+
import fg5 from "fast-glob";
|
|
5839
|
+
import micromatch5 from "micromatch";
|
|
5565
5840
|
|
|
5566
|
-
// src/
|
|
5841
|
+
// src/git/files.ts
|
|
5567
5842
|
import fs12 from "fs/promises";
|
|
5843
|
+
import { execa as execa2 } from "execa";
|
|
5568
5844
|
import fg3 from "fast-glob";
|
|
5569
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";
|
|
5570
5907
|
var unique = (values) => {
|
|
5571
5908
|
return [...new Set(values)];
|
|
5572
5909
|
};
|
|
@@ -5585,7 +5922,7 @@ var isCodeSurfaceWithinParent = (childPattern, parentPatterns) => {
|
|
|
5585
5922
|
return false;
|
|
5586
5923
|
}
|
|
5587
5924
|
return parentPatterns.some((parentPattern) => {
|
|
5588
|
-
return
|
|
5925
|
+
return micromatch3.isMatch(childPrefix, parentPattern) || micromatch3.isMatch(childPattern, parentPattern);
|
|
5589
5926
|
});
|
|
5590
5927
|
};
|
|
5591
5928
|
var ensureChildPath = async (rootDir, areaFilesRoot, filePath) => {
|
|
@@ -5615,7 +5952,7 @@ var ensureChildPath = async (rootDir, areaFilesRoot, filePath) => {
|
|
|
5615
5952
|
var readRouteFile = async (rootDir, filePath) => {
|
|
5616
5953
|
try {
|
|
5617
5954
|
return {
|
|
5618
|
-
source: await
|
|
5955
|
+
source: await fs13.readFile(resolveRepoPath(rootDir, filePath), "utf8"),
|
|
5619
5956
|
diagnostic: null
|
|
5620
5957
|
};
|
|
5621
5958
|
} catch (error) {
|
|
@@ -5734,7 +6071,7 @@ var resolveAreaRouting = async (rootDir, config) => {
|
|
|
5734
6071
|
);
|
|
5735
6072
|
}
|
|
5736
6073
|
}
|
|
5737
|
-
const routeFilesUnderRoot = await
|
|
6074
|
+
const routeFilesUnderRoot = await fg4([`${config.areaFilesRoot}/**/*.md`], {
|
|
5738
6075
|
cwd: rootDir,
|
|
5739
6076
|
onlyFiles: true,
|
|
5740
6077
|
followSymbolicLinks: false
|
|
@@ -5776,7 +6113,8 @@ var resolveAreaRouting = async (rootDir, config) => {
|
|
|
5776
6113
|
};
|
|
5777
6114
|
|
|
5778
6115
|
// src/sync/classify.ts
|
|
5779
|
-
import
|
|
6116
|
+
import path6 from "path";
|
|
6117
|
+
import micromatch4 from "micromatch";
|
|
5780
6118
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5781
6119
|
".c",
|
|
5782
6120
|
".cc",
|
|
@@ -5853,11 +6191,11 @@ var FUNCTIONAL_CONFIG_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
5853
6191
|
"swagger.yaml",
|
|
5854
6192
|
"swagger.yml"
|
|
5855
6193
|
]);
|
|
5856
|
-
var
|
|
6194
|
+
var normalizePath2 = (filePath) => {
|
|
5857
6195
|
return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
|
|
5858
6196
|
};
|
|
5859
6197
|
var getBaseName = (filePath) => {
|
|
5860
|
-
const segments =
|
|
6198
|
+
const segments = normalizePath2(filePath).split("/");
|
|
5861
6199
|
return segments.at(-1) ?? filePath;
|
|
5862
6200
|
};
|
|
5863
6201
|
var getExtension = (filePath) => {
|
|
@@ -5868,20 +6206,23 @@ var getExtension = (filePath) => {
|
|
|
5868
6206
|
}
|
|
5869
6207
|
return baseName.slice(extensionIndex).toLowerCase();
|
|
5870
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
|
+
};
|
|
5871
6212
|
var isConfigPath = (filePath) => {
|
|
5872
|
-
const normalizedPath =
|
|
6213
|
+
const normalizedPath = normalizePath2(filePath);
|
|
5873
6214
|
const baseName = getBaseName(normalizedPath);
|
|
5874
6215
|
const extension = getExtension(normalizedPath);
|
|
5875
6216
|
return CONFIG_BASENAMES.has(baseName) || CONFIG_EXTENSIONS.has(extension) || CONFIG_SUFFIXES.some((suffix) => baseName.endsWith(suffix));
|
|
5876
6217
|
};
|
|
5877
6218
|
var isFunctionalConfigPath = (filePath) => {
|
|
5878
|
-
const normalizedPath =
|
|
6219
|
+
const normalizedPath = normalizePath2(filePath);
|
|
5879
6220
|
const baseName = getBaseName(normalizedPath).toLowerCase();
|
|
5880
6221
|
const extension = getExtension(normalizedPath);
|
|
5881
6222
|
return normalizedPath.startsWith(".github/workflows/") || FUNCTIONAL_CONFIG_BASENAMES.has(baseName) || (extension === ".yaml" || extension === ".yml" || extension === ".json") && FUNCTIONAL_CONFIG_DIRECTORIES.test(normalizedPath);
|
|
5882
6223
|
};
|
|
5883
6224
|
var isCodeLikePath = (filePath) => {
|
|
5884
|
-
const normalizedPath =
|
|
6225
|
+
const normalizedPath = normalizePath2(filePath);
|
|
5885
6226
|
const extension = getExtension(normalizedPath);
|
|
5886
6227
|
if (CODE_EXTENSIONS.has(extension)) {
|
|
5887
6228
|
return true;
|
|
@@ -5889,7 +6230,7 @@ var isCodeLikePath = (filePath) => {
|
|
|
5889
6230
|
return extension.length === 0 && COMMON_CODE_DIRECTORIES.test(normalizedPath);
|
|
5890
6231
|
};
|
|
5891
6232
|
var classifyPath = (filePath, ignorePatterns) => {
|
|
5892
|
-
const normalizedPath =
|
|
6233
|
+
const normalizedPath = normalizePath2(filePath);
|
|
5893
6234
|
if (normalizedPath === ".truthmark/config.yml") {
|
|
5894
6235
|
return "config";
|
|
5895
6236
|
}
|
|
@@ -5899,7 +6240,7 @@ var classifyPath = (filePath, ignorePatterns) => {
|
|
|
5899
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") {
|
|
5900
6241
|
return "derived";
|
|
5901
6242
|
}
|
|
5902
|
-
if (ignorePatterns.length > 0 &&
|
|
6243
|
+
if (ignorePatterns.length > 0 && micromatch4.isMatch(normalizedPath, ignorePatterns)) {
|
|
5903
6244
|
return "ignored";
|
|
5904
6245
|
}
|
|
5905
6246
|
if (normalizedPath.toLowerCase().endsWith(".md")) {
|
|
@@ -5921,9 +6262,9 @@ var classifyPath = (filePath, ignorePatterns) => {
|
|
|
5921
6262
|
var looksLikeGlob2 = (pattern) => {
|
|
5922
6263
|
return /[*?[\]{}()!+@]/u.test(pattern);
|
|
5923
6264
|
};
|
|
5924
|
-
var
|
|
6265
|
+
var pathExists3 = async (absolutePath) => {
|
|
5925
6266
|
try {
|
|
5926
|
-
await
|
|
6267
|
+
await fs14.stat(absolutePath);
|
|
5927
6268
|
return true;
|
|
5928
6269
|
} catch (error) {
|
|
5929
6270
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -5932,33 +6273,6 @@ var pathExists4 = async (absolutePath) => {
|
|
|
5932
6273
|
throw error;
|
|
5933
6274
|
}
|
|
5934
6275
|
};
|
|
5935
|
-
var COVERAGE_SCAN_PATTERNS = [
|
|
5936
|
-
"app/**/*",
|
|
5937
|
-
"api/**/*",
|
|
5938
|
-
"apps/**/*",
|
|
5939
|
-
"bin/**/*",
|
|
5940
|
-
"client/**/*",
|
|
5941
|
-
"cmd/**/*",
|
|
5942
|
-
"frontend/**/*",
|
|
5943
|
-
"infra/**/*",
|
|
5944
|
-
"infrastructure/**/*",
|
|
5945
|
-
"internal/**/*",
|
|
5946
|
-
"k8s/**/*",
|
|
5947
|
-
"kubernetes/**/*",
|
|
5948
|
-
"lib/**/*",
|
|
5949
|
-
"packages/**/*",
|
|
5950
|
-
"pkg/**/*",
|
|
5951
|
-
"proto/**/*",
|
|
5952
|
-
"schema/**/*",
|
|
5953
|
-
"schemas/**/*",
|
|
5954
|
-
"scripts/**/*",
|
|
5955
|
-
"server/**/*",
|
|
5956
|
-
"services/**/*",
|
|
5957
|
-
"src/**/*",
|
|
5958
|
-
"terraform/**/*",
|
|
5959
|
-
"web/**/*",
|
|
5960
|
-
".github/workflows/**/*"
|
|
5961
|
-
];
|
|
5962
6276
|
var BROAD_CODE_SURFACES = /* @__PURE__ */ new Set([
|
|
5963
6277
|
"app/**",
|
|
5964
6278
|
"apps/**",
|
|
@@ -6080,15 +6394,12 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6080
6394
|
productTruthRoot: resolveProductTruthRoot(config),
|
|
6081
6395
|
engineeringTruthRoot: resolveEngineeringTruthRoot(config)
|
|
6082
6396
|
});
|
|
6083
|
-
const discoveredCodeFiles = await
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
followSymbolicLinks: false,
|
|
6088
|
-
dot: true
|
|
6089
|
-
});
|
|
6397
|
+
const discoveredCodeFiles = await discoverRepositoryFilePaths(
|
|
6398
|
+
rootDir,
|
|
6399
|
+
config.ignore
|
|
6400
|
+
);
|
|
6090
6401
|
const rawCodeFiles = discoveredCodeFiles.filter(
|
|
6091
|
-
(filePath) => classifyPath(filePath, config.ignore) === "functional-code"
|
|
6402
|
+
(filePath) => classifyPath(filePath, config.ignore) === "functional-code" && !isTestPath(filePath)
|
|
6092
6403
|
);
|
|
6093
6404
|
const diagnostics = [...routing.diagnostics];
|
|
6094
6405
|
const truthDocumentPaths = [];
|
|
@@ -6100,15 +6411,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6100
6411
|
valid: true,
|
|
6101
6412
|
patterns: []
|
|
6102
6413
|
}));
|
|
6103
|
-
const codeFiles =
|
|
6104
|
-
for (const codeFile of rawCodeFiles.sort()) {
|
|
6105
|
-
try {
|
|
6106
|
-
await assertRepoContainment(rootDir, resolveRepoPath(rootDir, codeFile));
|
|
6107
|
-
codeFiles.push(codeFile);
|
|
6108
|
-
} catch {
|
|
6109
|
-
continue;
|
|
6110
|
-
}
|
|
6111
|
-
}
|
|
6414
|
+
const codeFiles = rawCodeFiles;
|
|
6112
6415
|
const truthReferences = routing.truthDocumentReferences;
|
|
6113
6416
|
for (const area of truthReferences) {
|
|
6114
6417
|
let areaHasTruthDocumentErrors = false;
|
|
@@ -6158,7 +6461,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6158
6461
|
] of area.truthDocuments.entries()) {
|
|
6159
6462
|
const routedEntry = area.truthDocumentEntries[truthDocumentIndex];
|
|
6160
6463
|
if (looksLikeGlob2(truthDocument)) {
|
|
6161
|
-
const matches = (await
|
|
6464
|
+
const matches = (await fg5([truthDocument], { cwd: rootDir, onlyFiles: true })).sort();
|
|
6162
6465
|
if (matches.length === 0) {
|
|
6163
6466
|
diagnostics.push({
|
|
6164
6467
|
category: "area-index",
|
|
@@ -6213,7 +6516,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6213
6516
|
areaHasTruthDocumentErrors = true;
|
|
6214
6517
|
continue;
|
|
6215
6518
|
}
|
|
6216
|
-
if (!await
|
|
6519
|
+
if (!await pathExists3(absoluteTruthDocumentPath)) {
|
|
6217
6520
|
diagnostics.push({
|
|
6218
6521
|
category: "area-index",
|
|
6219
6522
|
severity: "error",
|
|
@@ -6266,7 +6569,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6266
6569
|
entry.valid = false;
|
|
6267
6570
|
continue;
|
|
6268
6571
|
}
|
|
6269
|
-
const matches = await
|
|
6572
|
+
const matches = await fg5([codeSurfaceEntry], {
|
|
6270
6573
|
cwd: rootDir,
|
|
6271
6574
|
onlyFiles: true,
|
|
6272
6575
|
followSymbolicLinks: false
|
|
@@ -6317,7 +6620,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6317
6620
|
entry.valid = false;
|
|
6318
6621
|
continue;
|
|
6319
6622
|
}
|
|
6320
|
-
if (!await
|
|
6623
|
+
if (!await pathExists3(absoluteCodeSurfacePath)) {
|
|
6321
6624
|
diagnostics.push({
|
|
6322
6625
|
category: "area-index",
|
|
6323
6626
|
severity: "error",
|
|
@@ -6332,7 +6635,7 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6332
6635
|
}
|
|
6333
6636
|
for (const codeFile of codeFiles.sort()) {
|
|
6334
6637
|
const matched = areaCoverage.some(
|
|
6335
|
-
(entry) => entry.valid && entry.patterns.some((pattern) =>
|
|
6638
|
+
(entry) => entry.valid && entry.patterns.some((pattern) => micromatch5.isMatch(codeFile, pattern))
|
|
6336
6639
|
);
|
|
6337
6640
|
if (!matched) {
|
|
6338
6641
|
diagnostics.push({
|
|
@@ -6364,8 +6667,8 @@ var checkAreas = async (rootDir, config) => {
|
|
|
6364
6667
|
};
|
|
6365
6668
|
|
|
6366
6669
|
// src/checks/decisions.ts
|
|
6367
|
-
import
|
|
6368
|
-
import
|
|
6670
|
+
import fs15 from "fs/promises";
|
|
6671
|
+
import micromatch6 from "micromatch";
|
|
6369
6672
|
var PRODUCT_CAPABILITY_REQUIRED_HEADINGS = [
|
|
6370
6673
|
"Capability Promise",
|
|
6371
6674
|
"Users And Value",
|
|
@@ -6401,11 +6704,11 @@ var FORBIDDEN_ENGINEERING_HEADINGS = [
|
|
|
6401
6704
|
var isTruthDocumentKind3 = (value) => {
|
|
6402
6705
|
return TRUTH_DOCUMENT_KINDS.includes(value);
|
|
6403
6706
|
};
|
|
6404
|
-
var
|
|
6707
|
+
var escapeRegExp = (value) => {
|
|
6405
6708
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6406
6709
|
};
|
|
6407
6710
|
var hasHeading = (source, heading) => {
|
|
6408
|
-
return new RegExp(`^#{2,3}\\s+${
|
|
6711
|
+
return new RegExp(`^#{2,3}\\s+${escapeRegExp(heading)}\\s*$`, "mu").test(
|
|
6409
6712
|
source
|
|
6410
6713
|
);
|
|
6411
6714
|
};
|
|
@@ -6469,7 +6772,7 @@ var decisionTruthGlobs = (config) => {
|
|
|
6469
6772
|
];
|
|
6470
6773
|
};
|
|
6471
6774
|
var isDecisionTruthCandidate = (config, filePath) => {
|
|
6472
|
-
return !filePath.endsWith("/README.md") &&
|
|
6775
|
+
return !filePath.endsWith("/README.md") && micromatch6.isMatch(filePath, decisionTruthGlobs(config));
|
|
6473
6776
|
};
|
|
6474
6777
|
var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocumentEntries = []) => {
|
|
6475
6778
|
const diagnostics = [];
|
|
@@ -6480,7 +6783,7 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
|
|
|
6480
6783
|
(filePath) => truthDocumentMap.has(filePath) || isDecisionTruthCandidate(config, filePath)
|
|
6481
6784
|
).sort();
|
|
6482
6785
|
for (const filePath of candidatePaths) {
|
|
6483
|
-
const source = await
|
|
6786
|
+
const source = await fs15.readFile(
|
|
6484
6787
|
resolveRepoPath(rootDir, filePath),
|
|
6485
6788
|
"utf8"
|
|
6486
6789
|
);
|
|
@@ -6521,19 +6824,85 @@ var checkDecisionSections = async (rootDir, config, markdownPaths, truthDocument
|
|
|
6521
6824
|
return diagnostics;
|
|
6522
6825
|
};
|
|
6523
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
|
+
|
|
6524
6896
|
// src/impact/build.ts
|
|
6525
|
-
import
|
|
6897
|
+
import path11 from "path";
|
|
6526
6898
|
import micromatch7 from "micromatch";
|
|
6527
6899
|
|
|
6528
6900
|
// src/repo-index/file-tree.ts
|
|
6529
|
-
import
|
|
6530
|
-
import
|
|
6531
|
-
import { execa as execa2 } from "execa";
|
|
6532
|
-
import fg5 from "fast-glob";
|
|
6533
|
-
import micromatch6 from "micromatch";
|
|
6901
|
+
import fs17 from "fs/promises";
|
|
6902
|
+
import path8 from "path";
|
|
6534
6903
|
|
|
6535
6904
|
// src/truth/source-references.ts
|
|
6536
|
-
import
|
|
6905
|
+
import path7 from "path";
|
|
6537
6906
|
var repoRootPrefixes = [
|
|
6538
6907
|
".codex/",
|
|
6539
6908
|
".github/",
|
|
@@ -6553,11 +6922,11 @@ var normalizeSourceReferencePath = (truthDocPath, referencePath) => {
|
|
|
6553
6922
|
(prefix) => strippedPath.startsWith(prefix)
|
|
6554
6923
|
);
|
|
6555
6924
|
if (!isRepoRelative && (strippedPath.startsWith(".") || !strippedPath.includes("/"))) {
|
|
6556
|
-
return
|
|
6557
|
-
|
|
6925
|
+
return path7.posix.normalize(
|
|
6926
|
+
path7.posix.join(path7.posix.dirname(truthDocPath), strippedPath)
|
|
6558
6927
|
);
|
|
6559
6928
|
}
|
|
6560
|
-
return
|
|
6929
|
+
return path7.posix.normalize(strippedPath);
|
|
6561
6930
|
};
|
|
6562
6931
|
var normalizeReferenceText = (value) => {
|
|
6563
6932
|
const trimmed = value.trim();
|
|
@@ -6622,9 +6991,6 @@ var languageByExtension = /* @__PURE__ */ new Map([
|
|
|
6622
6991
|
[".yaml", "yaml"],
|
|
6623
6992
|
[".toml", "toml"]
|
|
6624
6993
|
]);
|
|
6625
|
-
var isTestPath = (filePath) => {
|
|
6626
|
-
return filePath.startsWith("tests/") || filePath.includes("/__tests__/") || /(?:^|[./-])(test|spec)\.[cm]?[jt]sx?$/u.test(path7.posix.basename(filePath));
|
|
6627
|
-
};
|
|
6628
6994
|
var fileKind = (filePath, ignore) => {
|
|
6629
6995
|
const classification = classifyPath(filePath, ignore);
|
|
6630
6996
|
if (classification === "derived") {
|
|
@@ -6649,7 +7015,7 @@ var fileKind = (filePath, ignore) => {
|
|
|
6649
7015
|
};
|
|
6650
7016
|
var targetHintsForTest = (filePath) => {
|
|
6651
7017
|
const hints = /* @__PURE__ */ new Set();
|
|
6652
|
-
const basename =
|
|
7018
|
+
const basename = path8.posix.basename(filePath).replace(/\.(test|spec)\.[cm]?[jt]sx?$/u, "");
|
|
6653
7019
|
if (basename.length > 0) {
|
|
6654
7020
|
hints.add(basename);
|
|
6655
7021
|
}
|
|
@@ -6666,43 +7032,18 @@ var targetHintsForTest = (filePath) => {
|
|
|
6666
7032
|
}
|
|
6667
7033
|
return [...hints].sort();
|
|
6668
7034
|
};
|
|
6669
|
-
var defaultIgnore = [".git/**", "node_modules/**", "dist/**", "build/**"];
|
|
6670
|
-
var normalizePath2 = (filePath) => filePath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
|
|
6671
7035
|
var isTruthDocumentKind4 = (value) => {
|
|
6672
7036
|
return typeof value === "string" && TRUTH_DOCUMENT_KINDS.includes(value);
|
|
6673
7037
|
};
|
|
6674
|
-
var gitDiscoverableFiles = async (rootDir) => {
|
|
6675
|
-
const result = await execa2(
|
|
6676
|
-
"git",
|
|
6677
|
-
["ls-files", "--cached", "--others", "--exclude-standard", "--deduplicate"],
|
|
6678
|
-
{
|
|
6679
|
-
cwd: rootDir,
|
|
6680
|
-
reject: false
|
|
6681
|
-
}
|
|
6682
|
-
);
|
|
6683
|
-
if ((result.exitCode ?? 1) !== 0) {
|
|
6684
|
-
return null;
|
|
6685
|
-
}
|
|
6686
|
-
return result.stdout.split("\n").map((line) => normalizePath2(line.trim())).filter((line) => line.length > 0);
|
|
6687
|
-
};
|
|
6688
|
-
var isIgnoredPath = (filePath, ignore) => {
|
|
6689
|
-
return micromatch6.isMatch(filePath, [...defaultIgnore, ...ignore]);
|
|
6690
|
-
};
|
|
6691
7038
|
var discoverRepoFiles = async (rootDir, ignore) => {
|
|
6692
|
-
const discoveredFiles = await
|
|
6693
|
-
cwd: rootDir,
|
|
6694
|
-
onlyFiles: true,
|
|
6695
|
-
dot: true,
|
|
6696
|
-
ignore: [...defaultIgnore, ...ignore],
|
|
6697
|
-
followSymbolicLinks: false
|
|
6698
|
-
});
|
|
7039
|
+
const discoveredFiles = await discoverRepositoryFilePaths(rootDir, ignore);
|
|
6699
7040
|
const files = [];
|
|
6700
7041
|
const docs = [];
|
|
6701
7042
|
const tests = [];
|
|
6702
|
-
for (const filePath of discoveredFiles
|
|
7043
|
+
for (const filePath of discoveredFiles) {
|
|
6703
7044
|
let stat;
|
|
6704
7045
|
try {
|
|
6705
|
-
stat = await
|
|
7046
|
+
stat = await fs17.stat(path8.join(rootDir, filePath));
|
|
6706
7047
|
} catch (error) {
|
|
6707
7048
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
6708
7049
|
continue;
|
|
@@ -6712,7 +7053,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6712
7053
|
if (!stat.isFile()) {
|
|
6713
7054
|
continue;
|
|
6714
7055
|
}
|
|
6715
|
-
const extension =
|
|
7056
|
+
const extension = path8.posix.extname(filePath);
|
|
6716
7057
|
const kind = fileKind(filePath, ignore);
|
|
6717
7058
|
files.push({
|
|
6718
7059
|
path: filePath,
|
|
@@ -6726,7 +7067,7 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6726
7067
|
});
|
|
6727
7068
|
}
|
|
6728
7069
|
if (kind === "doc") {
|
|
6729
|
-
const source = await
|
|
7070
|
+
const source = await fs17.readFile(path8.join(rootDir, filePath), "utf8");
|
|
6730
7071
|
const parsed = parseFrontmatter(source);
|
|
6731
7072
|
const markdown = parseMarkdownDocument(parsed.content);
|
|
6732
7073
|
const title = markdown.headings.find((heading) => heading.depth === 1)?.text ?? null;
|
|
@@ -6754,8 +7095,8 @@ var discoverRepoFiles = async (rootDir, ignore) => {
|
|
|
6754
7095
|
};
|
|
6755
7096
|
|
|
6756
7097
|
// src/repo-index/package-metadata.ts
|
|
6757
|
-
import
|
|
6758
|
-
import
|
|
7098
|
+
import fs18 from "fs/promises";
|
|
7099
|
+
import path9 from "path";
|
|
6759
7100
|
import fg6 from "fast-glob";
|
|
6760
7101
|
var packageManagerFor = async (rootDir, packageDir) => {
|
|
6761
7102
|
const lockfiles = [
|
|
@@ -6767,7 +7108,7 @@ var packageManagerFor = async (rootDir, packageDir) => {
|
|
|
6767
7108
|
];
|
|
6768
7109
|
for (const [lockfile, manager] of lockfiles) {
|
|
6769
7110
|
try {
|
|
6770
|
-
await
|
|
7111
|
+
await fs18.access(path9.join(rootDir, packageDir, lockfile));
|
|
6771
7112
|
return manager;
|
|
6772
7113
|
} catch {
|
|
6773
7114
|
continue;
|
|
@@ -6784,8 +7125,8 @@ var discoverPackageMetadata = async (rootDir) => {
|
|
|
6784
7125
|
});
|
|
6785
7126
|
const packages = [];
|
|
6786
7127
|
for (const packageFile of packageFiles.sort()) {
|
|
6787
|
-
const packageDir =
|
|
6788
|
-
const raw = JSON.parse(await
|
|
7128
|
+
const packageDir = path9.posix.dirname(packageFile) === "." ? "" : path9.posix.dirname(packageFile);
|
|
7129
|
+
const raw = JSON.parse(await fs18.readFile(path9.join(rootDir, packageFile), "utf8"));
|
|
6789
7130
|
const scripts = raw.scripts && typeof raw.scripts === "object" ? Object.keys(raw.scripts).sort() : [];
|
|
6790
7131
|
packages.push({
|
|
6791
7132
|
path: packageFile,
|
|
@@ -6890,8 +7231,8 @@ var buildRepoIndex = async (cwd) => {
|
|
|
6890
7231
|
import { execa as execa4 } from "execa";
|
|
6891
7232
|
|
|
6892
7233
|
// src/git/changes.ts
|
|
6893
|
-
import
|
|
6894
|
-
import
|
|
7234
|
+
import fs19 from "fs/promises";
|
|
7235
|
+
import path10 from "path";
|
|
6895
7236
|
import { execa as execa3 } from "execa";
|
|
6896
7237
|
var normalizePath3 = (filePath) => {
|
|
6897
7238
|
return filePath.replaceAll("\\", "/").replace(/^\.\//u, "");
|
|
@@ -6900,9 +7241,9 @@ var listChangedPaths = async (cwd, args) => {
|
|
|
6900
7241
|
const result = await execa3("git", args, { cwd });
|
|
6901
7242
|
return result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => normalizePath3(line));
|
|
6902
7243
|
};
|
|
6903
|
-
var
|
|
7244
|
+
var pathExists4 = async (filePath) => {
|
|
6904
7245
|
try {
|
|
6905
|
-
await
|
|
7246
|
+
await fs19.access(filePath);
|
|
6906
7247
|
return true;
|
|
6907
7248
|
} catch {
|
|
6908
7249
|
return false;
|
|
@@ -6946,7 +7287,7 @@ var getUncommittedChanges = async (cwd) => {
|
|
|
6946
7287
|
const deletedPathCandidates = /* @__PURE__ */ new Set([...stagedDeletedPaths, ...unstagedDeletedPaths]);
|
|
6947
7288
|
for (const deletedPath of deletedPathCandidates) {
|
|
6948
7289
|
const change = getOrCreateChange(changesByPath, deletedPath);
|
|
6949
|
-
change.deleted = !await
|
|
7290
|
+
change.deleted = !await pathExists4(path10.join(rootDir, deletedPath));
|
|
6950
7291
|
}
|
|
6951
7292
|
return Array.from(changesByPath.values()).sort((left, right) => {
|
|
6952
7293
|
return left.path.localeCompare(right.path);
|
|
@@ -7050,10 +7391,10 @@ var toImpactRoute = (route) => ({
|
|
|
7050
7391
|
var changedFilePaths = (changedFile) => {
|
|
7051
7392
|
return [changedFile.path, ...changedFile.previousPath ? [changedFile.previousPath] : []];
|
|
7052
7393
|
};
|
|
7053
|
-
var
|
|
7394
|
+
var pathSegments2 = (filePath) => filePath.split("/").filter(Boolean);
|
|
7054
7395
|
var testHintMatchesChangedFile = (hints, changedPath) => {
|
|
7055
|
-
const changedBaseName =
|
|
7056
|
-
const changedSegments =
|
|
7396
|
+
const changedBaseName = path11.posix.basename(changedPath);
|
|
7397
|
+
const changedSegments = pathSegments2(changedPath);
|
|
7057
7398
|
return hints.some((hint) => changedBaseName.startsWith(hint) || changedSegments.includes(hint));
|
|
7058
7399
|
};
|
|
7059
7400
|
var buildImpactSet = async (cwd, options) => {
|
|
@@ -7149,12 +7490,12 @@ var checkFreshness = async (rootDir, _config, _truthDocumentPaths, base) => {
|
|
|
7149
7490
|
};
|
|
7150
7491
|
|
|
7151
7492
|
// src/evidence/validate.ts
|
|
7152
|
-
import
|
|
7493
|
+
import fs21 from "fs/promises";
|
|
7153
7494
|
import fg7 from "fast-glob";
|
|
7154
7495
|
|
|
7155
7496
|
// src/evidence/parse.ts
|
|
7156
|
-
import
|
|
7157
|
-
import
|
|
7497
|
+
import fs20 from "fs/promises";
|
|
7498
|
+
import path12 from "path";
|
|
7158
7499
|
import { parse as parse4 } from "yaml";
|
|
7159
7500
|
var yamlFencePattern = /```ya?ml\s*\n([\s\S]*?)```/giu;
|
|
7160
7501
|
var topLevelEvidenceMarkerPattern = /^evidence\s*:/imu;
|
|
@@ -7173,7 +7514,7 @@ var toEvidenceReference = (truthDocPath, raw) => {
|
|
|
7173
7514
|
};
|
|
7174
7515
|
};
|
|
7175
7516
|
var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
7176
|
-
const source = await
|
|
7517
|
+
const source = await fs20.readFile(path12.join(rootDir, truthDocPath), "utf8");
|
|
7177
7518
|
const parsed = parseFrontmatter(source);
|
|
7178
7519
|
const references = [];
|
|
7179
7520
|
for (const entry of parseSourceReferences(source, truthDocPath)) {
|
|
@@ -7204,9 +7545,9 @@ var parseEvidenceReferences = async (rootDir, truthDocPath) => {
|
|
|
7204
7545
|
};
|
|
7205
7546
|
|
|
7206
7547
|
// src/evidence/validate.ts
|
|
7207
|
-
var
|
|
7548
|
+
var pathExists5 = async (filePath) => {
|
|
7208
7549
|
try {
|
|
7209
|
-
await
|
|
7550
|
+
await fs21.access(filePath);
|
|
7210
7551
|
return true;
|
|
7211
7552
|
} catch {
|
|
7212
7553
|
return false;
|
|
@@ -7251,7 +7592,7 @@ var validateHash = async (rootDir, reference) => {
|
|
|
7251
7592
|
if (!reference.contentHash.startsWith("sha256:")) {
|
|
7252
7593
|
return diagnosticFor(reference, `Evidence hash for ${reference.path} must use sha256:.`);
|
|
7253
7594
|
}
|
|
7254
|
-
const source = await
|
|
7595
|
+
const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
7255
7596
|
const lines = source.split("\n");
|
|
7256
7597
|
const startLine = reference.startLine ?? 1;
|
|
7257
7598
|
const endLine = reference.endLine ?? lines.length;
|
|
@@ -7265,7 +7606,7 @@ var validateLineSpan = async (rootDir, reference) => {
|
|
|
7265
7606
|
if (reference.startLine === void 0 && reference.endLine === void 0) {
|
|
7266
7607
|
return null;
|
|
7267
7608
|
}
|
|
7268
|
-
const source = await
|
|
7609
|
+
const source = await fs21.readFile(resolveRepoPath(rootDir, reference.path), "utf8");
|
|
7269
7610
|
const lines = source.split("\n");
|
|
7270
7611
|
const startLine = reference.startLine ?? 1;
|
|
7271
7612
|
const endLine = reference.endLine ?? lines.length;
|
|
@@ -7280,7 +7621,7 @@ var validateReference = async (rootDir, reference) => {
|
|
|
7280
7621
|
}
|
|
7281
7622
|
const absolutePath = resolveRepoPath(rootDir, reference.path);
|
|
7282
7623
|
await assertRepoContainment(rootDir, absolutePath);
|
|
7283
|
-
if (!await
|
|
7624
|
+
if (!await pathExists5(absolutePath)) {
|
|
7284
7625
|
diagnostics.push(diagnosticFor(reference, `Referenced file ${reference.path} does not exist.`));
|
|
7285
7626
|
return diagnostics;
|
|
7286
7627
|
}
|
|
@@ -7712,7 +8053,7 @@ var candidateStaleTruthDocsFor = (repoIndex, primaryTruthDocs, impactSet, diagno
|
|
|
7712
8053
|
]);
|
|
7713
8054
|
};
|
|
7714
8055
|
for (const truthDoc of [...primary, ...changedPaths].filter(
|
|
7715
|
-
(
|
|
8056
|
+
(path13) => indexedTruthDocs.has(path13)
|
|
7716
8057
|
)) {
|
|
7717
8058
|
for (const linkedTruthDoc of linkedTruthDocs(truthDoc)) {
|
|
7718
8059
|
addCandidate(linkedTruthDoc);
|
|
@@ -7910,7 +8251,7 @@ var buildWorkflowState = async (cwd, options) => {
|
|
|
7910
8251
|
};
|
|
7911
8252
|
|
|
7912
8253
|
// src/cli/handlers.ts
|
|
7913
|
-
import
|
|
8254
|
+
import fs22 from "fs/promises";
|
|
7914
8255
|
|
|
7915
8256
|
// src/agents/workflow-helper-validation.ts
|
|
7916
8257
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -8233,13 +8574,20 @@ var runConfig2 = async (options) => {
|
|
|
8233
8574
|
var runInit2 = async () => {
|
|
8234
8575
|
return runInit(process.cwd());
|
|
8235
8576
|
};
|
|
8577
|
+
var runUninstall2 = async (mode) => {
|
|
8578
|
+
return runUninstall(process.cwd(), mode);
|
|
8579
|
+
};
|
|
8236
8580
|
var runCheck2 = async (options = {}) => {
|
|
8237
8581
|
return runCheck(process.cwd(), options);
|
|
8238
8582
|
};
|
|
8239
8583
|
var runIndex = async () => {
|
|
8240
8584
|
const repoIndex = await buildRepoIndex(process.cwd());
|
|
8241
|
-
const errorCount = repoIndex.diagnostics.filter(
|
|
8242
|
-
|
|
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;
|
|
8243
8591
|
return {
|
|
8244
8592
|
command: "index",
|
|
8245
8593
|
summary: `Truthmark index completed with ${errorCount} error diagnostics and ${reviewCount} review diagnostics.`,
|
|
@@ -8265,8 +8613,12 @@ var runImpact = async (options) => {
|
|
|
8265
8613
|
};
|
|
8266
8614
|
}
|
|
8267
8615
|
const impactSet = await buildImpactSet(process.cwd(), { base: options.base });
|
|
8268
|
-
const errorCount = impactSet.diagnostics.filter(
|
|
8269
|
-
|
|
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;
|
|
8270
8622
|
return {
|
|
8271
8623
|
command: "impact",
|
|
8272
8624
|
summary: `Truthmark impact completed with ${errorCount} error diagnostics and ${reviewCount} review diagnostics.`,
|
|
@@ -8295,7 +8647,7 @@ var invalidWorkflowResult = (command, workflow) => ({
|
|
|
8295
8647
|
});
|
|
8296
8648
|
var readHelperFile = async (filePath, helper) => {
|
|
8297
8649
|
try {
|
|
8298
|
-
return await
|
|
8650
|
+
return await fs22.readFile(filePath, "utf8");
|
|
8299
8651
|
} catch (error) {
|
|
8300
8652
|
const message = error instanceof Error ? error.message : String(error);
|
|
8301
8653
|
return { ok: false, helper, errors: [`could not read file: ${message}`] };
|
|
@@ -8314,7 +8666,10 @@ var runValidateWriteLease = async (leaseFile, changedFilesFile) => {
|
|
|
8314
8666
|
if (typeof leaseText !== "string") {
|
|
8315
8667
|
return leaseText;
|
|
8316
8668
|
}
|
|
8317
|
-
const changedText = await readHelperFile(
|
|
8669
|
+
const changedText = await readHelperFile(
|
|
8670
|
+
changedFilesFile,
|
|
8671
|
+
"validate-write-lease"
|
|
8672
|
+
);
|
|
8318
8673
|
if (typeof changedText !== "string") {
|
|
8319
8674
|
return changedText;
|
|
8320
8675
|
}
|
|
@@ -8356,9 +8711,15 @@ var writeResult = (result, options) => {
|
|
|
8356
8711
|
};
|
|
8357
8712
|
var renderValidationHuman = (result) => {
|
|
8358
8713
|
if (result.ok === true) {
|
|
8359
|
-
return [
|
|
8714
|
+
return [
|
|
8715
|
+
`${result.helper}: ok`,
|
|
8716
|
+
...result.checks.map((check) => `- ${check}`)
|
|
8717
|
+
].join("\n");
|
|
8360
8718
|
}
|
|
8361
|
-
return [
|
|
8719
|
+
return [
|
|
8720
|
+
`${result.helper}: failed`,
|
|
8721
|
+
...result.errors.map((error) => `- ${error}`)
|
|
8722
|
+
].join("\n");
|
|
8362
8723
|
};
|
|
8363
8724
|
var toValidationCommandResult = (command, result) => ({
|
|
8364
8725
|
command,
|
|
@@ -8381,35 +8742,69 @@ var addJsonOption = (command) => {
|
|
|
8381
8742
|
};
|
|
8382
8743
|
var buildProgram = () => {
|
|
8383
8744
|
const program = new Command();
|
|
8384
|
-
program.name("truthmark").description(
|
|
8745
|
+
program.name("truthmark").description(
|
|
8746
|
+
"Git-native, branch-scoped truth workflow installer for local AI coding agents."
|
|
8747
|
+
).showHelpAfterError();
|
|
8385
8748
|
addJsonOption(
|
|
8386
|
-
program.command("config").description(
|
|
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")
|
|
8387
8755
|
).action(async (options) => {
|
|
8388
8756
|
writeResult(await runConfig2(options), options);
|
|
8389
8757
|
});
|
|
8390
8758
|
addJsonOption(
|
|
8391
|
-
program.command("init").description(
|
|
8759
|
+
program.command("init").description(
|
|
8760
|
+
"Initialize Truthmark workflow files in the current repository."
|
|
8761
|
+
)
|
|
8392
8762
|
).action(async (options) => {
|
|
8393
8763
|
writeResult(await runInit2(), options);
|
|
8394
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
|
+
});
|
|
8395
8781
|
addJsonOption(
|
|
8396
8782
|
program.command("check").description("Run local Truthmark diagnostics.").option("--base <ref>", "Base Git ref for freshness diagnostics")
|
|
8397
8783
|
).action(async (options) => {
|
|
8398
8784
|
writeResult(await runCheck2({ base: options.base }), options);
|
|
8399
8785
|
});
|
|
8400
8786
|
addJsonOption(
|
|
8401
|
-
program.command("index").description(
|
|
8787
|
+
program.command("index").description(
|
|
8788
|
+
"Inspect derived Truthmark workflow routing metadata for the current checkout."
|
|
8789
|
+
)
|
|
8402
8790
|
).action(async (options) => {
|
|
8403
8791
|
writeResult(await runIndex(), options);
|
|
8404
8792
|
});
|
|
8405
8793
|
addJsonOption(
|
|
8406
|
-
program.command("impact").description(
|
|
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")
|
|
8407
8797
|
).action(async (options) => {
|
|
8408
8798
|
writeResult(await runImpact({ base: options.base }), options);
|
|
8409
8799
|
});
|
|
8410
8800
|
const workflow = program.command("workflow").description("Inspect agent-facing Truthmark workflow state.");
|
|
8411
8801
|
addJsonOption(
|
|
8412
|
-
workflow.command("status").description(
|
|
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")
|
|
8413
8808
|
).action(async (options) => {
|
|
8414
8809
|
writeResult(
|
|
8415
8810
|
await runWorkflowStatus({
|
|
@@ -8419,11 +8814,17 @@ var buildProgram = () => {
|
|
|
8419
8814
|
options
|
|
8420
8815
|
);
|
|
8421
8816
|
});
|
|
8422
|
-
const validate = program.command("validate").description(
|
|
8817
|
+
const validate = program.command("validate").description(
|
|
8818
|
+
"Run optional Truthmark workflow helper validators from the installed CLI."
|
|
8819
|
+
);
|
|
8423
8820
|
addJsonOption(
|
|
8424
8821
|
validate.command("sync-report").description("Validate a Truth Sync report file.").argument("<report-file>", "Truth Sync report file")
|
|
8425
8822
|
).action(async (reportFile, options) => {
|
|
8426
|
-
writeValidationResult(
|
|
8823
|
+
writeValidationResult(
|
|
8824
|
+
"validate sync-report",
|
|
8825
|
+
await runValidateSyncReport(reportFile),
|
|
8826
|
+
options
|
|
8827
|
+
);
|
|
8427
8828
|
});
|
|
8428
8829
|
addJsonOption(
|
|
8429
8830
|
validate.command("document-report").description("Validate a Truth Document report file.").argument("<report-file>", "Truth Document report file")
|
|
@@ -8435,7 +8836,9 @@ var buildProgram = () => {
|
|
|
8435
8836
|
);
|
|
8436
8837
|
});
|
|
8437
8838
|
addJsonOption(
|
|
8438
|
-
validate.command("write-lease").description(
|
|
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")
|
|
8439
8842
|
).action(
|
|
8440
8843
|
async (leaseOrReportFile, changedFilesFile, options) => {
|
|
8441
8844
|
writeValidationResult(
|