codecartographer-pi 0.25.0 → 0.26.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.
Files changed (50) hide show
  1. package/.codecarto/broadside/SKILL.md +4 -1
  2. package/.codecarto/workflow/scaffold-version.yaml +1 -1
  3. package/README.md +2 -2
  4. package/dist/core/broadside/client.d.ts +56 -0
  5. package/dist/core/broadside/client.js +200 -0
  6. package/dist/core/broadside/collect.d.ts +68 -0
  7. package/dist/core/broadside/collect.js +676 -0
  8. package/dist/core/broadside/constants.d.ts +51 -0
  9. package/dist/core/broadside/constants.js +74 -0
  10. package/dist/core/broadside/lenses.d.ts +31 -0
  11. package/dist/core/broadside/lenses.js +312 -0
  12. package/dist/core/broadside/models.d.ts +46 -0
  13. package/dist/core/broadside/models.js +321 -0
  14. package/dist/core/broadside/render.d.ts +20 -0
  15. package/dist/core/broadside/render.js +285 -0
  16. package/dist/core/broadside/repo.d.ts +58 -0
  17. package/dist/core/broadside/repo.js +592 -0
  18. package/dist/core/broadside/requests.d.ts +23 -0
  19. package/dist/core/broadside/requests.js +71 -0
  20. package/dist/core/broadside/results.d.ts +36 -0
  21. package/dist/core/broadside/results.js +163 -0
  22. package/dist/core/broadside/schemas.d.ts +2 -0
  23. package/dist/core/broadside/schemas.js +342 -0
  24. package/dist/core/broadside/state.d.ts +99 -0
  25. package/dist/core/broadside/state.js +384 -0
  26. package/dist/core/broadside/submit.d.ts +30 -0
  27. package/dist/core/broadside/submit.js +350 -0
  28. package/dist/core/broadside/types.d.ts +491 -0
  29. package/dist/core/broadside/types.js +107 -0
  30. package/dist/core/{broadside-verify.d.ts → broadside/verify.d.ts} +23 -2
  31. package/dist/core/{broadside-verify.js → broadside/verify.js} +43 -5
  32. package/dist/core/broadside.d.ts +14 -952
  33. package/dist/core/broadside.js +25 -3726
  34. package/dist/core/completion.js +91 -72
  35. package/dist/core/dashboard-writer.js +9 -1
  36. package/dist/core/index.d.ts +0 -1
  37. package/dist/core/index.js +0 -1
  38. package/dist/core/library.d.ts +24 -1
  39. package/dist/core/library.js +46 -15
  40. package/dist/core/orchestrator-config.js +22 -8
  41. package/dist/core/status.d.ts +42 -23
  42. package/dist/core/status.js +163 -137
  43. package/dist/core/workspace.d.ts +2 -0
  44. package/dist/core/workspace.js +49 -25
  45. package/dist/core/yaml.js +9 -3
  46. package/dist/extensions/codecarto/auto-runner.js +41 -23
  47. package/dist/extensions/codecarto/index.js +9 -4
  48. package/dist/extensions/codecarto/phase-compaction.js +6 -2
  49. package/dist/mcp-server/server.js +15 -4
  50. package/package.json +1 -1
@@ -20,7 +20,12 @@
20
20
  // that only reads keeps the batch property that re-running is always safe.
21
21
  import { readdir, readFile, stat, writeFile } from "node:fs/promises";
22
22
  import { isAbsolute, join, relative, resolve } from "node:path";
23
- import { BROADSIDE_DIR, BROADSIDE_LENS_IDS, broadsideDirFor, defaultReasoningFor, isSlurpable, listRepoFiles, loadBroadsideState, loadSavedLensResults, parseLensJson, persistBroadsideRunMerging, } from "./broadside.js";
23
+ import { BROADSIDE_DIR, BROADSIDE_LENS_IDS } from "./constants.js";
24
+ import { defaultReasoningFor } from "./types.js";
25
+ import { redactSecrets } from "../secrets.js";
26
+ import { isSlurpable, listRepoFiles } from "./repo.js";
27
+ import { broadsideDirFor, loadBroadsideConfig, loadBroadsideState, persistBroadsideRunMerging } from "./state.js";
28
+ import { loadSavedLensResults, parseLensJson } from "./results.js";
24
29
  export const BROADSIDE_CHAT_URL = "https://openrouter.ai/api/v1/chat/completions";
25
30
  /** How many findings `verify` reads by default, most severe first. */
26
31
  export const BROADSIDE_VERIFY_DEFAULT_TOP = 10;
@@ -74,10 +79,29 @@ export function rankVerifiableFindings(stored) {
74
79
  * everything {@link isSlurpable} keeps out of a lens: credential stores, build
75
80
  * output, binaries. A path outside the repository, or one the listing does
76
81
  * not contain, is an error the model sees, not a read.
82
+ *
83
+ * Every line the reader hands back goes through the same secret-redaction
84
+ * pass `submit` runs over its slices (#358): the file list keeps credential
85
+ * *stores* out, but a key in an ordinary source file is exactly what a
86
+ * finding points a verifier at, and the tool result is the upload. `redact`
87
+ * mirrors config.yaml's `redact_secrets`. A pattern the model greps for can
88
+ * still tell it that a line matched; the line it sees is redacted.
77
89
  */
78
- export async function createRepoReader(cwd) {
90
+ export async function createRepoReader(cwd, opts = {}) {
79
91
  const { files } = await listRepoFiles(cwd);
80
92
  const readable = new Set(files.filter(isSlurpable));
93
+ const redact = opts.redact ?? true;
94
+ const redactions = { values: 0, files: new Set() };
95
+ const clean = (rel, line) => {
96
+ if (!redact)
97
+ return line;
98
+ const redaction = redactSecrets(line);
99
+ if (redaction.count > 0) {
100
+ redactions.values += redaction.count;
101
+ redactions.files.add(rel);
102
+ }
103
+ return redaction.text;
104
+ };
81
105
  const confine = (path) => {
82
106
  const abs = resolve(cwd, path);
83
107
  const rel = relative(cwd, abs).split("\\").join("/");
@@ -86,6 +110,7 @@ export async function createRepoReader(cwd) {
86
110
  return rel;
87
111
  };
88
112
  return {
113
+ redactions,
89
114
  async readFile(path, startLine, endLine) {
90
115
  const rel = confine(path);
91
116
  if (!readable.has(rel))
@@ -96,7 +121,7 @@ export async function createRepoReader(cwd) {
96
121
  const end = Math.min(lines.length, requestedEnd, start + READ_FILE_MAX_LINES - 1);
97
122
  if (start > lines.length)
98
123
  return `error: ${rel} has ${lines.length} lines`;
99
- return lines.slice(start - 1, end).map((line, i) => `${start + i}: ${line}`).join("\n");
124
+ return lines.slice(start - 1, end).map((line, i) => `${start + i}: ${clean(rel, line)}`).join("\n");
100
125
  },
101
126
  async grep(pattern, pathPrefix) {
102
127
  let regex;
@@ -123,7 +148,7 @@ export async function createRepoReader(cwd) {
123
148
  const lines = text.split("\n");
124
149
  for (let i = 0; i < lines.length && matches.length < GREP_MAX_MATCHES; i++) {
125
150
  if (regex.test(lines[i]))
126
- matches.push(`${rel}:${i + 1}: ${lines[i]}`);
151
+ matches.push(`${rel}:${i + 1}: ${clean(rel, lines[i])}`);
127
152
  }
128
153
  if (matches.length >= GREP_MAX_MATCHES)
129
154
  break;
@@ -341,7 +366,11 @@ export async function runBroadsideVerify(cwd, apiKey, opts = {}) {
341
366
  const model = opts.model ?? syncModelFor(run.model);
342
367
  const maxCost = opts.maxCost ?? 0;
343
368
  const fetcher = opts.fetcher ?? fetch;
344
- const reader = await createRepoReader(cwd);
369
+ // The same switch submit honours: a repository that turned redaction off
370
+ // for its slices gets raw lines here too, and one that did not never
371
+ // uploads a key through a tool result (#358).
372
+ const config = await loadBroadsideConfig(broadsideDir);
373
+ const reader = await createRepoReader(cwd, { redact: config.redactSecrets });
345
374
  const selected = ranked.slice(0, top);
346
375
  const findings = [];
347
376
  let totalCost = 0;
@@ -376,6 +405,7 @@ export async function runBroadsideVerify(cwd, apiKey, opts = {}) {
376
405
  confirmed: findings.filter((f) => f.verdict === "confirmed").length,
377
406
  cost: totalCost,
378
407
  at: new Date().toISOString(),
408
+ redactedValues: reader.redactions.values,
379
409
  };
380
410
  const result = {
381
411
  runId: run.id,
@@ -386,6 +416,8 @@ export async function runBroadsideVerify(cwd, apiKey, opts = {}) {
386
416
  findings,
387
417
  totalCost,
388
418
  ...(stoppedByCost && { stoppedByCost: true }),
419
+ redactedValues: reader.redactions.values,
420
+ redactedFiles: [...reader.redactions.files].sort(),
389
421
  };
390
422
  await writeFile(join(runDir, "verified.json"), `${JSON.stringify({ ...entry, run_id: run.id, candidates: ranked.length, findings }, null, "\t")}\n`, "utf8");
391
423
  await writeFile(join(runDir, "verified.md"), renderVerifiedMarkdown(result), "utf8");
@@ -407,6 +439,9 @@ export function renderVerifiedMarkdown(result) {
407
439
  "is a lead worth a human's next look, not a validated claim.",
408
440
  "",
409
441
  ];
442
+ if (result.redactedValues > 0) {
443
+ lines.push(`${result.redactedValues} secret-like value(s) were redacted from tool output before upload (${result.redactedFiles.join(", ")}).`, "");
444
+ }
410
445
  for (const f of result.findings) {
411
446
  lines.push(`## ${VERDICT_MARK[f.verdict] ?? "?"} ${f.index}. [${f.severity}] ${f.title}`, "", `- **verdict**: ${f.verdict} (${f.confidence})`, `- **location**: ${f.location}`, `- **lens**: ${f.lensId} (${f.customId})`);
412
447
  if (f.evidence.length > 0)
@@ -428,6 +463,9 @@ export function verifyResultText(result) {
428
463
  for (const f of result.findings) {
429
464
  lines.push(` ${VERDICT_MARK[f.verdict] ?? "?"} [${f.severity}] ${f.title} @ ${f.location} — ${f.verdict}`);
430
465
  }
466
+ if (result.redactedValues > 0) {
467
+ lines.push(` ${result.redactedValues} secret-like value(s) redacted from tool output before upload (${result.redactedFiles.join(", ")}).`);
468
+ }
431
469
  lines.push(`Details in ${result.outputDir}/verified.md. A confirmed finding is a lead for a human's next look, not a validated claim.`);
432
470
  return lines.join("\n");
433
471
  }