@cassiomc1/forgeloop 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +1 -1
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/CONTRIBUTING.md +90 -0
- package/DOCS_INDEX.md +13 -11
- package/ENG/c-development-eng.md +112 -0
- package/ENG/cpp-development-eng.md +109 -0
- package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
- package/ENG/go-development-eng.md +103 -0
- package/ENG/java-development-eng.md +125 -0
- package/ENG/nodejs-backend-development-eng.md +605 -0
- package/ENG/php-development-eng.md +104 -0
- package/ENG/rust-development-eng.md +422 -0
- package/ENG/sql-development-eng.md +108 -0
- package/ENG/swift-development-eng.md +111 -0
- package/ENG/typescript-development-eng.md +108 -0
- package/GUIDE_ROUTER.md +418 -9
- package/QUALITY_SCORECARD.md +1 -0
- package/README.md +44 -33
- package/THIRD_PARTY_NOTICES.md +19 -7
- package/completions/_forgeloop +3 -3
- package/completions/forgeloop.bash +3 -3
- package/completions/forgeloop.fish +7 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +55 -2
- package/docs/CLI_REFERENCE.md +28 -6
- package/docs/DOCUMENTATION_GUIDE.md +2 -1
- package/docs/GETTING_STARTED.md +59 -0
- package/docs/PACKAGE_CONTENTS.md +28 -14
- package/docs/RECIPES.md +23 -0
- package/docs/RELEASE_CHECKLIST.md +30 -2
- package/docs/TROUBLESHOOTING.md +100 -2
- package/docs/documentation-manifest.json +652 -0
- package/docs/protocol-requirements.json +77 -0
- package/package.json +19 -4
- package/schemas/routing-input.schema.json +1 -1
- package/scripts/CI_VALIDATORS.md +84 -11
- package/scripts/generate-agent-protocol-summary.mjs +36 -0
- package/src/commands/next.js +19 -7
- package/src/commands/task-create.js +84 -25
- package/src/commands/task-list.js +22 -2
- package/src/config/guides.json +44 -0
- package/src/core/build-script.js +151 -0
- package/src/core/c-cpp-project.js +143 -0
- package/src/core/cli-command-definitions.js +8 -1
- package/src/core/command-executors.js +5 -3
- package/src/core/command-input.js +140 -102
- package/src/core/contract-presets.js +82 -0
- package/src/core/error-codes.js +3 -3
- package/src/core/filesystem.js +1 -10
- package/src/core/go-project.js +206 -0
- package/src/core/java-project.js +403 -0
- package/src/core/multi-language-project.js +117 -0
- package/src/core/next-explanation.js +63 -0
- package/src/core/php-project.js +85 -0
- package/src/core/project-detection.js +1760 -52
- package/src/core/reconcile-closure.js +4 -1
- package/src/core/router.js +156 -3
- package/src/core/rust-project.js +400 -0
- package/src/core/sql-project.js +141 -0
- package/src/core/swift-project.js +200 -0
- package/src/core/typescript-project.js +349 -0
- package/src/core/xml-structure.js +123 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createContract } from "./contract.js";
|
|
2
|
+
|
|
3
|
+
const PRESET_DEFINITIONS = Object.freeze({
|
|
4
|
+
documentation: Object.freeze({
|
|
5
|
+
objective: "Complete the requested documentation change with verified project facts.",
|
|
6
|
+
constraints: ["Preserve repository-local instructions and existing public documentation contracts."],
|
|
7
|
+
risks: [],
|
|
8
|
+
verification: [{ id: "verify-documentation", text: "Relevant documentation checks pass.", type: "VERIFICATION" }],
|
|
9
|
+
successCriteria: [{ id: "complete-documentation", text: "The requested documentation change is complete and accurate.", type: "PRODUCT" }],
|
|
10
|
+
stopConditions: ["A required project fact or source cannot be confirmed."],
|
|
11
|
+
}),
|
|
12
|
+
bug: Object.freeze({
|
|
13
|
+
objective: "Diagnose and correct the requested bug without changing unrelated behavior.",
|
|
14
|
+
constraints: ["Preserve the existing public contract unless a breaking change is explicitly requested."],
|
|
15
|
+
risks: [],
|
|
16
|
+
verification: [{ id: "verify-bug-fix", text: "A focused regression check and proportional regression checks pass.", type: "VERIFICATION" }],
|
|
17
|
+
successCriteria: [{ id: "complete-bug-fix", text: "The reported behavior is corrected and the regression is covered.", type: "PRODUCT" }],
|
|
18
|
+
stopConditions: ["The failure cannot be reproduced or narrowed with available evidence."],
|
|
19
|
+
}),
|
|
20
|
+
feature: Object.freeze({
|
|
21
|
+
objective: "Implement the requested feature within the confirmed project scope.",
|
|
22
|
+
constraints: ["Preserve existing architecture and public contracts unless a change is explicitly requested."],
|
|
23
|
+
risks: [],
|
|
24
|
+
verification: [{ id: "verify-feature", text: "Focused behavior checks and proportional regression checks pass.", type: "VERIFICATION" }],
|
|
25
|
+
successCriteria: [{ id: "complete-feature", text: "The requested feature behavior is implemented and documented where applicable.", type: "PRODUCT" }],
|
|
26
|
+
stopConditions: ["Required product behavior or acceptance facts remain unresolved."],
|
|
27
|
+
}),
|
|
28
|
+
release: Object.freeze({
|
|
29
|
+
objective: "Prepare the requested release with independently verified artifact and publication evidence.",
|
|
30
|
+
constraints: [
|
|
31
|
+
"Do not publish, deploy, or mutate external release state without explicit authority.",
|
|
32
|
+
"Separate local checks, repository integration, publication, and deployment evidence.",
|
|
33
|
+
],
|
|
34
|
+
risks: ["publication"],
|
|
35
|
+
verification: [{ id: "verify-release", text: "Release identity, package/artifact integrity, and required checks are independently verified.", type: "VERIFICATION" }],
|
|
36
|
+
successCriteria: [{
|
|
37
|
+
id: "publish-release",
|
|
38
|
+
text: "The requested release publication status is independently verified.",
|
|
39
|
+
type: "PUBLICATION",
|
|
40
|
+
requiredPublicationStatus: "published",
|
|
41
|
+
}],
|
|
42
|
+
stopConditions: ["Publication authority, target, or identity evidence is not available."],
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const CONTRACT_PRESET_IDS = Object.freeze(Object.keys(PRESET_DEFINITIONS));
|
|
47
|
+
|
|
48
|
+
function normalizedClaims(claims) {
|
|
49
|
+
return [...new Set((Array.isArray(claims) ? claims : [])
|
|
50
|
+
.filter((claim) => typeof claim === "string" && claim.trim() !== "")
|
|
51
|
+
.map((claim) => claim.trim()))];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createPresetContract({ taskId, preset, claims = [] } = {}) {
|
|
55
|
+
if (!Object.prototype.hasOwnProperty.call(PRESET_DEFINITIONS, preset)) {
|
|
56
|
+
const error = new Error(`Unknown contract preset: ${preset}. Expected one of ${CONTRACT_PRESET_IDS.join(", ")}`);
|
|
57
|
+
error.code = "E_CONTRACT_PRESET_UNKNOWN";
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
const definition = PRESET_DEFINITIONS[preset];
|
|
61
|
+
const confirmedClaims = normalizedClaims(claims);
|
|
62
|
+
const unresolvedDecisions = confirmedClaims.length > 0
|
|
63
|
+
? []
|
|
64
|
+
: ["Confirm the concrete deliverables and affected behavior before implementation."];
|
|
65
|
+
return createContract({
|
|
66
|
+
taskId,
|
|
67
|
+
objective: definition.objective,
|
|
68
|
+
deliverables: confirmedClaims.length > 0 ? confirmedClaims : ["Deliverables confirmed by task scope."],
|
|
69
|
+
constraints: definition.constraints,
|
|
70
|
+
risks: definition.risks,
|
|
71
|
+
verification: definition.verification,
|
|
72
|
+
successCriteria: definition.successCriteria,
|
|
73
|
+
stopConditions: definition.stopConditions,
|
|
74
|
+
unresolvedDecisions,
|
|
75
|
+
sourceRefs: [`contract-preset:${preset}`],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function contractPresetDefinition(preset) {
|
|
80
|
+
if (!Object.prototype.hasOwnProperty.call(PRESET_DEFINITIONS, preset)) return null;
|
|
81
|
+
return structuredClone(PRESET_DEFINITIONS[preset]);
|
|
82
|
+
}
|
package/src/core/error-codes.js
CHANGED
|
@@ -788,8 +788,8 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
|
|
|
788
788
|
code: "E_RECONCILE_PHASE_INVALID",
|
|
789
789
|
category: "lifecycle",
|
|
790
790
|
classification: "PUBLIC_STABLE",
|
|
791
|
-
meaning: "reconcile-closure was invoked for a task that is not EXECUTING or
|
|
792
|
-
safeResolution: "reconcile-closure supports EXECUTING or
|
|
791
|
+
meaning: "reconcile-closure was invoked for a task that is not EXECUTING, VERIFYING, or REVIEWING.",
|
|
792
|
+
safeResolution: "reconcile-closure supports EXECUTING, VERIFYING, or REVIEWING tasks whose objective is already satisfied.",
|
|
793
793
|
}),
|
|
794
794
|
E_RECONCILE_UNSUPPORTED_DRIFT: Object.freeze({
|
|
795
795
|
code: "E_RECONCILE_UNSUPPORTED_DRIFT",
|
|
@@ -831,7 +831,7 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
|
|
|
831
831
|
category: "freshness",
|
|
832
832
|
classification: "PUBLIC_STABLE",
|
|
833
833
|
meaning: "The work-state checkpoint must be revalidated before the lifecycle can continue.",
|
|
834
|
-
safeResolution: "Run forgeloop reconcile-closure for externally satisfied EXECUTING tasks, or inspect the freshness reasons for other drift.",
|
|
834
|
+
safeResolution: "Run forgeloop reconcile-closure for externally satisfied EXECUTING, VERIFYING, or REVIEWING tasks, or inspect the freshness reasons for other drift.",
|
|
835
835
|
}),
|
|
836
836
|
E_DIAGNOSIS_REQUIRED: Object.freeze({
|
|
837
837
|
code: "E_DIAGNOSIS_REQUIRED",
|
package/src/core/filesystem.js
CHANGED
|
@@ -129,7 +129,6 @@ export async function assertSafePath(root, relativePath) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
let existing = destination;
|
|
132
|
-
let relativeExisting = path.normalize(relativePath);
|
|
133
132
|
while (true) {
|
|
134
133
|
try {
|
|
135
134
|
const info = await lstatWithTransientWindowsRetry(existing);
|
|
@@ -137,14 +136,7 @@ export async function assertSafePath(root, relativePath) {
|
|
|
137
136
|
throw new Error(`Path uses a symlink inside target directory: ${relativePath}`);
|
|
138
137
|
}
|
|
139
138
|
const resolvedRoot = await realpathWithTransientWindowsRetry(absoluteRoot);
|
|
140
|
-
|
|
141
|
-
// return different equivalent spellings (for example, short names or
|
|
142
|
-
// extended-length prefixes) for the root and a child path when those
|
|
143
|
-
// paths are canonicalized independently. Reconstructing the child from
|
|
144
|
-
// the canonical root keeps the comparison in one namespace without
|
|
145
|
-
// weakening the realpath containment check for junctions or symlinks.
|
|
146
|
-
const canonicalExisting = path.resolve(resolvedRoot, relativeExisting);
|
|
147
|
-
const resolvedExisting = await realpathWithTransientWindowsRetry(canonicalExisting);
|
|
139
|
+
const resolvedExisting = await realpathWithTransientWindowsRetry(existing);
|
|
148
140
|
if (!isPathWithin(resolvedRoot, resolvedExisting)) {
|
|
149
141
|
throw new Error(`Path escapes target directory: ${relativePath}`);
|
|
150
142
|
}
|
|
@@ -154,7 +146,6 @@ export async function assertSafePath(root, relativePath) {
|
|
|
154
146
|
const parent = path.dirname(existing);
|
|
155
147
|
if (parent === existing) throw new Error(`Path does not resolve inside target directory: ${relativePath}`);
|
|
156
148
|
existing = parent;
|
|
157
|
-
relativeExisting = path.dirname(relativeExisting);
|
|
158
149
|
}
|
|
159
150
|
}
|
|
160
151
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { createLanguageProject, portablePath } from "./multi-language-project.js";
|
|
4
|
+
|
|
5
|
+
const GO_DIRECTIVE = /^(module|go|toolchain|godebug|require|replace|exclude|retract|tool|ignore)\b/u;
|
|
6
|
+
const GO_WORK_DIRECTIVES = new Set(["go", "toolchain", "godebug", "use", "replace", "tool"]);
|
|
7
|
+
|
|
8
|
+
function invalidGoResult() {
|
|
9
|
+
return {
|
|
10
|
+
valid: false,
|
|
11
|
+
module: null,
|
|
12
|
+
goVersion: null,
|
|
13
|
+
toolchain: null,
|
|
14
|
+
uses: [],
|
|
15
|
+
localReplacements: [],
|
|
16
|
+
ignorePaths: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stripGoComment(line) {
|
|
21
|
+
const index = line.indexOf("//");
|
|
22
|
+
return (index < 0 ? line : line.slice(0, index)).trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseGoDirective(line) {
|
|
26
|
+
const block = line.match(/^(module|go|toolchain|godebug|require|replace|exclude|retract|use|tool|ignore)\b\s*\(\s*$/u);
|
|
27
|
+
const match = line.match(/^(module|go|toolchain|godebug|require|replace|exclude|retract|use|tool|ignore)\b\s*(?:\(([^)]*)\)|(.+))$/u);
|
|
28
|
+
if (block) return { name: block[1], value: "" };
|
|
29
|
+
if (!match) return null;
|
|
30
|
+
return {
|
|
31
|
+
name: match[1],
|
|
32
|
+
value: (match[2] ?? match[3] ?? "").trim(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function collectGoBlock(lines, start) {
|
|
37
|
+
const values = [];
|
|
38
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
39
|
+
const line = stripGoComment(lines[index]);
|
|
40
|
+
if (line === ")") return { values, end: index, valid: true };
|
|
41
|
+
if (line !== "") values.push(line);
|
|
42
|
+
}
|
|
43
|
+
return { values, end: lines.length, valid: false };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validGoDirectiveValue(name, value) {
|
|
47
|
+
if (!value || /\s{2,}/u.test(value)) return false;
|
|
48
|
+
if (name === "ignore") {
|
|
49
|
+
const portable = value.replaceAll("\\", "/");
|
|
50
|
+
const segments = portable.split("/");
|
|
51
|
+
return !value.includes("\\")
|
|
52
|
+
&& !portable.startsWith("/")
|
|
53
|
+
&& !/^[A-Za-z]:\//u.test(portable)
|
|
54
|
+
&& !segments.includes("..")
|
|
55
|
+
&& !value.includes("=>");
|
|
56
|
+
}
|
|
57
|
+
if (["require", "exclude"].includes(name)) return /^\S+\s+v\S+$/u.test(value);
|
|
58
|
+
if (name === "replace") return /^\S+(?:\s+v\S+)?\s+=>\s+\S+(?:\s+v\S+)?$/u.test(value);
|
|
59
|
+
if (name === "retract") return /^\S+(?:\s*,\s*\S+)?$/u.test(value);
|
|
60
|
+
if (name === "godebug") return /^\S+=\S+$/u.test(value);
|
|
61
|
+
if (name === "tool") return /^\S+(?:\s+v\S+)?$/u.test(value);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseGoModMetadataDirective(state, directive) {
|
|
66
|
+
if (directive.name === "module") {
|
|
67
|
+
if (state.module !== null || directive.value === "" || /\s/u.test(directive.value)) return false;
|
|
68
|
+
state.module = directive.value;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (directive.name === "go") {
|
|
72
|
+
if (state.goVersion !== null || !/^\d+(?:\.\d+){1,2}$/u.test(directive.value)) return false;
|
|
73
|
+
state.goVersion = directive.value;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (directive.name === "toolchain") {
|
|
77
|
+
if (state.toolchain !== null || !/^\S+$/u.test(directive.value)) return false;
|
|
78
|
+
state.toolchain = directive.value;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseGoModBlockDirective(lines, index, directive) {
|
|
85
|
+
const block = collectGoBlock(lines, index);
|
|
86
|
+
const valid = block.valid && block.values.every((value) => validGoDirectiveValue(directive.name, value));
|
|
87
|
+
return {
|
|
88
|
+
valid,
|
|
89
|
+
end: block.end,
|
|
90
|
+
ignorePaths: valid && directive.name === "ignore" ? block.values : [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseGoModLine(lines, index, state) {
|
|
95
|
+
const line = stripGoComment(lines[index]);
|
|
96
|
+
if (line === "" || line.startsWith("//")) return { valid: true, end: index, ignorePaths: [] };
|
|
97
|
+
const directive = parseGoDirective(line);
|
|
98
|
+
if (!directive || !GO_DIRECTIVE.test(line)) return { valid: false, end: index, ignorePaths: [] };
|
|
99
|
+
const metadataValid = parseGoModMetadataDirective(state, directive);
|
|
100
|
+
if (metadataValid !== null) return { valid: metadataValid, end: index, ignorePaths: [] };
|
|
101
|
+
if (line.endsWith("(")) return parseGoModBlockDirective(lines, index, directive);
|
|
102
|
+
const valid = validGoDirectiveValue(directive.name, directive.value);
|
|
103
|
+
return {
|
|
104
|
+
valid,
|
|
105
|
+
end: index,
|
|
106
|
+
ignorePaths: valid && directive.name === "ignore" ? [directive.value] : [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function parseGoMod(text) {
|
|
111
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) return invalidGoResult();
|
|
112
|
+
const lines = text.replaceAll("\r\n", "\n").split("\n");
|
|
113
|
+
const state = { module: null, goVersion: null, toolchain: null, valid: true };
|
|
114
|
+
const ignorePaths = [];
|
|
115
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
116
|
+
const parsed = parseGoModLine(lines, index, state);
|
|
117
|
+
state.valid &&= parsed.valid;
|
|
118
|
+
ignorePaths.push(...parsed.ignorePaths);
|
|
119
|
+
index = parsed.end;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
valid: state.valid && state.module !== null,
|
|
123
|
+
module: state.module,
|
|
124
|
+
goVersion: state.goVersion,
|
|
125
|
+
toolchain: state.toolchain,
|
|
126
|
+
uses: [],
|
|
127
|
+
localReplacements: [],
|
|
128
|
+
ignorePaths: [...new Set(ignorePaths)],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function parseGoWork(text) {
|
|
133
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) {
|
|
134
|
+
return { valid: false, goVersion: null, uses: [] };
|
|
135
|
+
}
|
|
136
|
+
const lines = text.replaceAll("\r\n", "\n").split("\n");
|
|
137
|
+
let goVersion = null;
|
|
138
|
+
const uses = [];
|
|
139
|
+
let valid = true;
|
|
140
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
141
|
+
const line = stripGoComment(lines[index]);
|
|
142
|
+
if (line === "") continue;
|
|
143
|
+
const directive = parseGoDirective(line);
|
|
144
|
+
if (!directive) {
|
|
145
|
+
valid = false;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (directive.name === "go") {
|
|
149
|
+
if (goVersion !== null || !/^\d+(?:\.\d+){1,2}$/u.test(directive.value)) valid = false;
|
|
150
|
+
else goVersion = directive.value;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (directive.name === "toolchain") {
|
|
154
|
+
if (!/^\S+$/u.test(directive.value)) valid = false;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!GO_WORK_DIRECTIVES.has(directive.name)) {
|
|
158
|
+
valid = false;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (directive.name !== "use") {
|
|
162
|
+
if (line.endsWith("(")) {
|
|
163
|
+
const block = collectGoBlock(lines, index);
|
|
164
|
+
if (!block.valid || !block.values.every((value) => validGoDirectiveValue(directive.name, value))) valid = false;
|
|
165
|
+
index = block.end;
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const values = line.endsWith("(") ? collectGoBlock(lines, index) : { values: [directive.value], end: index, valid: true };
|
|
170
|
+
if (!values.valid || values.values.some((value) => !validGoDirectiveValue("use", value)
|
|
171
|
+
|| value.startsWith("/") || /^[A-Za-z]:/u.test(value))) {
|
|
172
|
+
valid = false;
|
|
173
|
+
} else {
|
|
174
|
+
uses.push(...values.values);
|
|
175
|
+
}
|
|
176
|
+
index = values.end;
|
|
177
|
+
}
|
|
178
|
+
return { valid: valid && goVersion !== null && uses.length > 0, goVersion, uses: [...new Set(uses)] };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function inspectGoProject({ projectRoot, manifestRelative, manifestText, manifestName, targetRoot, projectFiles }) {
|
|
182
|
+
const parsed = manifestName === "go.work" ? parseGoWork(manifestText) : parseGoMod(manifestText);
|
|
183
|
+
const localUses = manifestName === "go.work"
|
|
184
|
+
? parsed.uses.filter((use) => projectFiles.some((file) => file.kind === "go"
|
|
185
|
+
&& parseGoMod(file.text).valid
|
|
186
|
+
&& portablePath(targetRoot, path.dirname(file.path)) === path.posix.normalize(path.posix.join(projectRoot, use))))
|
|
187
|
+
: [];
|
|
188
|
+
const primary = parsed.valid && (manifestName === "go.mod" || localUses.length > 0);
|
|
189
|
+
const primarySignals = primary
|
|
190
|
+
? [manifestName === "go.mod" ? `${manifestRelative}:module` : `${manifestRelative}:workspace`]
|
|
191
|
+
: [];
|
|
192
|
+
const supportingSignals = [];
|
|
193
|
+
if (parsed.goVersion) supportingSignals.push(`${manifestRelative}:go=${parsed.goVersion}`);
|
|
194
|
+
if (parsed.toolchain) supportingSignals.push(`${manifestRelative}:toolchain=${parsed.toolchain}`);
|
|
195
|
+
for (const use of localUses) supportingSignals.push(`${manifestRelative}:use=${use}`);
|
|
196
|
+
return createLanguageProject({
|
|
197
|
+
kind: "go",
|
|
198
|
+
root: projectRoot,
|
|
199
|
+
manifest: manifestRelative,
|
|
200
|
+
frameworks: primary ? ["go"] : [],
|
|
201
|
+
primary,
|
|
202
|
+
primarySignals,
|
|
203
|
+
supportingSignals,
|
|
204
|
+
internal: { ...parsed, valid: parsed.valid, manifestName, localUses },
|
|
205
|
+
});
|
|
206
|
+
}
|