@structured-vibe-coding/core 0.9.1 → 0.10.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/dist/index.js CHANGED
@@ -2,35 +2,16 @@ import {
2
2
  AGENT_INTEGRATIONS,
3
3
  AGENT_TARGET_OPTIONS,
4
4
  ALL_AGENT_TARGETS,
5
- ArtifactDefSchema,
6
- ChangeMetaSchema,
7
- ConfigSchema,
8
5
  DEFAULT_INIT_INTEGRATION,
9
- GateSchema,
10
6
  PROTOCOL_MD,
11
7
  SDD_AGENT_ROLES,
12
- SDD_DIR,
13
- StageSchema,
14
- VerifyCommandSchema,
15
- WorkflowSchema,
16
- activePointerPath,
17
8
  activeStages,
18
9
  agentHostPaths,
19
10
  agentKickoffMessage,
20
- archiveDir,
21
11
  buildAgentPrompt,
22
12
  buildContext,
23
13
  changeDirName,
24
- changeMetaPath,
25
- changePath,
26
- changesDir,
27
14
  clearActiveIf,
28
- configPath,
29
- copyDir,
30
- copyDirSkipExisting,
31
- defaultConfig,
32
- domainsDir,
33
- ensureDir,
34
15
  firstActiveStageId,
35
16
  formatStatus,
36
17
  getActiveChangeId,
@@ -40,47 +21,72 @@ import {
40
21
  installAgentIntegration,
41
22
  installAgentIntegrations,
42
23
  integrationKeyFor,
43
- isInitialized,
44
24
  isStageSkipped,
45
25
  listChanges,
46
- listDirs,
47
- listFiles,
48
26
  listWorkflowNames,
49
27
  loadChangeMeta,
50
- loadConfig,
51
28
  loadInstalledAgent,
52
29
  loadWorkflow,
53
- memoryDir,
54
30
  nextStageId,
55
31
  nowIso,
56
32
  optionForTarget,
57
33
  parseAgentTargets,
58
34
  parseIntegration,
59
- pathExists,
60
- readText,
61
- readYaml,
62
35
  recommendWorkflow,
63
36
  refreshActiveAgentContext,
64
37
  removeOtherAgentHosts,
65
- removePath,
66
38
  renderThinAgent,
67
39
  resolveChangeId,
68
40
  resolveStages,
69
41
  saveChangeMeta,
70
- saveConfig,
71
- sddRoot,
72
42
  setActiveChange,
73
43
  shouldAutoSkip,
74
44
  slugify,
75
- templatesDir,
76
45
  todayIsoDate,
77
46
  uniqueChangeDirName,
78
- workflowsDir,
79
- writeAgentHandoff,
47
+ writeAgentHandoff
48
+ } from "./chunk-UAOFNSLQ.js";
49
+ import {
50
+ ArtifactDefSchema,
51
+ ChangeMetaSchema,
52
+ ConfigSchema,
53
+ GateSchema,
54
+ StageSchema,
55
+ VerifyCommandSchema,
56
+ WorkflowSchema,
57
+ defaultConfig,
58
+ isInitialized,
59
+ loadConfig,
60
+ saveConfig
61
+ } from "./chunk-KZHNYGD6.js";
62
+ import {
63
+ SDD_DIR,
64
+ activePointerPath,
65
+ archiveDir,
66
+ changeMetaPath,
67
+ changePath,
68
+ changesDir,
69
+ configPath,
70
+ domainsDir,
71
+ memoryDir,
72
+ sddRoot,
73
+ templatesDir,
74
+ workflowsDir
75
+ } from "./chunk-7MJYPJMR.js";
76
+ import {
77
+ copyDir,
78
+ copyDirSkipExisting,
79
+ ensureDir,
80
+ listDirs,
81
+ listFiles,
82
+ pathExists,
83
+ readText,
84
+ readYaml,
85
+ removePath,
80
86
  writeText,
81
87
  writeTextIfMissing,
82
88
  writeYaml
83
- } from "./chunk-M245MC6E.js";
89
+ } from "./chunk-EPFURATC.js";
84
90
 
85
91
  // src/change.ts
86
92
  import { join as join2 } from "pathe";
@@ -277,7 +283,7 @@ async function createChange(input) {
277
283
  await materializeStageArtifacts(projectRoot, config, id, workflow, meta, firstStage);
278
284
  await setActiveChange(projectRoot, config, id);
279
285
  try {
280
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-BA7WVN35.js");
286
+ const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-IB37YJX6.js");
281
287
  await refreshActiveAgentContext2(projectRoot);
282
288
  } catch {
283
289
  }
@@ -360,7 +366,7 @@ async function advanceStage(projectRoot, config, changeId, opts) {
360
366
  stage2
361
367
  );
362
368
  try {
363
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-BA7WVN35.js");
369
+ const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-IB37YJX6.js");
364
370
  await refreshActiveAgentContext2(projectRoot);
365
371
  } catch {
366
372
  }
@@ -407,7 +413,7 @@ async function advanceStage(projectRoot, config, changeId, opts) {
407
413
  stage
408
414
  );
409
415
  try {
410
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-BA7WVN35.js");
416
+ const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-IB37YJX6.js");
411
417
  await refreshActiveAgentContext2(projectRoot);
412
418
  } catch {
413
419
  }
@@ -921,13 +927,1606 @@ ${output}`
921
927
  ok
922
928
  };
923
929
  }
930
+
931
+ // src/code-context/index.ts
932
+ import { extname as extname4, join as join8, normalize as normalize5 } from "pathe";
933
+
934
+ // src/code-context/adapters/registry.ts
935
+ import { extname as extname2 } from "pathe";
936
+
937
+ // src/code-context/adapters/typescript.ts
938
+ import ts from "typescript";
939
+ import { extname } from "pathe";
940
+ var TS_JS_EXTENSIONS = [
941
+ ".ts",
942
+ ".tsx",
943
+ ".js",
944
+ ".jsx",
945
+ ".mts",
946
+ ".cts",
947
+ ".mjs",
948
+ ".cjs"
949
+ ];
950
+ function scriptKindForPath(filePath) {
951
+ const ext = extname(filePath).toLowerCase();
952
+ switch (ext) {
953
+ case ".tsx":
954
+ return ts.ScriptKind.TSX;
955
+ case ".jsx":
956
+ return ts.ScriptKind.JSX;
957
+ case ".js":
958
+ case ".mjs":
959
+ case ".cjs":
960
+ return ts.ScriptKind.JS;
961
+ case ".cts":
962
+ case ".mts":
963
+ case ".ts":
964
+ default:
965
+ return ts.ScriptKind.TS;
966
+ }
967
+ }
968
+ function lineOf(sf, pos) {
969
+ return sf.getLineAndCharacterOfPosition(pos).line + 1;
970
+ }
971
+ function hasExportModifier(node) {
972
+ const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
973
+ return Boolean(mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword));
974
+ }
975
+ function isTopLevel(node, sf) {
976
+ let p = node.parent;
977
+ while (p && p !== sf) {
978
+ if (ts.isFunctionDeclaration(p) || ts.isFunctionExpression(p) || ts.isArrowFunction(p) || ts.isMethodDeclaration(p) || ts.isConstructorDeclaration(p) || ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p)) {
979
+ return false;
980
+ }
981
+ if (ts.isClassDeclaration(p) || ts.isClassExpression(p)) {
982
+ return false;
983
+ }
984
+ p = p.parent;
985
+ }
986
+ return true;
987
+ }
988
+ function extractFromSource(filePath, source, repoRelativePath) {
989
+ const sf = ts.createSourceFile(
990
+ filePath,
991
+ source,
992
+ ts.ScriptTarget.Latest,
993
+ /*setParentNodes*/
994
+ true,
995
+ scriptKindForPath(filePath)
996
+ );
997
+ const symbols = [];
998
+ const imports = [];
999
+ const exports = [];
1000
+ const references = [];
1001
+ const pushSymbol = (name, node, kind, exported) => {
1002
+ if (!name) return;
1003
+ symbols.push({
1004
+ name,
1005
+ kind,
1006
+ filePath: repoRelativePath,
1007
+ startLine: lineOf(sf, node.getStart(sf, false)),
1008
+ endLine: lineOf(sf, node.getEnd()),
1009
+ exported
1010
+ });
1011
+ if (exported) {
1012
+ exports.push({ name, kind });
1013
+ }
1014
+ };
1015
+ const visit = (node) => {
1016
+ if (ts.isImportDeclaration(node)) {
1017
+ const spec = node.moduleSpecifier;
1018
+ if (ts.isStringLiteral(spec)) {
1019
+ const names = [];
1020
+ const clause = node.importClause;
1021
+ if (clause?.name) names.push(clause.name.text);
1022
+ if (clause?.namedBindings) {
1023
+ if (ts.isNamespaceImport(clause.namedBindings)) {
1024
+ names.push(clause.namedBindings.name.text);
1025
+ } else if (ts.isNamedImports(clause.namedBindings)) {
1026
+ for (const el of clause.namedBindings.elements) {
1027
+ names.push(el.name.text);
1028
+ }
1029
+ }
1030
+ }
1031
+ imports.push({ from: spec.text, names: names.length ? names : void 0 });
1032
+ }
1033
+ }
1034
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
1035
+ imports.push({ from: node.moduleSpecifier.text, names: ["*"] });
1036
+ }
1037
+ if (ts.isFunctionDeclaration(node) && node.name && isTopLevel(node, sf)) {
1038
+ pushSymbol(node.name.text, node, "function", hasExportModifier(node));
1039
+ }
1040
+ if (ts.isClassDeclaration(node) && node.name && isTopLevel(node, sf)) {
1041
+ pushSymbol(node.name.text, node, "class", hasExportModifier(node));
1042
+ }
1043
+ if (ts.isInterfaceDeclaration(node) && isTopLevel(node, sf)) {
1044
+ pushSymbol(node.name.text, node, "interface", hasExportModifier(node));
1045
+ }
1046
+ if (ts.isTypeAliasDeclaration(node) && isTopLevel(node, sf)) {
1047
+ pushSymbol(node.name.text, node, "type", hasExportModifier(node));
1048
+ }
1049
+ if (ts.isEnumDeclaration(node) && isTopLevel(node, sf)) {
1050
+ pushSymbol(node.name.text, node, "type", hasExportModifier(node));
1051
+ }
1052
+ if (ts.isVariableStatement(node) && isTopLevel(node, sf)) {
1053
+ const exported = hasExportModifier(node);
1054
+ for (const decl of node.declarationList.declarations) {
1055
+ if (!ts.isIdentifier(decl.name)) continue;
1056
+ const name = decl.name.text;
1057
+ const init = decl.initializer;
1058
+ const isFn = init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init));
1059
+ if (exported || isFn) {
1060
+ pushSymbol(name, decl, isFn ? "function" : "const", exported);
1061
+ }
1062
+ }
1063
+ }
1064
+ if (ts.isExportAssignment(node) && !node.isExportEquals) {
1065
+ exports.push({ name: "default", kind: "export" });
1066
+ }
1067
+ if (ts.isIdentifier(node) && node.parent && !ts.isImportSpecifier(node.parent)) {
1068
+ const parent = node.parent;
1069
+ if (ts.isCallExpression(parent) || ts.isPropertyAccessExpression(parent) || ts.isTypeReferenceNode(parent)) {
1070
+ references.push({ name: node.text, line: lineOf(sf, node.getStart(sf)) });
1071
+ }
1072
+ }
1073
+ ts.forEachChild(node, visit);
1074
+ };
1075
+ visit(sf);
1076
+ return {
1077
+ ok: true,
1078
+ language: "typescript",
1079
+ symbols,
1080
+ imports,
1081
+ exports,
1082
+ references: references.slice(0, 200)
1083
+ // bound noise
1084
+ };
1085
+ }
1086
+ var typescriptAdapter = {
1087
+ id: "typescript",
1088
+ extensions: [...TS_JS_EXTENSIONS],
1089
+ extract(input) {
1090
+ try {
1091
+ return extractFromSource(input.filePath, input.source, input.filePath);
1092
+ } catch (err) {
1093
+ return {
1094
+ ok: false,
1095
+ language: "typescript",
1096
+ symbols: [],
1097
+ imports: [],
1098
+ exports: [],
1099
+ errors: [err instanceof Error ? err.message : String(err)]
1100
+ };
1101
+ }
1102
+ }
1103
+ };
1104
+ function isTypescriptAdapterAvailable() {
1105
+ try {
1106
+ return typeof ts.createSourceFile === "function";
1107
+ } catch {
1108
+ return false;
1109
+ }
1110
+ }
1111
+
1112
+ // src/code-context/adapters/registry.ts
1113
+ var AdapterRegistry = class {
1114
+ byExt = /* @__PURE__ */ new Map();
1115
+ constructor(adapters) {
1116
+ const list = adapters ?? [typescriptAdapter];
1117
+ for (const a of list) {
1118
+ for (const ext of a.extensions) {
1119
+ this.byExt.set(ext.toLowerCase(), a);
1120
+ }
1121
+ }
1122
+ }
1123
+ getByPath(filePath) {
1124
+ const ext = extname2(filePath).toLowerCase();
1125
+ return this.byExt.get(ext);
1126
+ }
1127
+ hasExtension(ext) {
1128
+ return this.byExt.has(ext.toLowerCase());
1129
+ }
1130
+ };
1131
+
1132
+ // src/code-context/formatter.ts
1133
+ function fenceLang(filePath) {
1134
+ const lower = filePath.toLowerCase();
1135
+ if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) return "tsx";
1136
+ if (lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts")) {
1137
+ return "ts";
1138
+ }
1139
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) {
1140
+ return "js";
1141
+ }
1142
+ return "";
1143
+ }
1144
+ function formatMarkdown(input) {
1145
+ const lines = [];
1146
+ lines.push("# Code context");
1147
+ lines.push("");
1148
+ lines.push(
1149
+ "> Generated on-demand for agents. Not a substitute for process context"
1150
+ );
1151
+ lines.push(
1152
+ "> (.sdd/protocol.md, active-context, change packs)."
1153
+ );
1154
+ lines.push(
1155
+ "> Regenerable \u2014 run `sdd context --out change` (or pass `--path` / `--symbol`)."
1156
+ );
1157
+ lines.push("");
1158
+ lines.push("## Summary");
1159
+ lines.push(
1160
+ `- Change: ${input.changeTitle ?? input.plan.changeId ?? "(none)"}`
1161
+ );
1162
+ lines.push(
1163
+ `- Focus paths: ${input.summary.focusPaths.length ? input.summary.focusPaths.join(", ") : "(none)"}`
1164
+ );
1165
+ lines.push(
1166
+ `- Focus symbols: ${input.summary.focusSymbols.length ? input.summary.focusSymbols.join(", ") : "(none)"}`
1167
+ );
1168
+ lines.push(
1169
+ `- Keywords: ${input.summary.keywords.length ? input.summary.keywords.join(", ") : "(none)"}`
1170
+ );
1171
+ lines.push(
1172
+ `- Files analyzed: ${input.summary.filesAnalyzed} \xB7 Symbols: ${input.summary.symbolsExtracted} \xB7 Slices: ${input.summary.slicesEmitted}`
1173
+ );
1174
+ lines.push(`- Truncated: ${input.summary.truncated ? "yes" : "no"}`);
1175
+ if (input.plan.notes.length) {
1176
+ lines.push(`- Notes: ${input.plan.notes.join("; ")}`);
1177
+ }
1178
+ lines.push("");
1179
+ lines.push("## Gaps");
1180
+ if (!input.gaps.length) {
1181
+ lines.push("_None._");
1182
+ } else {
1183
+ for (const g of input.gaps) {
1184
+ const pathPart = g.path ? ` \u2014 \`${g.path}\`` : "";
1185
+ lines.push(`- (\`${g.code}\`) ${g.message}${pathPart}`);
1186
+ }
1187
+ }
1188
+ lines.push("");
1189
+ lines.push("## Slices");
1190
+ lines.push("");
1191
+ let truncated = input.summary.truncated;
1192
+ const maxLines = input.maxOutputLines ?? 8e3;
1193
+ const maxTokens = input.maxTokensApprox ?? 12e3;
1194
+ let approxTokens = 0;
1195
+ for (const slice of input.slices) {
1196
+ if (lines.length >= maxLines) {
1197
+ truncated = true;
1198
+ break;
1199
+ }
1200
+ const header = `### \`${slice.filePath}\`${slice.symbolName ? ` \xB7 \`${slice.symbolName}\`` : ""} (lines ${slice.startLine}\u2013${slice.endLine}) \xB7 ${slice.reason}`;
1201
+ lines.push(header);
1202
+ lines.push("```" + fenceLang(slice.filePath));
1203
+ lines.push(slice.body);
1204
+ lines.push("```");
1205
+ lines.push("");
1206
+ approxTokens += Math.ceil((header.length + slice.body.length) / 4);
1207
+ if (approxTokens > maxTokens) {
1208
+ truncated = true;
1209
+ break;
1210
+ }
1211
+ }
1212
+ if (!input.slices.length) {
1213
+ lines.push("_No slices emitted._");
1214
+ lines.push("");
1215
+ }
1216
+ lines.push("## Structural notes");
1217
+ const topFiles = [
1218
+ ...new Set(input.slices.map((s) => s.filePath))
1219
+ ].slice(0, 15);
1220
+ if (topFiles.length) {
1221
+ lines.push(`- Ranked files (sample): ${topFiles.join(", ")}`);
1222
+ } else {
1223
+ lines.push("- No structural neighborhood (empty analysis set).");
1224
+ }
1225
+ lines.push("");
1226
+ let markdown = lines.join("\n");
1227
+ if (Math.ceil(markdown.length / 4) > maxTokens) {
1228
+ truncated = true;
1229
+ markdown = markdown.slice(0, maxTokens * 4) + "\n\n\u2026(truncated)\n";
1230
+ }
1231
+ return { markdown, truncated };
1232
+ }
1233
+ function formatJsonSummary(summary, gaps, slices, ok) {
1234
+ return JSON.stringify(
1235
+ {
1236
+ ok,
1237
+ summary,
1238
+ gaps,
1239
+ slices: slices.map((s) => ({
1240
+ id: s.id,
1241
+ filePath: s.filePath,
1242
+ startLine: s.startLine,
1243
+ endLine: s.endLine,
1244
+ symbolName: s.symbolName,
1245
+ reason: s.reason
1246
+ }))
1247
+ },
1248
+ null,
1249
+ 2
1250
+ );
1251
+ }
1252
+
1253
+ // src/code-context/focus.ts
1254
+ import { promises as fs } from "fs";
1255
+ import { isAbsolute, join as join6, normalize as normalize3, relative } from "pathe";
1256
+
1257
+ // src/code-context/ignore.ts
1258
+ import { basename as basename2, extname as extname3, normalize as normalize2 } from "pathe";
1259
+
1260
+ // src/code-context/secrets.ts
1261
+ import { basename, normalize } from "pathe";
1262
+ var SECRET_BASENAME_PATTERNS = [
1263
+ /^\.env$/i,
1264
+ /^\.env\..+/i,
1265
+ /\.pem$/i,
1266
+ /\.key$/i,
1267
+ /id_rsa/i,
1268
+ /credentials/i,
1269
+ /secret/i
1270
+ ];
1271
+ function isSecretPath(filePath) {
1272
+ const norm = normalize(filePath).replace(/\\/g, "/");
1273
+ const parts = norm.split("/").filter(Boolean);
1274
+ if (parts.includes(".git")) return true;
1275
+ if (norm.includes("/.git/") || norm.startsWith(".git/") || norm === ".git") {
1276
+ return true;
1277
+ }
1278
+ const base = basename(norm);
1279
+ return SECRET_BASENAME_PATTERNS.some((re) => re.test(base));
1280
+ }
1281
+
1282
+ // src/code-context/ignore.ts
1283
+ var IGNORED_DIR_SEGMENTS = /* @__PURE__ */ new Set([
1284
+ "node_modules",
1285
+ "dist",
1286
+ "coverage",
1287
+ ".turbo",
1288
+ ".next",
1289
+ "build",
1290
+ ".cache"
1291
+ ]);
1292
+ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
1293
+ ".png",
1294
+ ".jpg",
1295
+ ".jpeg",
1296
+ ".gif",
1297
+ ".webp",
1298
+ ".ico",
1299
+ ".svg",
1300
+ ".woff",
1301
+ ".woff2",
1302
+ ".ttf",
1303
+ ".eot",
1304
+ ".pdf",
1305
+ ".zip",
1306
+ ".tar",
1307
+ ".gz",
1308
+ ".tgz",
1309
+ ".7z",
1310
+ ".rar",
1311
+ ".exe",
1312
+ ".dll",
1313
+ ".so",
1314
+ ".dylib",
1315
+ ".bin",
1316
+ ".wasm",
1317
+ ".mp3",
1318
+ ".mp4",
1319
+ ".mov",
1320
+ ".lock"
1321
+ ]);
1322
+ function isIgnoredPath(filePath) {
1323
+ const norm = normalize2(filePath).replace(/\\/g, "/");
1324
+ const parts = norm.split("/").filter(Boolean);
1325
+ for (const p of parts) {
1326
+ if (IGNORED_DIR_SEGMENTS.has(p)) return true;
1327
+ }
1328
+ const ext = extname3(basename2(norm)).toLowerCase();
1329
+ if (BINARY_EXTENSIONS.has(ext)) return true;
1330
+ const base = basename2(norm);
1331
+ if (base === "package-lock.json" || base === "pnpm-lock.yaml" || base === "yarn.lock" || base === "bun.lockb") {
1332
+ return true;
1333
+ }
1334
+ return false;
1335
+ }
1336
+ function classifyPath(filePath) {
1337
+ if (isSecretPath(filePath)) return "secret";
1338
+ if (isIgnoredPath(filePath)) return "ignored";
1339
+ return "allowed";
1340
+ }
1341
+
1342
+ // src/code-context/focus.ts
1343
+ var KEYWORD_STOPWORDS = new Set(
1344
+ [
1345
+ "a",
1346
+ "an",
1347
+ "the",
1348
+ "and",
1349
+ "or",
1350
+ "to",
1351
+ "for",
1352
+ "of",
1353
+ "in",
1354
+ "on",
1355
+ "at",
1356
+ "by",
1357
+ "with",
1358
+ "from",
1359
+ "as",
1360
+ "is",
1361
+ "are",
1362
+ "be",
1363
+ "this",
1364
+ "that",
1365
+ "it",
1366
+ "we",
1367
+ "i",
1368
+ "you",
1369
+ "our",
1370
+ "add",
1371
+ "want",
1372
+ "feature",
1373
+ "using",
1374
+ "like",
1375
+ "lets",
1376
+ "let",
1377
+ "explore",
1378
+ "proper",
1379
+ "get",
1380
+ "code",
1381
+ "context",
1382
+ "agent",
1383
+ "agents",
1384
+ "ai",
1385
+ "sota",
1386
+ "state",
1387
+ "art",
1388
+ "techniques",
1389
+ "change",
1390
+ "stage",
1391
+ "implement",
1392
+ "please",
1393
+ "should",
1394
+ "must",
1395
+ "will",
1396
+ "can",
1397
+ "via",
1398
+ "into",
1399
+ "over",
1400
+ "under"
1401
+ ].map((s) => s.toLowerCase())
1402
+ );
1403
+ var SOURCE_ROOTS = ["packages", "src", "lib"];
1404
+ var PATH_LIKE_RE = /(?:packages|src|lib)\/[\w./@+-]+\.\w{1,10}|`([^`]+\.\w{1,10})`|([\w./@+-]+\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs))/g;
1405
+ function tokenizeKeywords(text) {
1406
+ const raw = text.toLowerCase().replace(/[^a-z0-9_./-]+/g, " ").split(/\s+/).filter(Boolean);
1407
+ const out = [];
1408
+ const seen = /* @__PURE__ */ new Set();
1409
+ for (const t of raw) {
1410
+ if (t.length < 3) continue;
1411
+ if (/^\d+$/.test(t)) continue;
1412
+ if (KEYWORD_STOPWORDS.has(t)) continue;
1413
+ if (seen.has(t)) continue;
1414
+ seen.add(t);
1415
+ out.push(t);
1416
+ }
1417
+ return out;
1418
+ }
1419
+ function extractPathLikeTokens(text) {
1420
+ const found = [];
1421
+ const seen = /* @__PURE__ */ new Set();
1422
+ let m;
1423
+ const re = new RegExp(PATH_LIKE_RE.source, "g");
1424
+ while ((m = re.exec(text)) !== null) {
1425
+ const p = (m[1] || m[2] || m[0]).replace(/`/g, "");
1426
+ if (!p || seen.has(p)) continue;
1427
+ seen.add(p);
1428
+ found.push(p);
1429
+ }
1430
+ return found;
1431
+ }
1432
+ function toRepoRelative(projectRoot, p) {
1433
+ const abs = isAbsolute(p) ? p : join6(projectRoot, p);
1434
+ const rel = relative(projectRoot, abs);
1435
+ return normalize3(rel).replace(/\\/g, "/");
1436
+ }
1437
+ function toAbsolute(projectRoot, p) {
1438
+ return isAbsolute(p) ? p : join6(projectRoot, p);
1439
+ }
1440
+ async function walkFiles(absDir, projectRoot, maxFiles, out, gaps) {
1441
+ if (out.length >= maxFiles) return;
1442
+ if (!await pathExists(absDir)) return;
1443
+ let entries;
1444
+ try {
1445
+ entries = await fs.readdir(absDir, { withFileTypes: true });
1446
+ } catch {
1447
+ return;
1448
+ }
1449
+ for (const entry of entries) {
1450
+ if (out.length >= maxFiles) return;
1451
+ const abs = join6(absDir, entry.name);
1452
+ const rel = toRepoRelative(projectRoot, abs);
1453
+ const kind = classifyPath(rel);
1454
+ if (kind === "secret" || kind === "ignored") continue;
1455
+ if (entry.isDirectory()) {
1456
+ if (entry.name.startsWith(".") && entry.name !== ".") continue;
1457
+ await walkFiles(abs, projectRoot, maxFiles, out, gaps);
1458
+ } else if (entry.isFile()) {
1459
+ out.push(rel);
1460
+ }
1461
+ }
1462
+ }
1463
+ async function collectUnderPaths(projectRoot, paths, maxWalk, gaps) {
1464
+ const files = [];
1465
+ const seen = /* @__PURE__ */ new Set();
1466
+ for (const p of paths) {
1467
+ const abs = toAbsolute(projectRoot, p);
1468
+ const rel = toRepoRelative(projectRoot, abs);
1469
+ const kind = classifyPath(rel);
1470
+ if (kind === "secret") {
1471
+ gaps.push({
1472
+ code: "path_denied_secret",
1473
+ message: `Skipped potential secret path: ${rel}`,
1474
+ path: rel
1475
+ });
1476
+ continue;
1477
+ }
1478
+ if (kind === "ignored") {
1479
+ gaps.push({
1480
+ code: "path_ignored",
1481
+ message: `Skipped ignored path: ${rel}`,
1482
+ path: rel
1483
+ });
1484
+ continue;
1485
+ }
1486
+ if (!await pathExists(abs)) {
1487
+ gaps.push({
1488
+ code: "path_missing",
1489
+ message: `Path not found: ${rel}`,
1490
+ path: rel
1491
+ });
1492
+ continue;
1493
+ }
1494
+ const st = await fs.stat(abs);
1495
+ if (st.isDirectory()) {
1496
+ await walkFiles(abs, projectRoot, maxWalk, files, gaps);
1497
+ } else if (st.isFile()) {
1498
+ if (!seen.has(rel)) {
1499
+ seen.add(rel);
1500
+ files.push(rel);
1501
+ }
1502
+ }
1503
+ }
1504
+ const uniq = [];
1505
+ const s = /* @__PURE__ */ new Set();
1506
+ for (const f of files) {
1507
+ if (s.has(f)) continue;
1508
+ s.add(f);
1509
+ uniq.push(f);
1510
+ }
1511
+ return uniq;
1512
+ }
1513
+ async function readBounded(absPath, maxChars) {
1514
+ if (!await pathExists(absPath)) return null;
1515
+ try {
1516
+ const text = await readText(absPath);
1517
+ return text.slice(0, maxChars);
1518
+ } catch {
1519
+ return null;
1520
+ }
1521
+ }
1522
+ async function resolveChangeMeta(projectRoot, changeId, gaps) {
1523
+ let config;
1524
+ try {
1525
+ config = await loadConfig(projectRoot);
1526
+ } catch {
1527
+ return null;
1528
+ }
1529
+ let id = changeId ?? null;
1530
+ if (!id) {
1531
+ try {
1532
+ id = await getActiveChangeId(projectRoot, config);
1533
+ } catch {
1534
+ id = null;
1535
+ }
1536
+ }
1537
+ if (!id) {
1538
+ gaps.push({
1539
+ code: "no_active_change",
1540
+ message: "No active change; focusing without change-pack signals."
1541
+ });
1542
+ return null;
1543
+ }
1544
+ const cpath = changePath(projectRoot, config, id);
1545
+ const metaPath = join6(cpath, "meta.yaml");
1546
+ if (!await pathExists(metaPath)) {
1547
+ if (!await pathExists(cpath)) {
1548
+ gaps.push({
1549
+ code: "change_not_found",
1550
+ message: `Change not found: ${id}`,
1551
+ path: id
1552
+ });
1553
+ return null;
1554
+ }
1555
+ }
1556
+ try {
1557
+ const meta = await readYaml(metaPath);
1558
+ return {
1559
+ id,
1560
+ title: meta.title ?? id,
1561
+ path: cpath,
1562
+ stage: meta.stage
1563
+ };
1564
+ } catch {
1565
+ gaps.push({
1566
+ code: "change_not_found",
1567
+ message: `Could not load change meta: ${id}`,
1568
+ path: id
1569
+ });
1570
+ return null;
1571
+ }
1572
+ }
1573
+ async function searchByKeywords(projectRoot, keywords, symbols, maxFiles) {
1574
+ const out = [];
1575
+ const terms = [...keywords, ...symbols.map((s) => s.toLowerCase())];
1576
+ if (!terms.length) return out;
1577
+ for (const root of SOURCE_ROOTS) {
1578
+ const abs = join6(projectRoot, root);
1579
+ if (!await pathExists(abs)) continue;
1580
+ const files = [];
1581
+ await walkFiles(abs, projectRoot, maxFiles * 4, files, []);
1582
+ for (const rel of files) {
1583
+ if (out.length >= maxFiles) return out;
1584
+ const lower = rel.toLowerCase();
1585
+ const base = lower.split("/").pop() ?? lower;
1586
+ if (terms.some((t) => lower.includes(t) || base.includes(t))) {
1587
+ out.push(rel);
1588
+ continue;
1589
+ }
1590
+ if (symbols.length) {
1591
+ try {
1592
+ const text = await readText(join6(projectRoot, rel));
1593
+ if (symbols.some((s) => text.includes(s))) {
1594
+ out.push(rel);
1595
+ }
1596
+ } catch {
1597
+ }
1598
+ }
1599
+ }
1600
+ }
1601
+ return out;
1602
+ }
1603
+ async function resolveFocus(request, options) {
1604
+ const gaps = [];
1605
+ const notes = [];
1606
+ const projectRoot = request.projectRoot;
1607
+ const maxWalk = options?.maxCandidateWalk ?? 200;
1608
+ const seedSymbols = [...request.symbols ?? []].filter(Boolean);
1609
+ let keywords = tokenizeKeywords(request.query ?? "");
1610
+ let seedPaths = [];
1611
+ let candidatePaths = [];
1612
+ let changeId = request.changeId ?? null;
1613
+ if (request.paths?.length) {
1614
+ notes.push(`Focus from explicit paths (${request.paths.length})`);
1615
+ const files = await collectUnderPaths(projectRoot, request.paths, maxWalk, gaps);
1616
+ seedPaths = [...files];
1617
+ candidatePaths = [...files];
1618
+ }
1619
+ const change = await resolveChangeMeta(projectRoot, request.changeId, gaps);
1620
+ if (change) {
1621
+ changeId = change.id;
1622
+ const titleKw = tokenizeKeywords(change.title);
1623
+ for (const k of titleKw) {
1624
+ if (!keywords.includes(k)) keywords.push(k);
1625
+ }
1626
+ notes.push(`Change signals from: ${change.id}`);
1627
+ for (const artifact of ["tasks.md", "feature.md", "stories.md", "lld.md"]) {
1628
+ const body = await readBounded(join6(change.path, artifact), 8e3);
1629
+ if (!body) continue;
1630
+ if (!request.paths?.length) {
1631
+ for (const p of extractPathLikeTokens(body)) {
1632
+ const abs = toAbsolute(projectRoot, p);
1633
+ if (await pathExists(abs)) {
1634
+ const rel = toRepoRelative(projectRoot, abs);
1635
+ const kind = classifyPath(rel);
1636
+ if (kind === "allowed" && !candidatePaths.includes(rel)) {
1637
+ candidatePaths.push(rel);
1638
+ if (!seedPaths.includes(rel)) seedPaths.push(rel);
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1643
+ if (artifact === "tasks.md" && body) {
1644
+ const more = tokenizeKeywords(body.slice(0, 2e3));
1645
+ for (const k of more.slice(0, 12)) {
1646
+ if (!keywords.includes(k)) keywords.push(k);
1647
+ }
1648
+ }
1649
+ }
1650
+ if (!request.paths?.length && (keywords.length || seedSymbols.length)) {
1651
+ const found = await searchByKeywords(
1652
+ projectRoot,
1653
+ keywords,
1654
+ seedSymbols,
1655
+ maxWalk
1656
+ );
1657
+ for (const f of found) {
1658
+ if (!candidatePaths.includes(f)) candidatePaths.push(f);
1659
+ if (!seedPaths.includes(f) && seedPaths.length < 20) seedPaths.push(f);
1660
+ }
1661
+ if (found.length) notes.push(`Keyword/path search found ${found.length} candidates`);
1662
+ }
1663
+ }
1664
+ if (!request.paths?.length && !change && (request.query || seedSymbols.length)) {
1665
+ notes.push("Focus from query/symbols only");
1666
+ const found = await searchByKeywords(
1667
+ projectRoot,
1668
+ keywords,
1669
+ seedSymbols,
1670
+ maxWalk
1671
+ );
1672
+ for (const f of found) {
1673
+ if (!candidatePaths.includes(f)) candidatePaths.push(f);
1674
+ if (!seedPaths.includes(f)) seedPaths.push(f);
1675
+ }
1676
+ }
1677
+ const filteredSeeds = [];
1678
+ for (const p of seedPaths) {
1679
+ const kind = classifyPath(p);
1680
+ if (kind !== "allowed") continue;
1681
+ if (await pathExists(toAbsolute(projectRoot, p))) filteredSeeds.push(p);
1682
+ }
1683
+ const filteredCandidates = [];
1684
+ const seen = /* @__PURE__ */ new Set();
1685
+ for (const p of [...filteredSeeds, ...candidatePaths]) {
1686
+ if (seen.has(p)) continue;
1687
+ const kind = classifyPath(p);
1688
+ if (kind !== "allowed") continue;
1689
+ if (!await pathExists(toAbsolute(projectRoot, p))) continue;
1690
+ seen.add(p);
1691
+ filteredCandidates.push(p);
1692
+ }
1693
+ keywords = keywords.slice(0, 40);
1694
+ if (seedSymbols.length && !filteredCandidates.length) {
1695
+ notes.push("Symbols provided but no matching files found yet");
1696
+ }
1697
+ return {
1698
+ plan: {
1699
+ seedPaths: filteredSeeds,
1700
+ seedSymbols,
1701
+ keywords,
1702
+ candidatePaths: filteredCandidates,
1703
+ changeId,
1704
+ notes
1705
+ },
1706
+ gaps
1707
+ };
1708
+ }
1709
+
1710
+ // src/code-context/graph.ts
1711
+ import { dirname as dirname2, join as join7, normalize as normalize4 } from "pathe";
1712
+ var GraphLite = class {
1713
+ files = /* @__PURE__ */ new Map();
1714
+ edges = [];
1715
+ add(path, source, extract) {
1716
+ const norm = normalize4(path).replace(/\\/g, "/");
1717
+ this.files.set(norm, { path: norm, extract, source });
1718
+ for (const imp of extract.imports) {
1719
+ this.edges.push({
1720
+ from: norm,
1721
+ to: imp.from,
1722
+ names: imp.names
1723
+ });
1724
+ }
1725
+ }
1726
+ allSymbols() {
1727
+ const out = [];
1728
+ for (const node of this.files.values()) {
1729
+ out.push(...node.extract.symbols);
1730
+ }
1731
+ return out;
1732
+ }
1733
+ getSource(path) {
1734
+ return this.files.get(normalize4(path).replace(/\\/g, "/"))?.source;
1735
+ }
1736
+ /**
1737
+ * Resolve relative import specifier to a known graph file path, if present.
1738
+ */
1739
+ resolveImportTarget(fromFile, specifier) {
1740
+ if (!specifier.startsWith(".")) return void 0;
1741
+ const base = dirname2(fromFile);
1742
+ const joined = normalize4(join7(base, specifier)).replace(/\\/g, "/");
1743
+ const candidates = [
1744
+ joined,
1745
+ `${joined}.ts`,
1746
+ `${joined}.tsx`,
1747
+ `${joined}.js`,
1748
+ `${joined}.jsx`,
1749
+ `${joined}.mts`,
1750
+ `${joined}.cts`,
1751
+ `${joined}/index.ts`,
1752
+ `${joined}/index.js`
1753
+ ];
1754
+ for (const c of candidates) {
1755
+ if (this.files.has(c)) return c;
1756
+ }
1757
+ for (const [p] of this.files) {
1758
+ if (p === joined || p.startsWith(joined + ".")) return p;
1759
+ }
1760
+ return void 0;
1761
+ }
1762
+ /** Import hop distance from any seed file (0 = seed, 1 = direct import, …). */
1763
+ hopDistance(filePath, seedPaths, maxHops) {
1764
+ const norm = normalize4(filePath).replace(/\\/g, "/");
1765
+ const seeds = new Set(seedPaths.map((s) => normalize4(s).replace(/\\/g, "/")));
1766
+ if (seeds.has(norm)) return 0;
1767
+ let frontier = new Set(seeds);
1768
+ const visited = new Set(seeds);
1769
+ for (let hop = 1; hop <= maxHops; hop++) {
1770
+ const next = /* @__PURE__ */ new Set();
1771
+ for (const f of frontier) {
1772
+ for (const e of this.edges) {
1773
+ if (e.from !== f) continue;
1774
+ const target = this.resolveImportTarget(e.from, e.to);
1775
+ if (!target || visited.has(target)) continue;
1776
+ visited.add(target);
1777
+ next.add(target);
1778
+ if (target === norm) return hop;
1779
+ }
1780
+ }
1781
+ frontier = next;
1782
+ if (!frontier.size) break;
1783
+ }
1784
+ return null;
1785
+ }
1786
+ importNeighbors(filePath) {
1787
+ const norm = normalize4(filePath).replace(/\\/g, "/");
1788
+ const out = [];
1789
+ for (const e of this.edges) {
1790
+ if (e.from !== norm) continue;
1791
+ const t = this.resolveImportTarget(e.from, e.to);
1792
+ if (t) out.push(t);
1793
+ }
1794
+ return out;
1795
+ }
1796
+ };
1797
+
1798
+ // src/code-context/ranker.ts
1799
+ import { basename as basename3 } from "pathe";
1800
+ function isTestPath(filePath) {
1801
+ const p = filePath.replace(/\\/g, "/");
1802
+ return p.includes("__tests__/") || p.includes("/__tests__/") || /\.test\.[cm]?[jt]sx?$/.test(p) || /\.spec\.[cm]?[jt]sx?$/.test(p);
1803
+ }
1804
+ function pathSegments(filePath) {
1805
+ return filePath.replace(/\\/g, "/").toLowerCase().split("/").filter(Boolean).flatMap((s) => s.replace(/\.[^.]+$/, "").split(/[-_.]/));
1806
+ }
1807
+ function scoreSymbol(symbol, plan, graph, caps) {
1808
+ let score = 0;
1809
+ const reasons = [];
1810
+ const seeds = new Set(plan.seedPaths);
1811
+ const seedSymbols = plan.seedSymbols;
1812
+ if (seedSymbols.length) {
1813
+ const exact = seedSymbols.find((s) => s === symbol.name);
1814
+ if (exact) {
1815
+ score += 100;
1816
+ reasons.push("exact-symbol");
1817
+ } else {
1818
+ const ci = seedSymbols.find(
1819
+ (s) => s.toLowerCase() === symbol.name.toLowerCase()
1820
+ );
1821
+ if (ci) {
1822
+ score += 90;
1823
+ reasons.push("symbol-ci");
1824
+ }
1825
+ }
1826
+ }
1827
+ if (seeds.has(symbol.filePath)) {
1828
+ score += 40;
1829
+ reasons.push("seed-path");
1830
+ }
1831
+ const hop = graph.hopDistance(symbol.filePath, plan.seedPaths, caps.maxImportHops);
1832
+ if (hop === 0) {
1833
+ score += 30;
1834
+ reasons.push("hop-0");
1835
+ } else if (hop === 1) {
1836
+ score += 18;
1837
+ reasons.push("hop-1");
1838
+ }
1839
+ const nameLower = symbol.name.toLowerCase();
1840
+ let kwHits = 0;
1841
+ for (const k of plan.keywords) {
1842
+ if (nameLower.includes(k)) {
1843
+ kwHits++;
1844
+ if (kwHits >= 3) break;
1845
+ }
1846
+ }
1847
+ if (kwHits) {
1848
+ score += 15 * kwHits;
1849
+ reasons.push(`keyword-name\xD7${kwHits}`);
1850
+ }
1851
+ const segs = pathSegments(symbol.filePath);
1852
+ let pathHits = 0;
1853
+ for (const k of plan.keywords) {
1854
+ if (segs.some((s) => s.includes(k) || k.includes(s))) {
1855
+ pathHits++;
1856
+ if (pathHits >= 3) break;
1857
+ }
1858
+ }
1859
+ if (pathHits) {
1860
+ score += 8 * pathHits;
1861
+ reasons.push(`keyword-path\xD7${pathHits}`);
1862
+ }
1863
+ if (symbol.exported) {
1864
+ score += 5;
1865
+ reasons.push("exported");
1866
+ }
1867
+ const base = basename3(symbol.filePath).toLowerCase();
1868
+ let titleHits = 0;
1869
+ for (const k of plan.keywords.slice(0, 10)) {
1870
+ if (base.includes(k)) {
1871
+ titleHits++;
1872
+ if (titleHits >= 2) break;
1873
+ }
1874
+ }
1875
+ if (titleHits) {
1876
+ score += 6 * titleHits;
1877
+ reasons.push(`title-path\xD7${titleHits}`);
1878
+ }
1879
+ const focusIsTest = plan.seedPaths.some(isTestPath) || plan.keywords.some((k) => k === "test" || k === "tests" || k === "vitest");
1880
+ if (isTestPath(symbol.filePath) && !focusIsTest) {
1881
+ score -= 12;
1882
+ reasons.push("test-penalty");
1883
+ }
1884
+ const source = graph.getSource(symbol.filePath);
1885
+ if (source) {
1886
+ const lines = source.split(/\r?\n/).length;
1887
+ if (lines > 400) {
1888
+ const over = Math.floor((lines - 400) / 200);
1889
+ const pen = Math.min(10, over);
1890
+ if (pen > 0) {
1891
+ score -= pen;
1892
+ reasons.push("huge-file");
1893
+ }
1894
+ }
1895
+ }
1896
+ if (symbol.kind === "function" || symbol.kind === "class" || symbol.kind === "interface") {
1897
+ score += 2;
1898
+ }
1899
+ return { symbol, score, reasons };
1900
+ }
1901
+ function rankSymbols(graph, plan, caps) {
1902
+ const items = graph.allSymbols().map((s) => scoreSymbol(s, plan, graph, caps));
1903
+ items.sort((a, b) => {
1904
+ if (b.score !== a.score) return b.score - a.score;
1905
+ const fa = a.symbol.filePath.localeCompare(b.symbol.filePath);
1906
+ if (fa !== 0) return fa;
1907
+ return a.symbol.startLine - b.symbol.startLine;
1908
+ });
1909
+ return items.slice(0, caps.maxSymbols);
1910
+ }
1911
+
1912
+ // src/code-context/slicer.ts
1913
+ function sliceBody(source, startLine, endLine, maxLines) {
1914
+ const lines = source.split(/\r?\n/);
1915
+ const start = Math.max(1, startLine);
1916
+ let end = Math.min(lines.length, endLine);
1917
+ let truncated = false;
1918
+ if (end - start + 1 > maxLines) {
1919
+ end = start + maxLines - 1;
1920
+ truncated = true;
1921
+ }
1922
+ const body = lines.slice(start - 1, end).join("\n");
1923
+ return { body, end, truncated };
1924
+ }
1925
+ function emitSlices(ranked, graph, caps, options) {
1926
+ const slices = [];
1927
+ const gaps = [];
1928
+ let truncated = false;
1929
+ const seen = /* @__PURE__ */ new Set();
1930
+ for (const item of ranked) {
1931
+ if (slices.length >= caps.maxSlices) {
1932
+ truncated = true;
1933
+ break;
1934
+ }
1935
+ const sym = item.symbol;
1936
+ if (isSecretPath(sym.filePath)) continue;
1937
+ const source = graph.getSource(sym.filePath);
1938
+ if (!source) continue;
1939
+ const { body, end, truncated: lineTrunc } = sliceBody(
1940
+ source,
1941
+ sym.startLine,
1942
+ sym.endLine,
1943
+ caps.maxLinesPerSlice
1944
+ );
1945
+ if (lineTrunc) truncated = true;
1946
+ const id = `${sym.filePath}:${sym.startLine}-${end}:${sym.name}`;
1947
+ if (seen.has(id)) continue;
1948
+ seen.add(id);
1949
+ const reasonParts = item.reasons.length ? item.reasons.slice(0, 4).join(", ") : "ranked";
1950
+ slices.push({
1951
+ id,
1952
+ filePath: sym.filePath,
1953
+ startLine: sym.startLine,
1954
+ endLine: end,
1955
+ symbolName: sym.name,
1956
+ reason: reasonParts,
1957
+ body
1958
+ });
1959
+ if (options?.includeNeighbors && slices.length < caps.maxSlices) {
1960
+ const neighbors = graph.importNeighbors(sym.filePath);
1961
+ for (const n of neighbors.slice(0, 1)) {
1962
+ if (isSecretPath(n)) continue;
1963
+ const nSource = graph.getSource(n);
1964
+ const nNode = graph.files.get(n);
1965
+ if (!nSource || !nNode) continue;
1966
+ const nSym = nNode.extract.symbols.find((s) => s.exported) ?? nNode.extract.symbols[0];
1967
+ if (!nSym) continue;
1968
+ const nb = sliceBody(
1969
+ nSource,
1970
+ nSym.startLine,
1971
+ nSym.endLine,
1972
+ caps.maxLinesPerSlice
1973
+ );
1974
+ const nid = `${nSym.filePath}:${nSym.startLine}-${nb.end}:${nSym.name}:neighbor`;
1975
+ if (seen.has(nid)) continue;
1976
+ seen.add(nid);
1977
+ slices.push({
1978
+ id: nid,
1979
+ filePath: nSym.filePath,
1980
+ startLine: nSym.startLine,
1981
+ endLine: nb.end,
1982
+ symbolName: nSym.name,
1983
+ reason: `neighbor-of ${sym.name}`,
1984
+ body: nb.body
1985
+ });
1986
+ if (nb.truncated) truncated = true;
1987
+ break;
1988
+ }
1989
+ }
1990
+ }
1991
+ if (truncated) {
1992
+ gaps.push({
1993
+ code: "cap_truncated",
1994
+ message: "Output truncated at configured caps (maxSlices/maxLinesPerSlice)."
1995
+ });
1996
+ }
1997
+ return { slices, gaps, truncated };
1998
+ }
1999
+
2000
+ // src/code-context/types.ts
2001
+ var DEFAULT_CODE_CONTEXT_CAPS = {
2002
+ maxFiles: 40,
2003
+ maxSymbols: 30,
2004
+ maxSlices: 20,
2005
+ maxLinesPerSlice: 80,
2006
+ maxOutputLines: 8e3,
2007
+ maxTokensApprox: 12e3,
2008
+ maxImportHops: 1
2009
+ };
2010
+ function mergeCaps(partial) {
2011
+ return { ...DEFAULT_CODE_CONTEXT_CAPS, ...partial };
2012
+ }
2013
+
2014
+ // src/code-context/adapters/fallback.ts
2015
+ var fallbackAdapter = {
2016
+ id: "fallback",
2017
+ extensions: [],
2018
+ extract(input) {
2019
+ const lines = input.source.split(/\r?\n/);
2020
+ const headEnd = Math.min(lines.length, 20);
2021
+ return {
2022
+ ok: true,
2023
+ language: "text",
2024
+ symbols: [
2025
+ {
2026
+ name: "(file-header)",
2027
+ kind: "unknown",
2028
+ filePath: input.filePath,
2029
+ startLine: 1,
2030
+ endLine: headEnd,
2031
+ exported: false
2032
+ }
2033
+ ],
2034
+ imports: [],
2035
+ exports: [],
2036
+ errors: ["fallback: no AST; shallow header only"]
2037
+ };
2038
+ }
2039
+ };
2040
+
2041
+ // src/code-context/index.ts
2042
+ function emptySummary(partial) {
2043
+ return {
2044
+ focusPaths: [],
2045
+ focusSymbols: [],
2046
+ keywords: [],
2047
+ filesAnalyzed: 0,
2048
+ symbolsExtracted: 0,
2049
+ slicesEmitted: 0,
2050
+ truncated: false,
2051
+ ...partial
2052
+ };
2053
+ }
2054
+ function hardFailure(gaps, message, summary) {
2055
+ const markdown = [
2056
+ "# Code context",
2057
+ "",
2058
+ "> Generated on-demand for agents. Not a substitute for process context.",
2059
+ "",
2060
+ "## Summary",
2061
+ `- Error: ${message}`,
2062
+ "",
2063
+ "## Gaps",
2064
+ ...gaps.map(
2065
+ (g) => `- (\`${g.code}\`) ${g.message}${g.path ? ` \u2014 \`${g.path}\`` : ""}`
2066
+ ),
2067
+ "",
2068
+ "## Slices",
2069
+ "",
2070
+ "_None (hard failure)._",
2071
+ "",
2072
+ "## Structural notes",
2073
+ "- Pipeline did not complete analysis.",
2074
+ ""
2075
+ ].join("\n");
2076
+ return {
2077
+ ok: false,
2078
+ markdown,
2079
+ summary: emptySummary(summary),
2080
+ slices: [],
2081
+ gaps
2082
+ };
2083
+ }
2084
+ async function generateCodeContext(request, deps) {
2085
+ const caps = mergeCaps(request.caps);
2086
+ const projectRoot = request.projectRoot;
2087
+ const gaps = [];
2088
+ if (!deps?.adapters?.length && !isTypescriptAdapterAvailable()) {
2089
+ return hardFailure(
2090
+ [
2091
+ {
2092
+ code: "adapter_unavailable",
2093
+ message: "TypeScript language adapter could not be loaded; ensure `typescript` is installed with @structured-vibe-coding/core."
2094
+ }
2095
+ ],
2096
+ "Language adapter unavailable"
2097
+ );
2098
+ }
2099
+ const registry = new AdapterRegistry(deps?.adapters);
2100
+ const { plan, gaps: focusGaps } = await resolveFocus(request);
2101
+ gaps.push(...focusGaps);
2102
+ const hasFocus = plan.seedPaths.length > 0 || plan.candidatePaths.length > 0 || plan.seedSymbols.length > 0 || plan.keywords.length > 0 || Boolean(request.paths?.length) || Boolean(request.query) || Boolean(request.symbols?.length);
2103
+ if (!plan.seedPaths.length && !plan.candidatePaths.length && !plan.seedSymbols.length && !request.paths?.length && !request.query && !request.symbols?.length) {
2104
+ if (!plan.keywords.length) {
2105
+ gaps.push({
2106
+ code: "no_focus",
2107
+ message: "No focus resolved. Pass --path, --symbol, and/or --query, or run with an active change."
2108
+ });
2109
+ return hardFailure(gaps, "No focus resolved", {
2110
+ focusSymbols: plan.seedSymbols,
2111
+ keywords: plan.keywords
2112
+ });
2113
+ }
2114
+ }
2115
+ if (!plan.candidatePaths.length && !plan.seedPaths.length) {
2116
+ if (!hasFocus) {
2117
+ gaps.push({
2118
+ code: "no_focus",
2119
+ message: "No focus resolved. Pass --path, --symbol, and/or --query, or run with an active change."
2120
+ });
2121
+ return hardFailure(gaps, "No focus resolved", {
2122
+ focusSymbols: plan.seedSymbols,
2123
+ keywords: plan.keywords
2124
+ });
2125
+ }
2126
+ gaps.push({
2127
+ code: "no_focus",
2128
+ message: "Focus signals present but no candidate source files resolved. Pass --path to a file or directory."
2129
+ });
2130
+ return hardFailure(gaps, "No candidate files", {
2131
+ focusSymbols: plan.seedSymbols,
2132
+ keywords: plan.keywords,
2133
+ focusPaths: plan.seedPaths
2134
+ });
2135
+ }
2136
+ let candidates = [...plan.candidatePaths];
2137
+ if (!candidates.length) candidates = [...plan.seedPaths];
2138
+ let truncated = false;
2139
+ if (candidates.length > caps.maxFiles) {
2140
+ candidates = candidates.slice(0, caps.maxFiles);
2141
+ truncated = true;
2142
+ gaps.push({
2143
+ code: "cap_truncated",
2144
+ message: `Candidate files capped at maxFiles=${caps.maxFiles}.`
2145
+ });
2146
+ }
2147
+ const graph = new GraphLite();
2148
+ let filesAnalyzed = 0;
2149
+ let symbolsExtracted = 0;
2150
+ for (const rel of candidates) {
2151
+ const kind = classifyPath(rel);
2152
+ if (kind === "secret") {
2153
+ gaps.push({
2154
+ code: "path_denied_secret",
2155
+ message: `Skipped potential secret path: ${rel}`,
2156
+ path: rel
2157
+ });
2158
+ continue;
2159
+ }
2160
+ if (kind === "ignored") {
2161
+ gaps.push({
2162
+ code: "path_ignored",
2163
+ message: `Skipped ignored path: ${rel}`,
2164
+ path: rel
2165
+ });
2166
+ continue;
2167
+ }
2168
+ const abs = join8(projectRoot, rel);
2169
+ if (!await pathExists(abs)) {
2170
+ gaps.push({
2171
+ code: "path_missing",
2172
+ message: `Path not found: ${rel}`,
2173
+ path: rel
2174
+ });
2175
+ continue;
2176
+ }
2177
+ const adapter = registry.getByPath(rel);
2178
+ if (!adapter) {
2179
+ gaps.push({
2180
+ code: "unsupported_language",
2181
+ message: `No structure adapter for ${extname4(rel) || rel}; skipped.`,
2182
+ path: rel
2183
+ });
2184
+ continue;
2185
+ }
2186
+ let source;
2187
+ try {
2188
+ source = await readText(abs);
2189
+ } catch (err) {
2190
+ gaps.push({
2191
+ code: "parse_error",
2192
+ message: `Could not read ${rel}`,
2193
+ path: rel,
2194
+ detail: err instanceof Error ? err.message : String(err)
2195
+ });
2196
+ continue;
2197
+ }
2198
+ let extract;
2199
+ try {
2200
+ extract = await adapter.extract({
2201
+ filePath: normalize5(rel).replace(/\\/g, "/"),
2202
+ source,
2203
+ projectRoot
2204
+ });
2205
+ } catch (err) {
2206
+ gaps.push({
2207
+ code: "parse_error",
2208
+ message: `Parse failed for ${rel} (continuing with other files).`,
2209
+ path: rel,
2210
+ detail: err instanceof Error ? err.message : String(err)
2211
+ });
2212
+ continue;
2213
+ }
2214
+ if (!extract.ok) {
2215
+ gaps.push({
2216
+ code: "parse_error",
2217
+ message: `Parse failed for ${rel} (continuing with other files).`,
2218
+ path: rel,
2219
+ detail: extract.errors?.join("; ")
2220
+ });
2221
+ if (!extract.symbols.length) continue;
2222
+ }
2223
+ extract.symbols = extract.symbols.map((s) => ({
2224
+ ...s,
2225
+ filePath: normalize5(rel).replace(/\\/g, "/")
2226
+ }));
2227
+ if (!extract.symbols.length && extract.ok) {
2228
+ gaps.push({
2229
+ code: "empty_extract",
2230
+ message: `No symbols extracted from ${rel}.`,
2231
+ path: rel
2232
+ });
2233
+ }
2234
+ graph.add(normalize5(rel).replace(/\\/g, "/"), source, extract);
2235
+ filesAnalyzed++;
2236
+ symbolsExtracted += extract.symbols.length;
2237
+ }
2238
+ const ranked = rankSymbols(graph, plan, caps);
2239
+ const sliceResult = emitSlices(ranked, graph, caps, {
2240
+ includeNeighbors: request.includeNeighbors
2241
+ });
2242
+ gaps.push(...sliceResult.gaps);
2243
+ truncated = truncated || sliceResult.truncated;
2244
+ const summary = {
2245
+ focusPaths: plan.seedPaths,
2246
+ focusSymbols: plan.seedSymbols,
2247
+ keywords: plan.keywords,
2248
+ filesAnalyzed,
2249
+ symbolsExtracted,
2250
+ slicesEmitted: sliceResult.slices.length,
2251
+ truncated
2252
+ };
2253
+ let changeTitle = plan.changeId;
2254
+ if (plan.changeId) {
2255
+ try {
2256
+ const { readYaml: readYaml2 } = await import("./fs-J6DIV6CX.js");
2257
+ const metaPath = join8(
2258
+ projectRoot,
2259
+ // best-effort: changes/<id>/meta.yaml via relative walk
2260
+ // Prefer using change path from resolve — re-read lightly
2261
+ "changes",
2262
+ plan.changeId,
2263
+ "meta.yaml"
2264
+ );
2265
+ const { loadConfig: loadConfig2 } = await import("./config-4QZMRSL3.js");
2266
+ const { changePath: changePath2 } = await import("./paths-64GA32IZ.js");
2267
+ const config = await loadConfig2(projectRoot);
2268
+ const mp = join8(changePath2(projectRoot, config, plan.changeId), "meta.yaml");
2269
+ if (await pathExists(mp)) {
2270
+ const meta = await readYaml2(mp);
2271
+ changeTitle = meta.title ?? plan.changeId;
2272
+ } else if (await pathExists(metaPath)) {
2273
+ const meta = await readYaml2(metaPath);
2274
+ changeTitle = meta.title ?? plan.changeId;
2275
+ }
2276
+ } catch {
2277
+ }
2278
+ }
2279
+ const formatted = formatMarkdown({
2280
+ plan,
2281
+ summary,
2282
+ slices: sliceResult.slices,
2283
+ gaps,
2284
+ changeTitle,
2285
+ maxOutputLines: caps.maxOutputLines,
2286
+ maxTokensApprox: caps.maxTokensApprox
2287
+ });
2288
+ if (formatted.truncated && !summary.truncated) {
2289
+ summary.truncated = true;
2290
+ if (!gaps.some((g) => g.code === "cap_truncated")) {
2291
+ gaps.push({
2292
+ code: "cap_truncated",
2293
+ message: "Output truncated at configured caps (maxOutputLines/maxTokensApprox)."
2294
+ });
2295
+ }
2296
+ }
2297
+ const finalFormat = formatMarkdown({
2298
+ plan,
2299
+ summary,
2300
+ slices: sliceResult.slices,
2301
+ gaps,
2302
+ changeTitle,
2303
+ maxOutputLines: caps.maxOutputLines,
2304
+ maxTokensApprox: caps.maxTokensApprox
2305
+ });
2306
+ let artifactPath;
2307
+ if (request.writeArtifactTo) {
2308
+ await writeText(request.writeArtifactTo, finalFormat.markdown);
2309
+ artifactPath = request.writeArtifactTo;
2310
+ }
2311
+ return {
2312
+ ok: true,
2313
+ markdown: finalFormat.markdown,
2314
+ summary,
2315
+ slices: sliceResult.slices,
2316
+ gaps,
2317
+ artifactPath
2318
+ };
2319
+ }
2320
+
2321
+ // src/refine.ts
2322
+ import { join as join9 } from "pathe";
2323
+ function stageArtifactRefs(ctx, stageId) {
2324
+ const stage = getStage(ctx.workflow, ctx.meta, stageId);
2325
+ if (!stage) return [];
2326
+ return stage.artifacts.map((a) => ({
2327
+ stageId,
2328
+ path: a.path,
2329
+ required: Boolean(a.required),
2330
+ absPath: join9(ctx.path, a.path)
2331
+ }));
2332
+ }
2333
+ function listMarkdown(refs) {
2334
+ if (!refs.length) return "_None._";
2335
+ return refs.map(
2336
+ (r) => `- \`${r.path}\` (stage \`${r.stageId}\`${r.required ? ", required" : ""}) \u2192 \`${r.absPath}\``
2337
+ ).join("\n");
2338
+ }
2339
+ async function buildRefinePlan(opts) {
2340
+ const ctx = await buildContext(
2341
+ opts.projectRoot,
2342
+ opts.config,
2343
+ opts.changeId
2344
+ );
2345
+ const mode = opts.mode ?? "refine";
2346
+ const focusOnly = Boolean(opts.focusOnly);
2347
+ const focusStageId = opts.stageId?.trim() || ctx.meta.stage;
2348
+ const all = resolveStages(ctx.workflow, ctx.meta);
2349
+ const focusIdx = all.findIndex((s) => s.id === focusStageId);
2350
+ if (focusIdx < 0) {
2351
+ const ids = all.map((s) => s.id).join(", ");
2352
+ throw new Error(
2353
+ `Unknown stage "${focusStageId}" for workflow "${ctx.meta.workflow}". Stages: ${ids || "(none)"}`
2354
+ );
2355
+ }
2356
+ if (isStageSkipped(ctx.meta, focusStageId) || shouldAutoSkip(all[focusIdx], ctx.meta)) {
2357
+ throw new Error(
2358
+ `Stage "${focusStageId}" is skipped for this change. Un-skip it or pick another stage.`
2359
+ );
2360
+ }
2361
+ const focusStage = all[focusIdx];
2362
+ const priorArtifacts = [];
2363
+ for (let i = 0; i < focusIdx; i++) {
2364
+ const s = all[i];
2365
+ if (isStageSkipped(ctx.meta, s.id) || shouldAutoSkip(s, ctx.meta)) continue;
2366
+ priorArtifacts.push(...stageArtifactRefs(ctx, s.id));
2367
+ }
2368
+ const laterArtifacts = [];
2369
+ for (let i = focusIdx + 1; i < all.length; i++) {
2370
+ const s = all[i];
2371
+ if (isStageSkipped(ctx.meta, s.id) || shouldAutoSkip(s, ctx.meta)) continue;
2372
+ laterArtifacts.push(...stageArtifactRefs(ctx, s.id));
2373
+ }
2374
+ const focusArtifacts = stageArtifactRefs(ctx, focusStageId);
2375
+ const constitutionPath = join9(memoryDir(opts.projectRoot, opts.config), "constitution.md");
2376
+ const constitutionExists = await pathExists(constitutionPath);
2377
+ const briefPath = join9(ctx.path, "refine-brief.md");
2378
+ const reportPath = join9(ctx.path, "quality-report.md");
2379
+ const openItemsPath = join9(ctx.path, "open-items.md");
2380
+ const briefMarkdown = buildRefineBriefMarkdown({
2381
+ ctx,
2382
+ focusStageId,
2383
+ focusStageTitle: focusStage.title ?? focusStageId,
2384
+ mode,
2385
+ focusOnly,
2386
+ focusArtifacts,
2387
+ priorArtifacts,
2388
+ laterArtifacts,
2389
+ constitutionPath,
2390
+ constitutionExists,
2391
+ reportPath,
2392
+ openItemsPath
2393
+ });
2394
+ const kickoff = [
2395
+ `SDD: ${mode} stage \`${focusStageId}\`.`,
2396
+ `Title: ${ctx.meta.title}`,
2397
+ `Change: ${ctx.id}`,
2398
+ `Read first: ${briefPath}`,
2399
+ `Also: .sdd/protocol.md, .sdd/active-context.md`,
2400
+ constitutionExists ? `Constitution (READ-ONLY): ${constitutionPath}` : `No constitution.md found (optional).`,
2401
+ mode === "analyze" ? `Mode ANALYZE: write findings to ${reportPath} only \u2014 do not edit pack artifacts.` : `Mode REFINE: improve focus-stage artifacts; impact-scan prior pack files (fix mechanical inconsistencies; highlight judgment calls). Never edit constitution. Do not run sdd next.`,
2402
+ focusOnly ? `focusOnly=true: edit only focus-stage files (still read prior + constitution).` : `Prior artifacts may be auto-fixed for clear contradictions/term drift; scope changes \u2192 highlight + open-items (human accept).`
2403
+ ].join(" ");
2404
+ return {
2405
+ changeId: ctx.id,
2406
+ title: ctx.meta.title,
2407
+ workflow: ctx.meta.workflow,
2408
+ focusStageId,
2409
+ focusStageTitle: focusStage.title ?? focusStageId,
2410
+ mode,
2411
+ focusOnly,
2412
+ focusArtifacts,
2413
+ priorArtifacts,
2414
+ laterArtifacts,
2415
+ constitutionPath,
2416
+ constitutionExists,
2417
+ changePath: ctx.path,
2418
+ briefPath,
2419
+ reportPath,
2420
+ openItemsPath,
2421
+ kickoff,
2422
+ briefMarkdown
2423
+ };
2424
+ }
2425
+ function buildRefineBriefMarkdown(opts) {
2426
+ const {
2427
+ ctx,
2428
+ focusStageId,
2429
+ focusStageTitle,
2430
+ mode,
2431
+ focusOnly,
2432
+ focusArtifacts,
2433
+ priorArtifacts,
2434
+ laterArtifacts,
2435
+ constitutionPath,
2436
+ constitutionExists,
2437
+ reportPath,
2438
+ openItemsPath
2439
+ } = opts;
2440
+ return `# Refine brief (auto-generated)
2441
+
2442
+ > Change: **${ctx.meta.title}** \xB7 \`${ctx.id}\`
2443
+ > Workflow: \`${ctx.meta.workflow}\` \xB7 Focus stage: **${focusStageId}** (${focusStageTitle})
2444
+ > Mode: **${mode}** \xB7 focusOnly: **${focusOnly}**
2445
+ > Regenerable: run \`sdd refine${focusStageId !== ctx.meta.stage ? ` ${focusStageId}` : ""}\` again.
2446
+
2447
+ ## Mission
2448
+
2449
+ You are the **SDD refine** agent (one agent, mode \`${mode}\`).
2450
+
2451
+ 1. Work by **stage**, not hard-coded filenames. Focus stage resolves to the files below.
2452
+ 2. **Never edit** \`memory/constitution.md\` (or any constitution path). Read it as constraints only.
2453
+ 3. **Do not** run \`sdd next\`, \`sdd complete\`, or change \`meta.yaml\` stage/status.
2454
+ 4. Process is **never blocked** by findings \u2014 report and improve docs only.
2455
+
2456
+ ## Constitution (READ-ONLY)
2457
+
2458
+ ${constitutionExists ? `- Path: \`${constitutionPath}\` \u2014 **read only**. Conflicts \u2192 highlight in report / open items; do not "fix" constitution.` : `- No constitution at \`${constitutionPath}\` \u2014 skip.`}
2459
+
2460
+ ## Focus stage artifacts (primary ${mode === "analyze" ? "review" : "edit"} target)
2461
+
2462
+ ${listMarkdown(focusArtifacts)}
2463
+
2464
+ ## Prior stage artifacts (pack trail \u2014 always read)
2465
+
2466
+ ${listMarkdown(priorArtifacts)}
2467
+
2468
+ ${focusOnly ? `**focusOnly:** Do **not** edit prior files. Still read them for consistency. Highlight conflicts for the human.` : `**Auto prior impact (required):** After refining the focus stage, use search (\`rg\` / grep) across the change pack for terms, claims, and names you changed. For each hit on a **prior** artifact:
2469
+ - **Fix** mechanical inconsistencies (contradictions, renamed terms, outdated references that clearly disagree with the refined focus).
2470
+ - **Highlight** (do not silently rewrite) product/scope judgment calls \u2014 put them under Open items only if the human should explicitly accept/defer.
2471
+ - Keep prior edits **minimal** \u2014 no drive-by rewrites of history.`}
2472
+
2473
+ ## Later stage artifacts (usually leave alone)
2474
+
2475
+ ${listMarkdown(laterArtifacts)}
2476
+
2477
+ Do **not** invent content for later stages that were not materialized yet. If a later file exists and contradicts the refined focus, **highlight** only (unless the human asked to align the whole pack).
2478
+
2479
+ ## Mode-specific rules
2480
+
2481
+ ### analyze
2482
+ - Write/update \`${reportPath}\` with findings (markdown).
2483
+ - Sections: Summary \xB7 Findings (with IDs AMB-001\u2026) \xB7 Open items (explicitly accepted only) \xB7 Disposition suggestions.
2484
+ - **Do not** edit focus/prior pack artifacts (report only).
2485
+
2486
+ ### refine
2487
+ - Improve **focus** stage artifacts so this step is clear, consistent, and useful.
2488
+ - Run impact scan on **prior** pack artifacts (unless focusOnly); fix mechanical issues; highlight judgment calls.
2489
+ - Append a short "Refine log" at the bottom of \`${reportPath}\` (what changed, prior fixes, highlights).
2490
+ - **Open items:** only move something into \`${openItemsPath}\` (or an Open items section) when the human has **explicitly accepted** deferral/acceptance with a note \u2014 or list candidates under "Proposed open items (awaiting human)" in the report. Never silently park hard questions as open items.
2491
+
2492
+ ## Tools
2493
+
2494
+ - Prefer reading full pack markdown for specs.
2495
+ - Use \`rg\`/\`grep\` for impact after edits (old terms, paths, API names).
2496
+ - Optional product code context: \`sdd context --path \u2026 --symbol \u2026\` (AST slices) \u2014 do not dump the whole repo.
2497
+
2498
+ ## Done when
2499
+
2500
+ - Focus stage artifacts are coherent for stage \`${focusStageId}\`.
2501
+ - Prior trail has no unaddressed **mechanical** contradictions (or focusOnly + highlights).
2502
+ - Constitution was not modified.
2503
+ - User can continue with \`sdd next\` when *they* are ready (you do not advance).
2504
+ `;
2505
+ }
2506
+ async function writeRefineBrief(plan, projectRoot) {
2507
+ await writeText(plan.briefPath, plan.briefMarkdown);
2508
+ const pointer = join9(sddRoot(projectRoot), "refine-brief.md");
2509
+ await writeText(
2510
+ pointer,
2511
+ `# Refine brief pointer
2512
+
2513
+ Active refine brief: \`${plan.briefPath}\`
2514
+
2515
+ Run: \`sdd refine${plan.focusStageId ? ` ${plan.focusStageId}` : ""}\`
2516
+
2517
+ `
2518
+ );
2519
+ return plan.briefPath;
2520
+ }
924
2521
  export {
925
2522
  AGENT_INTEGRATIONS,
926
2523
  AGENT_TARGET_OPTIONS,
927
2524
  ALL_AGENT_TARGETS,
2525
+ AdapterRegistry,
928
2526
  ArtifactDefSchema,
929
2527
  ChangeMetaSchema,
930
2528
  ConfigSchema,
2529
+ DEFAULT_CODE_CONTEXT_CAPS,
931
2530
  DEFAULT_INIT_INTEGRATION,
932
2531
  GateSchema,
933
2532
  PROTOCOL_MD,
@@ -945,11 +2544,13 @@ export {
945
2544
  archiveDir,
946
2545
  buildAgentPrompt,
947
2546
  buildContext,
2547
+ buildRefinePlan,
948
2548
  canLeaveStage,
949
2549
  changeDirName,
950
2550
  changeMetaPath,
951
2551
  changePath,
952
2552
  changesDir,
2553
+ classifyPath,
953
2554
  clearActiveIf,
954
2555
  completeChange,
955
2556
  configPath,
@@ -963,9 +2564,12 @@ export {
963
2564
  defaultsRoot,
964
2565
  domainsDir,
965
2566
  ensureDir,
2567
+ fallbackAdapter,
966
2568
  fallbackIncompleteStageId,
967
2569
  firstActiveStageId,
2570
+ formatJsonSummary,
968
2571
  formatStatus,
2572
+ generateCodeContext,
969
2573
  getActiveChangeId,
970
2574
  getIntegration,
971
2575
  getStage,
@@ -975,7 +2579,9 @@ export {
975
2579
  installAgentIntegration,
976
2580
  installAgentIntegrations,
977
2581
  integrationKeyFor,
2582
+ isIgnoredPath,
978
2583
  isInitialized,
2584
+ isSecretPath,
979
2585
  isStageSkipped,
980
2586
  isSubstantiveArtifactContent,
981
2587
  listChanges,
@@ -987,6 +2593,7 @@ export {
987
2593
  loadInstalledAgent,
988
2594
  loadWorkflow,
989
2595
  memoryDir,
2596
+ mergeCaps,
990
2597
  nextStageId,
991
2598
  nowIso,
992
2599
  optionForTarget,
@@ -1001,6 +2608,7 @@ export {
1001
2608
  removePath,
1002
2609
  renderThinAgent,
1003
2610
  resolveChangeId,
2611
+ resolveFocus,
1004
2612
  resolveStages,
1005
2613
  runLocalVerify,
1006
2614
  saveChangeMeta,
@@ -1013,9 +2621,12 @@ export {
1013
2621
  switchWorkflow,
1014
2622
  templatesDir,
1015
2623
  todayIsoDate,
2624
+ tokenizeKeywords,
2625
+ typescriptAdapter,
1016
2626
  uniqueChangeDirName,
1017
2627
  workflowsDir,
1018
2628
  writeAgentHandoff,
2629
+ writeRefineBrief,
1019
2630
  writeText,
1020
2631
  writeTextIfMissing,
1021
2632
  writeYaml