soloforge 1.4.6 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -4
- package/dist/adapters/claude_code/tools.d.ts.map +1 -1
- package/dist/adapters/claude_code/tools.js +129 -10
- package/dist/adapters/claude_code/tools.js.map +1 -1
- package/dist/bin/soloforge.d.ts.map +1 -1
- package/dist/bin/soloforge.js +3 -2
- package/dist/bin/soloforge.js.map +1 -1
- package/dist/engine/change_coordinator.d.ts.map +1 -1
- package/dist/engine/change_coordinator.js +2 -1
- package/dist/engine/change_coordinator.js.map +1 -1
- package/dist/engine/core_engineering_principles.d.ts.map +1 -1
- package/dist/engine/core_engineering_principles.js +2 -1
- package/dist/engine/core_engineering_principles.js.map +1 -1
- package/dist/engine/enforcement_guard.d.ts.map +1 -1
- package/dist/engine/enforcement_guard.js +2 -1
- package/dist/engine/enforcement_guard.js.map +1 -1
- package/dist/engine/extension_platform_contracts.d.ts +6 -0
- package/dist/engine/extension_platform_contracts.d.ts.map +1 -1
- package/dist/engine/extension_platform_contracts.js.map +1 -1
- package/dist/engine/impact_analyzer.d.ts.map +1 -1
- package/dist/engine/impact_analyzer.js +5 -23
- package/dist/engine/impact_analyzer.js.map +1 -1
- package/dist/engine/intent_expander.d.ts.map +1 -1
- package/dist/engine/intent_expander.js +9 -46
- package/dist/engine/intent_expander.js.map +1 -1
- package/dist/engine/onboarding.d.ts.map +1 -1
- package/dist/engine/onboarding.js +2 -1
- package/dist/engine/onboarding.js.map +1 -1
- package/dist/engine/path_scope_utils.d.ts +21 -0
- package/dist/engine/path_scope_utils.d.ts.map +1 -0
- package/dist/engine/path_scope_utils.js +126 -0
- package/dist/engine/path_scope_utils.js.map +1 -0
- package/dist/engine/plan_proposal_gate.d.ts.map +1 -1
- package/dist/engine/plan_proposal_gate.js +2 -1
- package/dist/engine/plan_proposal_gate.js.map +1 -1
- package/dist/engine/release_readiness_gate.d.ts.map +1 -1
- package/dist/engine/release_readiness_gate.js +15 -0
- package/dist/engine/release_readiness_gate.js.map +1 -1
- package/dist/engine/scope_controller.d.ts.map +1 -1
- package/dist/engine/scope_controller.js +5 -7
- package/dist/engine/scope_controller.js.map +1 -1
- package/dist/engine/scope_lease.d.ts.map +1 -1
- package/dist/engine/scope_lease.js +2 -2
- package/dist/engine/scope_lease.js.map +1 -1
- package/dist/engine/scope_resolver.d.ts +11 -0
- package/dist/engine/scope_resolver.d.ts.map +1 -0
- package/dist/engine/scope_resolver.js +526 -0
- package/dist/engine/scope_resolver.js.map +1 -0
- package/dist/engine/workspace_lease.d.ts.map +1 -1
- package/dist/engine/workspace_lease.js +6 -5
- package/dist/engine/workspace_lease.js.map +1 -1
- package/dist/knowledge/index_manager.d.ts.map +1 -1
- package/dist/knowledge/index_manager.js +2 -1
- package/dist/knowledge/index_manager.js.map +1 -1
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope_controller.d.ts","sourceRoot":"","sources":["../../src/engine/scope_controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"scope_controller.d.ts","sourceRoot":"","sources":["../../src/engine/scope_controller.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA6EpD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EAAE,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,gBAAgB,CAuDlB"}
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { debugLog } from "./debug_log.js";
|
|
5
|
+
import { hasGlobPattern, isPathInScope } from "./path_scope_utils.js";
|
|
5
6
|
/**
|
|
6
7
|
* 作用域控制器 — 检查文件路径是否在允许范围内,并检测敏感信息泄露。
|
|
7
8
|
*
|
|
@@ -93,13 +94,10 @@ export function checkScope(filePath, allowedPaths, content) {
|
|
|
93
94
|
const resolvedPath = realPath(filePath);
|
|
94
95
|
// 检查路径是否在允许的 scope 内(防止路径穿越攻击)
|
|
95
96
|
const inScope = allowedPaths.some((allowed) => {
|
|
96
|
-
const resolved =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (resolvedPath.length === resolved.length)
|
|
101
|
-
return true;
|
|
102
|
-
return resolvedPath[resolved.length] === "/";
|
|
97
|
+
const resolved = hasGlobPattern(allowed)
|
|
98
|
+
? (path.isAbsolute(allowed) ? allowed : path.resolve(allowed))
|
|
99
|
+
: realPath(allowed);
|
|
100
|
+
return isPathInScope(resolvedPath, resolved);
|
|
103
101
|
});
|
|
104
102
|
if (!inScope) {
|
|
105
103
|
debugLog(`作用域检查: 文件 ${filePath} 不在允许的 scope 内`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope_controller.js","sourceRoot":"","sources":["../../src/engine/scope_controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"scope_controller.js","sourceRoot":"","sources":["../../src/engine/scope_controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope_lease.d.ts","sourceRoot":"","sources":["../../src/engine/scope_lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"scope_lease.d.ts","sourceRoot":"","sources":["../../src/engine/scope_lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,WAAW;AACX,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,aAAa;AACb,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,UAAU,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,WAAW;AACX,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC1C;AAED,aAAa;AACb,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,MAAM,CAAS;IAEvB;;;OAGG;gBACS,QAAQ,EAAE,MAAM;IAM5B,2BAA2B;IAC3B,OAAO,CAAC,YAAY;IAgBpB,kBAAkB;IAClB,OAAO,CAAC,OAAO;IAOf;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,UAAU;IAexD;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAW1C;;;;;OAKG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAoBlE;;;OAGG;IACH,eAAe,IAAI,UAAU,EAAE;IAK/B;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAKlD;;;OAGG;IACH,UAAU,IAAI,YAAY;IAe1B;;OAEG;IACH,KAAK,IAAI,IAAI;CAMd"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import { debug } from "./logger.js";
|
|
15
|
+
import { isPathInScope } from "./path_scope_utils.js";
|
|
15
16
|
/** 范围租约存储 */
|
|
16
17
|
export class ScopeLeaseStore {
|
|
17
18
|
filePath;
|
|
@@ -160,7 +161,6 @@ export class ScopeLeaseStore {
|
|
|
160
161
|
}
|
|
161
162
|
/** 判断 child 路径是否在 parent 路径下 */
|
|
162
163
|
function isParentPath(parent, child) {
|
|
163
|
-
|
|
164
|
-
return rel.length > 0 && !rel.startsWith("..");
|
|
164
|
+
return isPathInScope(child, parent, { caseSensitive: true });
|
|
165
165
|
}
|
|
166
166
|
//# sourceMappingURL=scope_lease.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope_lease.js","sourceRoot":"","sources":["../../src/engine/scope_lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"scope_lease.js","sourceRoot":"","sources":["../../src/engine/scope_lease.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClassifyResult, ProjectConfig, ScopeResult } from "../types.js";
|
|
2
|
+
import type { IntentRouteDecision } from "./intent_router.js";
|
|
3
|
+
export interface ResolveTaskScopeInput {
|
|
4
|
+
projectPath: string;
|
|
5
|
+
config: ProjectConfig;
|
|
6
|
+
classification: ClassifyResult;
|
|
7
|
+
degraded: boolean;
|
|
8
|
+
routeDecision?: IntentRouteDecision;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveTaskScope(input: ResolveTaskScopeInput): ScopeResult;
|
|
11
|
+
//# sourceMappingURL=scope_resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope_resolver.d.ts","sourceRoot":"","sources":["../../src/engine/scope_resolver.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAIb,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAW9D,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACrC;AAigBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,WAAW,CA6F1E"}
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureTrailingSlash, hasGlobPattern, isPathInScope, isSameOrDescendantPath, matchesScopePattern, normalizeScopePattern, toPosixPath, } from "./path_scope_utils.js";
|
|
4
|
+
const IGNORED_DIRS = new Set([
|
|
5
|
+
".claude",
|
|
6
|
+
".codex",
|
|
7
|
+
".git",
|
|
8
|
+
".soloforge",
|
|
9
|
+
".trae",
|
|
10
|
+
"build",
|
|
11
|
+
"coverage",
|
|
12
|
+
"dist",
|
|
13
|
+
"node_modules",
|
|
14
|
+
"out",
|
|
15
|
+
"target",
|
|
16
|
+
]);
|
|
17
|
+
const BACKEND_HINTS = [
|
|
18
|
+
"backend",
|
|
19
|
+
"server",
|
|
20
|
+
"service",
|
|
21
|
+
"services",
|
|
22
|
+
"api",
|
|
23
|
+
"web",
|
|
24
|
+
"model",
|
|
25
|
+
"dao",
|
|
26
|
+
"repository",
|
|
27
|
+
];
|
|
28
|
+
const FRONTEND_HINTS = [
|
|
29
|
+
"frontend",
|
|
30
|
+
"client",
|
|
31
|
+
"webapp",
|
|
32
|
+
"ui",
|
|
33
|
+
"portal",
|
|
34
|
+
"platform",
|
|
35
|
+
"institution",
|
|
36
|
+
];
|
|
37
|
+
const KNOWN_SOURCE_ROOT_PATTERN = /^(?:src|[^/]+\/src|[^/]+\/[^/]+\/src|[^/]+\/[^/]+\/[^/]+\/src)\/$/;
|
|
38
|
+
const PATH_CASE_SENSITIVE = process.platform !== "win32" && process.platform !== "darwin";
|
|
39
|
+
function pathExists(projectPath, relativePath) {
|
|
40
|
+
try {
|
|
41
|
+
return fs.existsSync(path.join(projectPath, relativePath));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function isDirectory(projectPath, relativePath) {
|
|
48
|
+
try {
|
|
49
|
+
return fs.statSync(path.join(projectPath, relativePath)).isDirectory();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function readDirSafe(projectPath, relativePath) {
|
|
56
|
+
try {
|
|
57
|
+
return fs.readdirSync(path.join(projectPath, relativePath), { withFileTypes: true });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function normalizeRelPath(rawPath, basePath) {
|
|
64
|
+
const normalized = String(rawPath ?? "").replace(/\\/g, "/").trim();
|
|
65
|
+
if (!normalized || normalized === ".")
|
|
66
|
+
return normalizeScopePattern(basePath ?? ".");
|
|
67
|
+
const combined = basePath && basePath !== "."
|
|
68
|
+
? path.posix.normalize(path.posix.join(toPosixPath(basePath), normalized))
|
|
69
|
+
: path.posix.normalize(normalized);
|
|
70
|
+
return normalizeScopePattern(combined);
|
|
71
|
+
}
|
|
72
|
+
function dedupePreserveOrder(paths) {
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const result = [];
|
|
75
|
+
for (const raw of paths) {
|
|
76
|
+
const normalized = normalizeScopePattern(raw);
|
|
77
|
+
if (!normalized || seen.has(normalized))
|
|
78
|
+
continue;
|
|
79
|
+
seen.add(normalized);
|
|
80
|
+
result.push(normalized);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
function removeNestedBroadDuplicates(paths) {
|
|
85
|
+
const sorted = dedupePreserveOrder(paths).sort((a, b) => a.length - b.length);
|
|
86
|
+
const result = [];
|
|
87
|
+
for (const candidate of sorted) {
|
|
88
|
+
if (hasGlobPattern(candidate)) {
|
|
89
|
+
result.push(candidate);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (KNOWN_SOURCE_ROOT_PATTERN.test(candidate)) {
|
|
93
|
+
for (let i = result.length - 1; i >= 0; i -= 1) {
|
|
94
|
+
const existing = result[i];
|
|
95
|
+
if (!hasGlobPattern(existing)
|
|
96
|
+
&& KNOWN_SOURCE_ROOT_PATTERN.test(existing)
|
|
97
|
+
&& isSameOrDescendantPath(candidate, existing, { caseSensitive: PATH_CASE_SENSITIVE })) {
|
|
98
|
+
result.splice(i, 1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (result.some((existing) => !hasGlobPattern(existing)
|
|
103
|
+
&& isSameOrDescendantPath(candidate, existing, { caseSensitive: PATH_CASE_SENSITIVE }))) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
result.push(candidate);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
function getAffectedRepos(classification) {
|
|
111
|
+
return Array.isArray(classification?.affected_repos)
|
|
112
|
+
? classification.affected_repos.filter((repo) => typeof repo === "string" && repo.length > 0)
|
|
113
|
+
: [];
|
|
114
|
+
}
|
|
115
|
+
function taskNeedsBackend(input) {
|
|
116
|
+
const affected = getAffectedRepos(input.classification).join(" ").toLowerCase();
|
|
117
|
+
const intent = `${input.classification.task_type} ${input.routeDecision?.workflow_intent ?? ""} ${affected}`;
|
|
118
|
+
if (/backend|server|api|java|spring|数据库|接口|后端|服务|mapper|controller|service/i.test(intent))
|
|
119
|
+
return true;
|
|
120
|
+
if (input.config.tech_stack.backend?.framework && input.config.tech_stack.backend.framework !== "unknown") {
|
|
121
|
+
return affected.length === 0 || /backend|server|api|service|web|model/.test(affected);
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function taskNeedsFrontend(input) {
|
|
126
|
+
const affected = getAffectedRepos(input.classification).join(" ").toLowerCase();
|
|
127
|
+
const intent = `${input.classification.task_type} ${input.routeDecision?.workflow_intent ?? ""} ${affected}`;
|
|
128
|
+
if (/frontend|client|react|vue|页面|前端|component|route|ui/i.test(intent))
|
|
129
|
+
return true;
|
|
130
|
+
if (input.config.tech_stack.frontend?.framework && input.config.tech_stack.frontend.framework !== "unknown") {
|
|
131
|
+
return affected.length === 0 || /frontend|client|ui|app|web|platform|institution/.test(affected);
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
function roleFromPath(relativePath) {
|
|
136
|
+
const lower = relativePath.toLowerCase();
|
|
137
|
+
if (FRONTEND_HINTS.some((hint) => lower.split("/").includes(hint) || lower.includes(`/${hint}/`)))
|
|
138
|
+
return "frontend";
|
|
139
|
+
if (BACKEND_HINTS.some((hint) => lower.split("/").includes(hint) || lower.includes(`/${hint}/`)))
|
|
140
|
+
return "backend";
|
|
141
|
+
if (/(?:^|\/)src\/main\/(?:java|kotlin|scala)(?:\/|$)/.test(lower))
|
|
142
|
+
return "backend";
|
|
143
|
+
if (/(?:^|\/)(?:src|app)\/(?:pages|components|views|router|routes)(?:\/|$)/.test(lower))
|
|
144
|
+
return "frontend";
|
|
145
|
+
return "shared";
|
|
146
|
+
}
|
|
147
|
+
function addSourceRoot(roots, root) {
|
|
148
|
+
const normalized = ensureTrailingSlash(root.path);
|
|
149
|
+
if (!normalized || normalized === "./")
|
|
150
|
+
return;
|
|
151
|
+
const existing = roots.get(normalized);
|
|
152
|
+
if (existing) {
|
|
153
|
+
existing.evidence.push(...root.evidence.filter((item) => !existing.evidence.includes(item)));
|
|
154
|
+
if (existing.role === "shared" && root.role !== "shared")
|
|
155
|
+
existing.role = root.role;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
roots.set(normalized, { ...root, path: normalized });
|
|
159
|
+
}
|
|
160
|
+
function discoverMavenRoots(projectPath, roots) {
|
|
161
|
+
const stack = [""];
|
|
162
|
+
while (stack.length > 0) {
|
|
163
|
+
const current = stack.pop();
|
|
164
|
+
const entries = readDirSafe(projectPath, current);
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
|
|
167
|
+
continue;
|
|
168
|
+
const child = current ? `${current}/${entry.name}` : entry.name;
|
|
169
|
+
const depth = child.split("/").length;
|
|
170
|
+
if (depth <= 4)
|
|
171
|
+
stack.push(child);
|
|
172
|
+
}
|
|
173
|
+
const hasJavaSource = isDirectory(projectPath, path.posix.join(current, "src/main/java"))
|
|
174
|
+
|| isDirectory(projectPath, path.posix.join(current, "src/main/kotlin"));
|
|
175
|
+
const hasBuildFile = pathExists(projectPath, path.posix.join(current, "pom.xml"))
|
|
176
|
+
|| pathExists(projectPath, path.posix.join(current, "build.gradle"))
|
|
177
|
+
|| pathExists(projectPath, path.posix.join(current, "build.gradle.kts"));
|
|
178
|
+
const currentIsSourceLayer = /(?:^|\/)src(?:\/main)?$/i.test(current);
|
|
179
|
+
if (!currentIsSourceLayer && (hasJavaSource || (hasBuildFile && isDirectory(projectPath, path.posix.join(current, "src"))))) {
|
|
180
|
+
addSourceRoot(roots, {
|
|
181
|
+
path: current ? `${current}/src/` : "src/",
|
|
182
|
+
role: "backend",
|
|
183
|
+
evidence: [
|
|
184
|
+
hasJavaSource ? "detected src/main Java/Kotlin source tree" : "detected JVM build file with src directory",
|
|
185
|
+
],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function discoverFrontendRoots(projectPath, roots) {
|
|
191
|
+
const containerNames = new Set(["apps", "packages", "modules", "services", "clients", "frontends", "workspaces"]);
|
|
192
|
+
const stack = [""];
|
|
193
|
+
while (stack.length > 0) {
|
|
194
|
+
const current = stack.pop();
|
|
195
|
+
for (const entry of readDirSafe(projectPath, current)) {
|
|
196
|
+
if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
|
|
197
|
+
continue;
|
|
198
|
+
const child = current ? `${current}/${entry.name}` : entry.name;
|
|
199
|
+
const depth = child.split("/").length;
|
|
200
|
+
if (depth <= 3)
|
|
201
|
+
stack.push(child);
|
|
202
|
+
}
|
|
203
|
+
const srcCandidate = current ? `${current}/src` : "src";
|
|
204
|
+
if (isDirectory(projectPath, srcCandidate)) {
|
|
205
|
+
addSourceRoot(roots, {
|
|
206
|
+
path: `${srcCandidate}/`,
|
|
207
|
+
role: roleFromPath(srcCandidate),
|
|
208
|
+
evidence: ["detected frontend source directory candidate"],
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (containerNames.has(path.posix.basename(current))) {
|
|
212
|
+
for (const entry of readDirSafe(projectPath, current)) {
|
|
213
|
+
if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
|
|
214
|
+
continue;
|
|
215
|
+
const src = current ? `${current}/${entry.name}/src` : `${entry.name}/src`;
|
|
216
|
+
if (!isDirectory(projectPath, src))
|
|
217
|
+
continue;
|
|
218
|
+
addSourceRoot(roots, {
|
|
219
|
+
path: `${src}/`,
|
|
220
|
+
role: "frontend",
|
|
221
|
+
evidence: [`detected workspace package source: ${current || "."}/*/src`],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function discoverSourceRoots(projectPath) {
|
|
228
|
+
const roots = new Map();
|
|
229
|
+
discoverMavenRoots(projectPath, roots);
|
|
230
|
+
discoverFrontendRoots(projectPath, roots);
|
|
231
|
+
for (const topLevel of readDirSafe(projectPath, "")) {
|
|
232
|
+
if (!topLevel.isDirectory() || IGNORED_DIRS.has(topLevel.name))
|
|
233
|
+
continue;
|
|
234
|
+
const directSrc = `${topLevel.name}/src`;
|
|
235
|
+
if (isDirectory(projectPath, directSrc)) {
|
|
236
|
+
addSourceRoot(roots, {
|
|
237
|
+
path: `${directSrc}/`,
|
|
238
|
+
role: roleFromPath(directSrc),
|
|
239
|
+
evidence: ["detected top-level module src directory"],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return [...roots.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
244
|
+
}
|
|
245
|
+
function expandWeakPath(projectPath, declaredPath, role, discoveredRoots, warnings) {
|
|
246
|
+
const normalized = normalizeRelPath(declaredPath);
|
|
247
|
+
if (pathExists(projectPath, normalized))
|
|
248
|
+
return [normalized];
|
|
249
|
+
const basename = normalized.replace(/\/$/, "");
|
|
250
|
+
if (basename === "src" || basename.endsWith("/src")) {
|
|
251
|
+
const matchingRoots = discoveredRoots
|
|
252
|
+
.filter((root) => role === "shared" || root.role === role || root.role === "shared")
|
|
253
|
+
.map((root) => root.path);
|
|
254
|
+
if (matchingRoots.length > 0) {
|
|
255
|
+
warnings.push(`scope ${declaredPath} 在项目根不存在,已按真实多模块源码树展开为 ${matchingRoots.join(", ")}`);
|
|
256
|
+
return matchingRoots;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
warnings.push(`配置 scope ${declaredPath} 在项目中不存在,保留为声明路径但标记为低置信`);
|
|
260
|
+
return [normalized];
|
|
261
|
+
}
|
|
262
|
+
function addCandidate(candidates, candidate) {
|
|
263
|
+
for (const resolved of candidate.evidence.length > 0 ? [candidate] : [candidate]) {
|
|
264
|
+
candidates.push({
|
|
265
|
+
...resolved,
|
|
266
|
+
path: normalizeScopePattern(resolved.path),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function addConfigScopeCandidates(input, discoveredRoots, candidates, warnings) {
|
|
271
|
+
const needsBackend = taskNeedsBackend(input);
|
|
272
|
+
const needsFrontend = taskNeedsFrontend(input);
|
|
273
|
+
if (needsBackend) {
|
|
274
|
+
for (const declared of input.config.scope?.backend ?? []) {
|
|
275
|
+
for (const expanded of expandWeakPath(input.projectPath, declared, "backend", discoveredRoots, warnings)) {
|
|
276
|
+
addCandidate(candidates, {
|
|
277
|
+
path: expanded,
|
|
278
|
+
source: "config.scope.backend",
|
|
279
|
+
confidence: pathExists(input.projectPath, expanded) ? "high" : "low",
|
|
280
|
+
role: "backend",
|
|
281
|
+
declared_path: declared,
|
|
282
|
+
evidence: ["configured backend scope"],
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (needsFrontend) {
|
|
288
|
+
for (const declared of input.config.scope?.frontend ?? []) {
|
|
289
|
+
for (const expanded of expandWeakPath(input.projectPath, declared, "frontend", discoveredRoots, warnings)) {
|
|
290
|
+
addCandidate(candidates, {
|
|
291
|
+
path: expanded,
|
|
292
|
+
source: "config.scope.frontend",
|
|
293
|
+
confidence: pathExists(input.projectPath, expanded) ? "high" : "low",
|
|
294
|
+
role: "frontend",
|
|
295
|
+
declared_path: declared,
|
|
296
|
+
evidence: ["configured frontend scope"],
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function repoMatches(repo, affectedRepos) {
|
|
303
|
+
if (affectedRepos.length === 0)
|
|
304
|
+
return true;
|
|
305
|
+
const name = repo.name.toLowerCase();
|
|
306
|
+
const repoPath = repo.path.toLowerCase();
|
|
307
|
+
return affectedRepos.some((affected) => {
|
|
308
|
+
const normalized = affected.toLowerCase();
|
|
309
|
+
return normalized === name
|
|
310
|
+
|| name.includes(normalized)
|
|
311
|
+
|| normalized.includes(name)
|
|
312
|
+
|| repoPath.includes(normalized);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function repoRole(repo) {
|
|
316
|
+
const combined = `${repo.name} ${repo.path} ${repo.lang} ${repo.framework}`.toLowerCase();
|
|
317
|
+
if (/react|vue|angular|frontend|client|ui|typescript/.test(combined))
|
|
318
|
+
return "frontend";
|
|
319
|
+
if (/spring|java|backend|server|api|service|go|python|node/.test(combined))
|
|
320
|
+
return "backend";
|
|
321
|
+
return "shared";
|
|
322
|
+
}
|
|
323
|
+
function addRepoCandidates(input, discoveredRoots, candidates, warnings) {
|
|
324
|
+
const affectedRepos = getAffectedRepos(input.classification);
|
|
325
|
+
for (const repo of input.config.repos ?? []) {
|
|
326
|
+
if (!repoMatches(repo, affectedRepos))
|
|
327
|
+
continue;
|
|
328
|
+
const role = repoRole(repo);
|
|
329
|
+
for (const declaredScope of repo.scope ?? []) {
|
|
330
|
+
const scopeLooksProjectRelative = declaredScope.replace(/\\/g, "/").startsWith(`${repo.path.replace(/\\/g, "/").replace(/\/$/, "")}/`);
|
|
331
|
+
const declared = scopeLooksProjectRelative
|
|
332
|
+
? normalizeRelPath(declaredScope)
|
|
333
|
+
: normalizeRelPath(declaredScope, repo.path);
|
|
334
|
+
const expandedPaths = pathExists(input.projectPath, declared)
|
|
335
|
+
? [declared]
|
|
336
|
+
: expandWeakPath(input.projectPath, declared, role, discoveredRoots, warnings);
|
|
337
|
+
for (const expanded of expandedPaths) {
|
|
338
|
+
addCandidate(candidates, {
|
|
339
|
+
path: expanded,
|
|
340
|
+
source: "config.repos.scope",
|
|
341
|
+
confidence: pathExists(input.projectPath, expanded) ? "high" : "medium",
|
|
342
|
+
role,
|
|
343
|
+
repo_name: repo.name,
|
|
344
|
+
base_path: normalizeRelPath(repo.path),
|
|
345
|
+
declared_path: declaredScope,
|
|
346
|
+
evidence: [`repo ${repo.name} scope`],
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function addRouteCandidates(input, candidates) {
|
|
353
|
+
for (const routePath of input.routeDecision?.scope?.allowed_paths ?? []) {
|
|
354
|
+
addCandidate(candidates, {
|
|
355
|
+
path: normalizeRelPath(routePath),
|
|
356
|
+
source: "route_decision.scope.allowed_paths",
|
|
357
|
+
confidence: "high",
|
|
358
|
+
role: "shared",
|
|
359
|
+
declared_path: routePath,
|
|
360
|
+
evidence: ["explicit route scope"],
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function addFingerprintCandidates(input, discoveredRoots, candidates) {
|
|
365
|
+
const needsBackend = taskNeedsBackend(input);
|
|
366
|
+
const needsFrontend = taskNeedsFrontend(input);
|
|
367
|
+
for (const root of discoveredRoots) {
|
|
368
|
+
if ((root.role === "backend" && !needsBackend) || (root.role === "frontend" && !needsFrontend))
|
|
369
|
+
continue;
|
|
370
|
+
addCandidate(candidates, {
|
|
371
|
+
path: root.path,
|
|
372
|
+
source: "project_fingerprint.source_roots",
|
|
373
|
+
confidence: "medium",
|
|
374
|
+
role: root.role,
|
|
375
|
+
evidence: root.evidence,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function buildSources(candidates, selectedPaths) {
|
|
380
|
+
const grouped = new Map();
|
|
381
|
+
for (const candidate of candidates) {
|
|
382
|
+
if (!selectedPaths.includes(candidate.path))
|
|
383
|
+
continue;
|
|
384
|
+
const key = [
|
|
385
|
+
candidate.source,
|
|
386
|
+
candidate.role,
|
|
387
|
+
candidate.repo_name ?? "",
|
|
388
|
+
candidate.base_path ?? "",
|
|
389
|
+
candidate.declared_path ?? candidate.path,
|
|
390
|
+
].join("|");
|
|
391
|
+
const current = grouped.get(key);
|
|
392
|
+
if (current) {
|
|
393
|
+
if (!current.resolved_paths.includes(candidate.path))
|
|
394
|
+
current.resolved_paths.push(candidate.path);
|
|
395
|
+
current.evidence.push(...candidate.evidence.filter((item) => !current.evidence.includes(item)));
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
grouped.set(key, {
|
|
399
|
+
source: candidate.source,
|
|
400
|
+
confidence: candidate.confidence,
|
|
401
|
+
role: candidate.role,
|
|
402
|
+
repo_name: candidate.repo_name,
|
|
403
|
+
base_path: candidate.base_path,
|
|
404
|
+
declared_paths: [candidate.declared_path ?? candidate.path],
|
|
405
|
+
resolved_paths: [candidate.path],
|
|
406
|
+
evidence: [...candidate.evidence],
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return [...grouped.values()];
|
|
411
|
+
}
|
|
412
|
+
function readonlyFromRepos(input) {
|
|
413
|
+
const affected = getAffectedRepos(input.classification);
|
|
414
|
+
const readonly = [];
|
|
415
|
+
for (const repo of input.config.repos ?? []) {
|
|
416
|
+
if (repoMatches(repo, affected))
|
|
417
|
+
continue;
|
|
418
|
+
for (const scope of repo.scope ?? []) {
|
|
419
|
+
readonly.push(normalizeRelPath(scope, repo.path));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return dedupePreserveOrder(readonly);
|
|
423
|
+
}
|
|
424
|
+
function isBroadRootOnly(paths) {
|
|
425
|
+
return paths.length === 1 && (paths[0] === "." || paths[0] === "./");
|
|
426
|
+
}
|
|
427
|
+
function buildReport(input, allowedPaths, readonlyPaths, newFilesAllowed, candidates, discoveredRoots, warnings, conflicts, status) {
|
|
428
|
+
return {
|
|
429
|
+
status,
|
|
430
|
+
project_root: input.projectPath,
|
|
431
|
+
path_semantics: {
|
|
432
|
+
storage_format: "project_relative_posix",
|
|
433
|
+
host_platform: process.platform,
|
|
434
|
+
case_sensitive: process.platform !== "win32" && process.platform !== "darwin",
|
|
435
|
+
},
|
|
436
|
+
allowed_paths: allowedPaths,
|
|
437
|
+
readonly_paths: readonlyPaths,
|
|
438
|
+
new_files_allowed: newFilesAllowed,
|
|
439
|
+
sources: buildSources(candidates, allowedPaths),
|
|
440
|
+
discovered_source_roots: discoveredRoots.map((root) => root.path),
|
|
441
|
+
warnings: [...new Set(warnings)],
|
|
442
|
+
conflicts: [...new Set(conflicts)],
|
|
443
|
+
recovery_commands: [
|
|
444
|
+
"soloforge doctor",
|
|
445
|
+
"soloforge config repair --dry-run",
|
|
446
|
+
"soloforge init --interactive",
|
|
447
|
+
"soloforge sync-templates",
|
|
448
|
+
"soloforge sync-adapters",
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
export function resolveTaskScope(input) {
|
|
453
|
+
if (input.routeDecision && !input.routeDecision.mutation_allowed) {
|
|
454
|
+
const outputArtifact = input.routeDecision.output_artifact;
|
|
455
|
+
const allowedPaths = [];
|
|
456
|
+
if (outputArtifact?.path) {
|
|
457
|
+
const dir = outputArtifact.path.replace(/\\/g, "/").replace(/\/[^/]+$/, "");
|
|
458
|
+
allowedPaths.push(ensureTrailingSlash(dir || "."));
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
allowedPaths.push(".soloforge/output/");
|
|
462
|
+
}
|
|
463
|
+
const readonlyPaths = dedupePreserveOrder(input.config.repos.flatMap((repo) => (repo.scope ?? []).map((scope) => normalizeRelPath(scope, repo.path))));
|
|
464
|
+
const candidates = allowedPaths.map((allowed) => ({
|
|
465
|
+
path: allowed,
|
|
466
|
+
source: "route_decision.output_artifact",
|
|
467
|
+
confidence: "high",
|
|
468
|
+
role: "output",
|
|
469
|
+
evidence: ["read-only route may only write declared output artifact directory"],
|
|
470
|
+
}));
|
|
471
|
+
return {
|
|
472
|
+
allowed_paths: allowedPaths,
|
|
473
|
+
readonly_paths: readonlyPaths,
|
|
474
|
+
new_files_allowed: true,
|
|
475
|
+
new_file_patterns: ["*.md"],
|
|
476
|
+
resolution: buildReport(input, allowedPaths, readonlyPaths, true, candidates, [], [], [], "resolved"),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const warnings = [];
|
|
480
|
+
const conflicts = [];
|
|
481
|
+
const candidates = [];
|
|
482
|
+
const discoveredRoots = discoverSourceRoots(input.projectPath);
|
|
483
|
+
addRouteCandidates(input, candidates);
|
|
484
|
+
addRepoCandidates(input, discoveredRoots, candidates, warnings);
|
|
485
|
+
addConfigScopeCandidates(input, discoveredRoots, candidates, warnings);
|
|
486
|
+
addFingerprintCandidates(input, discoveredRoots, candidates);
|
|
487
|
+
const selected = removeNestedBroadDuplicates(candidates
|
|
488
|
+
.filter((candidate) => candidate.confidence !== "low" || pathExists(input.projectPath, candidate.path))
|
|
489
|
+
.map((candidate) => candidate.path));
|
|
490
|
+
let allowedPaths = selected;
|
|
491
|
+
let status = "resolved";
|
|
492
|
+
if (allowedPaths.length === 0) {
|
|
493
|
+
allowedPaths = discoveredRoots.map((root) => root.path);
|
|
494
|
+
if (allowedPaths.length > 0) {
|
|
495
|
+
status = "fallback";
|
|
496
|
+
warnings.push("未从配置得到可信 scope,已退回真实项目源码树;建议运行 soloforge doctor 并修复 config scope。");
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
allowedPaths = ["."];
|
|
500
|
+
status = "blocked";
|
|
501
|
+
conflicts.push("无法从配置或项目指纹推断源码 scope;请运行 soloforge init --interactive 确认项目结构。");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (isBroadRootOnly(allowedPaths) && discoveredRoots.length > 0) {
|
|
505
|
+
allowedPaths = discoveredRoots.map((root) => root.path);
|
|
506
|
+
status = "fallback";
|
|
507
|
+
warnings.push("scope 仅为项目根,已收窄为真实源码根,避免过宽写入范围。");
|
|
508
|
+
}
|
|
509
|
+
const readonlyPaths = readonlyFromRepos(input)
|
|
510
|
+
.filter((readonly) => !allowedPaths.some((allowed) => matchesScopePattern(readonly, allowed)
|
|
511
|
+
|| matchesScopePattern(allowed, readonly)
|
|
512
|
+
|| isPathInScope(readonly, allowed, { caseSensitive: PATH_CASE_SENSITIVE })
|
|
513
|
+
|| isPathInScope(allowed, readonly, { caseSensitive: PATH_CASE_SENSITIVE })));
|
|
514
|
+
const newFilesAllowed = !input.degraded && status !== "blocked";
|
|
515
|
+
const report = buildReport(input, allowedPaths, readonlyPaths, newFilesAllowed, candidates, discoveredRoots, warnings, conflicts, status);
|
|
516
|
+
return {
|
|
517
|
+
allowed_paths: allowedPaths,
|
|
518
|
+
readonly_paths: readonlyPaths,
|
|
519
|
+
new_files_allowed: newFilesAllowed,
|
|
520
|
+
new_file_patterns: newFilesAllowed && input.classification.strategy === "full_pipeline"
|
|
521
|
+
? ["**/*"]
|
|
522
|
+
: undefined,
|
|
523
|
+
resolution: report,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
//# sourceMappingURL=scope_resolver.js.map
|