@structured-vibe-coding/core 0.9.1 → 0.10.1
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/{agents-BA7WVN35.js → agents-IB37YJX6.js} +4 -1
- package/dist/chunk-7MJYPJMR.js +51 -0
- package/dist/chunk-EPFURATC.js +96 -0
- package/dist/chunk-KZHNYGD6.js +187 -0
- package/dist/{chunk-M245MC6E.js → chunk-UAOFNSLQ.js} +74 -335
- package/dist/config-4QZMRSL3.js +14 -0
- package/dist/fs-J6DIV6CX.js +28 -0
- package/dist/index.d.ts +275 -1
- package/dist/index.js +1648 -37
- package/dist/paths-64GA32IZ.js +28 -0
- package/package.json +2 -1
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
|
-
|
|
79
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|