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