cto-ai-cli 3.1.0 → 4.0.0
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/DOCS.md +352 -0
- package/README.md +192 -15
- package/dist/action/index.js +629 -83
- package/dist/api/dashboard.js +107 -23
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +108 -24
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +2925 -0
- package/dist/cli/score.js +3015 -237
- package/dist/cli/v2/index.js +133 -49
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +85 -1
- package/dist/engine/index.js +665 -42
- package/dist/engine/index.js.map +1 -1
- package/dist/gateway/index.d.ts +281 -0
- package/dist/gateway/index.js +2803 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/govern/index.d.ts +67 -3
- package/dist/govern/index.js +462 -23
- package/dist/govern/index.js.map +1 -1
- package/dist/interact/index.js +108 -24
- package/dist/interact/index.js.map +1 -1
- package/dist/mcp/v2.js +130 -46
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +3 -2
package/dist/mcp/v2.js
CHANGED
|
@@ -13778,8 +13778,8 @@ function date4(params) {
|
|
|
13778
13778
|
config(en_default());
|
|
13779
13779
|
|
|
13780
13780
|
// src/mcp/v2.ts
|
|
13781
|
-
import { resolve as resolve8, join as
|
|
13782
|
-
import { readFile as readFile8, writeFile as
|
|
13781
|
+
import { resolve as resolve8, join as join8 } from "path";
|
|
13782
|
+
import { readFile as readFile8, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
13783
13783
|
|
|
13784
13784
|
// src/engine/analyzer.ts
|
|
13785
13785
|
import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
|
|
@@ -14716,11 +14716,13 @@ function getActiveWatchers() {
|
|
|
14716
14716
|
}
|
|
14717
14717
|
|
|
14718
14718
|
// src/engine/selector.ts
|
|
14719
|
-
import { createHash as
|
|
14719
|
+
import { createHash as createHash4 } from "crypto";
|
|
14720
14720
|
|
|
14721
14721
|
// src/govern/secrets.ts
|
|
14722
14722
|
import { readFile as readFile3 } from "fs/promises";
|
|
14723
|
-
import {
|
|
14723
|
+
import { readFileSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
14724
|
+
import { resolve as resolve5, relative as relative4, join as join4, dirname as dirname2 } from "path";
|
|
14725
|
+
import { createHash as createHash3 } from "crypto";
|
|
14724
14726
|
var BUILTIN_PATTERNS = [
|
|
14725
14727
|
// API Keys
|
|
14726
14728
|
{ type: "api-key", source: `(?:api[_-]?key|apikey)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{20,})['"]?`, flags: "gi", severity: "critical", description: "API Key" },
|
|
@@ -14745,15 +14747,66 @@ var BUILTIN_PATTERNS = [
|
|
|
14745
14747
|
{ type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
|
|
14746
14748
|
{ type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
|
|
14747
14749
|
// Environment variables with secrets
|
|
14748
|
-
{ type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
|
|
14750
|
+
{ type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
|
|
14751
|
+
// Stripe
|
|
14752
|
+
{ type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
|
|
14753
|
+
{ type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
|
|
14754
|
+
{ type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
|
|
14755
|
+
// Slack
|
|
14756
|
+
{ type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
|
|
14757
|
+
{ type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
|
|
14758
|
+
{ type: "api-key", source: "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+", flags: "g", severity: "high", description: "Slack Webhook URL" },
|
|
14759
|
+
// Google
|
|
14760
|
+
{ type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
|
|
14761
|
+
{ type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
|
|
14762
|
+
// Azure
|
|
14763
|
+
{ type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
|
|
14764
|
+
// Twilio
|
|
14765
|
+
{ type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
|
|
14766
|
+
// SendGrid
|
|
14767
|
+
{ type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
|
|
14768
|
+
// JWT
|
|
14769
|
+
{ type: "token", source: "eyJ[a-zA-Z0-9_-]{10,}\\.eyJ[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}", flags: "g", severity: "high", description: "JSON Web Token" },
|
|
14770
|
+
// Datadog
|
|
14771
|
+
{ type: "api-key", source: `(?:DD_API_KEY|DATADOG_API_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{32})['"]?`, flags: "gi", severity: "critical", description: "Datadog API Key" },
|
|
14772
|
+
{ type: "api-key", source: `(?:DD_APP_KEY|DATADOG_APP_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{40})['"]?`, flags: "gi", severity: "critical", description: "Datadog App Key" },
|
|
14773
|
+
// Sentry
|
|
14774
|
+
{ type: "connection-string", source: "https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+", flags: "g", severity: "high", description: "Sentry DSN" },
|
|
14775
|
+
// Firebase
|
|
14776
|
+
{ type: "api-key", source: `(?:FIREBASE_API_KEY|FIREBASE_KEY)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{30,})['"]?`, flags: "gi", severity: "high", description: "Firebase API Key" },
|
|
14777
|
+
{ type: "connection-string", source: `firebase[a-z]*:\\/\\/[^\\s'"]+`, flags: "gi", severity: "high", description: "Firebase URL" },
|
|
14778
|
+
// Supabase
|
|
14779
|
+
{ type: "api-key", source: "sbp_[a-f0-9]{40}", flags: "g", severity: "critical", description: "Supabase Service Key" },
|
|
14780
|
+
{ type: "token", source: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}", flags: "g", severity: "high", description: "Supabase Anon/Service JWT" },
|
|
14781
|
+
// Vercel
|
|
14782
|
+
{ type: "token", source: `(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\s*[:=]\\s*['"]?([a-zA-Z0-9]{24,})['"]?`, flags: "gi", severity: "critical", description: "Vercel Token" },
|
|
14783
|
+
// Heroku
|
|
14784
|
+
{ type: "api-key", source: `(?:HEROKU_API_KEY|HEROKU_TOKEN)\\s*[:=]\\s*['"]?([a-f0-9\\-]{36,})['"]?`, flags: "gi", severity: "critical", description: "Heroku API Key" },
|
|
14785
|
+
// DigitalOcean
|
|
14786
|
+
{ type: "token", source: "dop_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean Personal Access Token" },
|
|
14787
|
+
{ type: "token", source: "doo_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean OAuth Token" },
|
|
14788
|
+
// Mailgun
|
|
14789
|
+
{ type: "api-key", source: "key-[a-zA-Z0-9]{32}", flags: "g", severity: "high", description: "Mailgun API Key" },
|
|
14790
|
+
// PII
|
|
14791
|
+
{ type: "pii", source: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", flags: "g", severity: "medium", description: "Email Address (PII)" },
|
|
14792
|
+
{ type: "pii", source: "\\b(?!000|666|9\\d{2})(\\d{3})[-.]?(?!00)(\\d{2})[-.]?(?!0000)(\\d{4})\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
|
|
14749
14793
|
];
|
|
14794
|
+
var _cachedBuiltinPatterns = null;
|
|
14795
|
+
function getBuiltinPatterns() {
|
|
14796
|
+
if (!_cachedBuiltinPatterns) {
|
|
14797
|
+
_cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({
|
|
14798
|
+
type: def.type,
|
|
14799
|
+
pattern: new RegExp(def.source, def.flags),
|
|
14800
|
+
severity: def.severity,
|
|
14801
|
+
description: def.description
|
|
14802
|
+
}));
|
|
14803
|
+
}
|
|
14804
|
+
return _cachedBuiltinPatterns;
|
|
14805
|
+
}
|
|
14750
14806
|
function buildPatterns(customPatterns = []) {
|
|
14751
|
-
const
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
severity: def.severity,
|
|
14755
|
-
description: def.description
|
|
14756
|
-
}));
|
|
14807
|
+
const builtins = getBuiltinPatterns();
|
|
14808
|
+
if (customPatterns.length === 0) return builtins;
|
|
14809
|
+
const patterns = [...builtins];
|
|
14757
14810
|
for (const custom2 of customPatterns) {
|
|
14758
14811
|
try {
|
|
14759
14812
|
patterns.push({
|
|
@@ -14767,7 +14820,7 @@ function buildPatterns(customPatterns = []) {
|
|
|
14767
14820
|
}
|
|
14768
14821
|
return patterns;
|
|
14769
14822
|
}
|
|
14770
|
-
function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
14823
|
+
function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
|
|
14771
14824
|
const findings = [];
|
|
14772
14825
|
const lines = content.split("\n");
|
|
14773
14826
|
const allPatterns = buildPatterns(customPatterns);
|
|
@@ -14779,6 +14832,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
|
14779
14832
|
while ((match = secretPattern.pattern.exec(line)) !== null) {
|
|
14780
14833
|
const matchText = match[0];
|
|
14781
14834
|
if (isTemplateOrPlaceholder(matchText)) continue;
|
|
14835
|
+
if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
|
|
14782
14836
|
findings.push({
|
|
14783
14837
|
type: secretPattern.type,
|
|
14784
14838
|
file: filePath,
|
|
@@ -14837,6 +14891,36 @@ function isTemplateOrPlaceholder(value) {
|
|
|
14837
14891
|
];
|
|
14838
14892
|
return placeholders.some((p) => p.test(value));
|
|
14839
14893
|
}
|
|
14894
|
+
var PII_SAFE_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
|
|
14895
|
+
"example.com",
|
|
14896
|
+
"example.org",
|
|
14897
|
+
"example.net",
|
|
14898
|
+
"test.com",
|
|
14899
|
+
"test.org",
|
|
14900
|
+
"test.net",
|
|
14901
|
+
"localhost",
|
|
14902
|
+
"localhost.localdomain",
|
|
14903
|
+
"email.com",
|
|
14904
|
+
"mail.com",
|
|
14905
|
+
"foo.com",
|
|
14906
|
+
"bar.com",
|
|
14907
|
+
"baz.com",
|
|
14908
|
+
"acme.com",
|
|
14909
|
+
"company.com",
|
|
14910
|
+
"corp.com",
|
|
14911
|
+
"noreply.com",
|
|
14912
|
+
"no-reply.com",
|
|
14913
|
+
"users.noreply.github.com",
|
|
14914
|
+
"placeholder.com"
|
|
14915
|
+
]);
|
|
14916
|
+
function isSafeEmail(value, extraDomains) {
|
|
14917
|
+
const match = value.match(/@([a-zA-Z0-9.-]+)$/);
|
|
14918
|
+
if (!match) return false;
|
|
14919
|
+
const domain2 = match[1].toLowerCase();
|
|
14920
|
+
if (PII_SAFE_EMAIL_DOMAINS.has(domain2)) return true;
|
|
14921
|
+
if (extraDomains && extraDomains.has(domain2)) return true;
|
|
14922
|
+
return false;
|
|
14923
|
+
}
|
|
14840
14924
|
function deduplicateFindings(findings) {
|
|
14841
14925
|
const seen = /* @__PURE__ */ new Set();
|
|
14842
14926
|
return findings.filter((f) => {
|
|
@@ -14850,8 +14934,8 @@ function deduplicateFindings(findings) {
|
|
|
14850
14934
|
// src/engine/pruner.ts
|
|
14851
14935
|
import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
14852
14936
|
import { readFile as readFile4 } from "fs/promises";
|
|
14853
|
-
import { existsSync as
|
|
14854
|
-
import { join as
|
|
14937
|
+
import { existsSync as existsSync3 } from "fs";
|
|
14938
|
+
import { join as join5 } from "path";
|
|
14855
14939
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
|
|
14856
14940
|
async function pruneFile(file2, level) {
|
|
14857
14941
|
if (level === "excluded") {
|
|
@@ -15097,9 +15181,9 @@ function addJSDoc(node, parts) {
|
|
|
15097
15181
|
function findTsConfig(filePath) {
|
|
15098
15182
|
let dir = filePath;
|
|
15099
15183
|
for (let i = 0; i < 10; i++) {
|
|
15100
|
-
dir =
|
|
15101
|
-
const candidate =
|
|
15102
|
-
if (
|
|
15184
|
+
dir = join5(dir, "..");
|
|
15185
|
+
const candidate = join5(dir, "tsconfig.json");
|
|
15186
|
+
if (existsSync3(candidate)) return candidate;
|
|
15103
15187
|
}
|
|
15104
15188
|
return void 0;
|
|
15105
15189
|
}
|
|
@@ -15366,7 +15450,7 @@ async function selectContext(input) {
|
|
|
15366
15450
|
);
|
|
15367
15451
|
const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
|
|
15368
15452
|
const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
|
|
15369
|
-
const hash2 =
|
|
15453
|
+
const hash2 = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
|
|
15370
15454
|
return {
|
|
15371
15455
|
files: selectedFiles,
|
|
15372
15456
|
totalTokens: usedTokens,
|
|
@@ -16866,25 +16950,25 @@ function fmt2(n) {
|
|
|
16866
16950
|
}
|
|
16867
16951
|
|
|
16868
16952
|
// src/engine/config.ts
|
|
16869
|
-
import { readFile as readFile6, writeFile, mkdir } from "fs/promises";
|
|
16870
|
-
import { join as
|
|
16871
|
-
import { existsSync as
|
|
16953
|
+
import { readFile as readFile6, writeFile as writeFile2, mkdir } from "fs/promises";
|
|
16954
|
+
import { join as join6 } from "path";
|
|
16955
|
+
import { existsSync as existsSync4 } from "fs";
|
|
16872
16956
|
import { parse as parseYAML, stringify as stringifyYAML } from "yaml";
|
|
16873
16957
|
var CONFIG_DIR = ".cto";
|
|
16874
16958
|
var CONFIG_FILE = "config.yml";
|
|
16875
16959
|
var POLICY_FILE = "policy.yml";
|
|
16876
16960
|
function getConfigPath(projectPath) {
|
|
16877
|
-
return
|
|
16961
|
+
return join6(projectPath, CONFIG_DIR, CONFIG_FILE);
|
|
16878
16962
|
}
|
|
16879
16963
|
function getPolicyPath(projectPath) {
|
|
16880
|
-
return
|
|
16964
|
+
return join6(projectPath, CONFIG_DIR, POLICY_FILE);
|
|
16881
16965
|
}
|
|
16882
16966
|
function getCTODir(projectPath) {
|
|
16883
|
-
return
|
|
16967
|
+
return join6(projectPath, CONFIG_DIR);
|
|
16884
16968
|
}
|
|
16885
16969
|
async function loadConfig(projectPath) {
|
|
16886
16970
|
const configPath = getConfigPath(projectPath);
|
|
16887
|
-
if (!
|
|
16971
|
+
if (!existsSync4(configPath)) {
|
|
16888
16972
|
return { ...DEFAULT_CONFIG };
|
|
16889
16973
|
}
|
|
16890
16974
|
try {
|
|
@@ -16900,20 +16984,20 @@ async function saveConfig(projectPath, config2) {
|
|
|
16900
16984
|
await mkdir(ctoDir, { recursive: true });
|
|
16901
16985
|
const configPath = getConfigPath(projectPath);
|
|
16902
16986
|
const yamlContent = stringifyYAML(config2, { indent: 2 });
|
|
16903
|
-
await
|
|
16987
|
+
await writeFile2(configPath, yamlContent, "utf-8");
|
|
16904
16988
|
return configPath;
|
|
16905
16989
|
}
|
|
16906
16990
|
async function initProjectConfig(projectPath) {
|
|
16907
16991
|
const ctoDir = getCTODir(projectPath);
|
|
16908
|
-
await mkdir(
|
|
16992
|
+
await mkdir(join6(ctoDir, "snapshots"), { recursive: true });
|
|
16909
16993
|
const created = [];
|
|
16910
16994
|
const configPath = getConfigPath(projectPath);
|
|
16911
|
-
if (!
|
|
16995
|
+
if (!existsSync4(configPath)) {
|
|
16912
16996
|
await saveConfig(projectPath, DEFAULT_CONFIG);
|
|
16913
16997
|
created.push(configPath);
|
|
16914
16998
|
}
|
|
16915
16999
|
const policyPath = getPolicyPath(projectPath);
|
|
16916
|
-
if (!
|
|
17000
|
+
if (!existsSync4(policyPath)) {
|
|
16917
17001
|
const defaultPolicy = {
|
|
16918
17002
|
version: "1.0",
|
|
16919
17003
|
name: "default",
|
|
@@ -16956,14 +17040,14 @@ async function initProjectConfig(projectPath) {
|
|
|
16956
17040
|
]
|
|
16957
17041
|
};
|
|
16958
17042
|
const yamlContent = stringifyYAML(defaultPolicy, { indent: 2 });
|
|
16959
|
-
await
|
|
17043
|
+
await writeFile2(policyPath, yamlContent, "utf-8");
|
|
16960
17044
|
created.push(policyPath);
|
|
16961
17045
|
}
|
|
16962
17046
|
return { configPath, policyPath, created };
|
|
16963
17047
|
}
|
|
16964
17048
|
async function loadPolicyFromYAML(projectPath) {
|
|
16965
17049
|
const policyPath = getPolicyPath(projectPath);
|
|
16966
|
-
if (!
|
|
17050
|
+
if (!existsSync4(policyPath)) return null;
|
|
16967
17051
|
try {
|
|
16968
17052
|
const raw = await readFile6(policyPath, "utf-8");
|
|
16969
17053
|
return parseYAML(raw);
|
|
@@ -17031,20 +17115,20 @@ function formatCost(cost) {
|
|
|
17031
17115
|
}
|
|
17032
17116
|
|
|
17033
17117
|
// src/govern/audit.ts
|
|
17034
|
-
import { randomUUID, createHash as
|
|
17118
|
+
import { randomUUID, createHash as createHash5 } from "crypto";
|
|
17035
17119
|
import { readdir as readdir3, chmod } from "fs/promises";
|
|
17036
|
-
import { join as
|
|
17120
|
+
import { join as join7 } from "path";
|
|
17037
17121
|
import { userInfo } from "os";
|
|
17038
17122
|
import { homedir } from "os";
|
|
17039
17123
|
var CTO_DIR = ".cto-ai";
|
|
17040
17124
|
var AUDIT_DIR = "audit";
|
|
17041
17125
|
var MAX_ENTRIES_PER_FILE = 500;
|
|
17042
17126
|
function getAuditDir() {
|
|
17043
|
-
return
|
|
17127
|
+
return join7(homedir(), CTO_DIR, AUDIT_DIR);
|
|
17044
17128
|
}
|
|
17045
17129
|
function getCurrentAuditFile() {
|
|
17046
17130
|
const date5 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
|
|
17047
|
-
return
|
|
17131
|
+
return join7(getAuditDir(), `audit_${date5}.json`);
|
|
17048
17132
|
}
|
|
17049
17133
|
function computeIntegrityHash(entry) {
|
|
17050
17134
|
const payload = JSON.stringify({
|
|
@@ -17055,7 +17139,7 @@ function computeIntegrityHash(entry) {
|
|
|
17055
17139
|
projectPath: entry.projectPath,
|
|
17056
17140
|
details: entry.details
|
|
17057
17141
|
});
|
|
17058
|
-
return
|
|
17142
|
+
return createHash5("sha256").update(payload).digest("hex");
|
|
17059
17143
|
}
|
|
17060
17144
|
async function ensureDir(dirPath) {
|
|
17061
17145
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
@@ -17071,9 +17155,9 @@ async function readJSON(filePath) {
|
|
|
17071
17155
|
}
|
|
17072
17156
|
}
|
|
17073
17157
|
async function writeJSON(filePath, data) {
|
|
17074
|
-
const { writeFile:
|
|
17075
|
-
await ensureDir(
|
|
17076
|
-
await
|
|
17158
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
17159
|
+
await ensureDir(join7(filePath, ".."));
|
|
17160
|
+
await writeFile4(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
17077
17161
|
}
|
|
17078
17162
|
async function logAudit(action, projectPath, details = {}) {
|
|
17079
17163
|
const auditDir = getAuditDir();
|
|
@@ -17122,7 +17206,7 @@ async function getAuditEntries(options = {}) {
|
|
|
17122
17206
|
const limit = options.limit ?? 100;
|
|
17123
17207
|
for (const file2 of auditFiles) {
|
|
17124
17208
|
if (allEntries.length >= limit) break;
|
|
17125
|
-
const entries = await readJSON(
|
|
17209
|
+
const entries = await readJSON(join7(auditDir, file2));
|
|
17126
17210
|
if (!entries) continue;
|
|
17127
17211
|
for (const entry of entries.reverse()) {
|
|
17128
17212
|
if (allEntries.length >= limit) break;
|
|
@@ -17373,7 +17457,7 @@ function fileMatchesCategory(path, category) {
|
|
|
17373
17457
|
}
|
|
17374
17458
|
|
|
17375
17459
|
// src/govern/snapshot.ts
|
|
17376
|
-
import { randomUUID as randomUUID3, createHash as
|
|
17460
|
+
import { randomUUID as randomUUID3, createHash as createHash6 } from "crypto";
|
|
17377
17461
|
import "fs/promises";
|
|
17378
17462
|
function createSnapshot(name, analysis, selection, metadata = {}) {
|
|
17379
17463
|
const files = selection.files.map((f) => ({
|
|
@@ -17466,13 +17550,13 @@ function compareSnapshots(older, newer) {
|
|
|
17466
17550
|
};
|
|
17467
17551
|
}
|
|
17468
17552
|
function hashString(input) {
|
|
17469
|
-
return
|
|
17553
|
+
return createHash6("sha256").update(input).digest("hex").substring(0, 16);
|
|
17470
17554
|
}
|
|
17471
17555
|
|
|
17472
17556
|
// src/mcp/v2.ts
|
|
17473
17557
|
var SNAPSHOTS_DIR = ".cto/snapshots";
|
|
17474
17558
|
async function loadSnapshotFile(projectPath, name) {
|
|
17475
|
-
const filePath =
|
|
17559
|
+
const filePath = join8(projectPath, SNAPSHOTS_DIR, `${name}.json`);
|
|
17476
17560
|
const content = await readFile8(filePath, "utf-8");
|
|
17477
17561
|
return JSON.parse(content);
|
|
17478
17562
|
}
|
|
@@ -17876,10 +17960,10 @@ server.tool(
|
|
|
17876
17960
|
budget: budget ?? config2.interaction.defaultBudget,
|
|
17877
17961
|
createdBy: process.env.USER ?? "unknown"
|
|
17878
17962
|
});
|
|
17879
|
-
const dir =
|
|
17963
|
+
const dir = join8(absPath, SNAPSHOTS_DIR);
|
|
17880
17964
|
await mkdir2(dir, { recursive: true });
|
|
17881
|
-
const filePath =
|
|
17882
|
-
await
|
|
17965
|
+
const filePath = join8(dir, `${name}.json`);
|
|
17966
|
+
await writeFile3(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
17883
17967
|
return {
|
|
17884
17968
|
content: [{
|
|
17885
17969
|
type: "text",
|